插入SD卡,注册驱动成功,那么在开发板的目录/dev/block下会出现SD卡的设备节
点。
179为主设备号,定义于include/linux/major.h #define MMC_BLOCK_MAJOR 179 179:0代表这块SD卡的设备节点mmcblk0,179:1代表这块SD卡的第一个分区mmcblk0p1,即主分区,如果有第二个分区,那就是179:2,最多可以有7个分区,即179:1~179:7(定义于block.c alloc_disk(1 << 3);)。不过,SD卡一般只有一个分区。如果有第二块SD卡插入,将会建立设备节点mmcblk1(179:8)和mmcblk1p1(179:9)。
下面通过对块设备驱动block.c的分析,看看SD卡是如何在块设备层建立节点和传输数据的。 4.1 数据结构 每个驱动都会有一个数据结构。幸运的是,我们SD卡块设备驱动的数据结构相对简单,在mmc_blk_data里,主要有两个成员,struct gendisk *disk和struct mmc_queue queue。
1) struct gendisk 是general disk的缩写,代表个通用的块设备,其中包括块设备的主分区结构struct hd_struct part0, 块设备的行为函数struct block_device_operations *fops,以及请求队列struct request_queue *queue等。 2) struct request_queue 存放所有I/O调度的算法。
3) struct request 请求是I/O调度者调度的对象,其中的结构struct bio是整个请求队列的核心,具体内容请参看LDD3。
4.2 块设备驱动 首先浏览一下源码,
static int __init mmc_blk_init(void) {
register_blkdev(MMC_BLOCK_MAJOR, \// 注册主设备号(若注册成功,/proc/devices的块设备下会出现mmc) mmc_register_driver(&mmc_driver); return 0; }
static struct mmc_driver mmc_driver = { .drv = {
.name = \ },
.probe = mmc_blk_probe, .remove = mmc_blk_remove,
.suspend = mmc_blk_suspend, .resume = mmc_blk_resume, };
static int mmc_blk_probe(struct mmc_card *card) {
struct mmc_blk_data *md; md = mmc_blk_alloc(card); mmc_blk_set_blksize(md, card); mmc_set_drvdata(card, md); add_disk(md->disk); return 0; ... ... }
4.2.1 设备驱动的初始化函数
仍然可以将驱动程序的初始化mmc_blk_probe(struct mmc_card *card)归纳为以下内容,
? ? ? ?
初始化设备驱动的数据结构mmc_blk_data,并挂载到card->dev.driver_data 实现块备驱动的功能函数struct block_device_operations *fops 注册设备,即注册kobject,建立sys文件,发送uevent等 其他需求,如mmc_blk_set_blksize(md, card);
1) 初始化mmc_blk_data
static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) {
struct mmc_blk_data *md;
md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);
md->read_only = mmc_blk_readonly(card);
md->disk = alloc_disk(1 << 3); // 分配了8个可用设备
spin_lock_init(&md->lock); md->usage = 1;
ret = mmc_init_queue(&md->queue, card, &md->lock); md->queue.issue_fn = mmc_blk_issue_rq; md->queue.data = md;
md->disk->major = MMC_BLOCK_MAJOR; md->disk->first_minor = devidx << MMC_SHIFT; md->disk->fops = &mmc_bdops; md->disk->private_data = md;
md->disk->queue = md->queue.queue; md->disk->driverfs_dev = &card->dev;
blk_queue_logical_block_size(md->queue.queue, 512); ... ... return md; }
完成初始化后,通过mmc_set_drvdata(card, md);将数据挂载到card->dev.driver_data下。 2) 功能函数
static const struct block_device_operations mmc_bdops = { .open = mmc_blk_open, .release = mmc_blk_release, .getgeo = mmc_blk_getgeo, .owner = THIS_MODULE, };
static int mmc_blk_open(struct block_device *bdev, fmode_t mode) {
struct mmc_blk_data *md = mmc_blk_get(bdev->bd_disk);
... ... }
struct block_device {
dev_t bd_dev; /* it's a search key */ struct inode * bd_inode; /* will die */ struct super_block * bd_super; ... ... };
与字符驱动类似,通过dev_t和inode找到设备。 3) 注册驱动
void add_disk(struct gendisk *disk) {
blk_register_region(disk_devt(disk), disk->minors, NULL, exact_match, exact_lock, disk); register_disk(disk); blk_register_queue(disk); ... ... }
blk_register_region()在linux中实现了一种利用哈希表管理设备号的机制。 register_disk()对应alloc_disk(),完成对块设备的注册,其实质是通过
register_disk()->blkdev_get()->__blkdev_get()->rescan_partitions()->add_partitions()添加
分区,建立设备节点。
blk_register_queue()对应blk_init_queue()完成对请求队列的注册,其实质是通过elv_register_queue()注册请求队列的算法。
关于块设备更为具体的代码分析可参看linux那些事。 4.2.2 请求队列
mmc_init_queue申请并初始化一个请求队列,开启负责处理这个请求队列的守护进程。