http://blog.chinaunix.net/uid-30282771-id-5113192.html
块设备剖析之块设备注册 - add_disk()函数解析
2015-07-07 23:08:45
分类: LINUX
本文所有内容基于内核版本Linux-v3.2.40。
add_disk()是块设备注册的内核接口,是块设备驱动的最后一步,也是最关键的一步,下面就分析一下该函数实现的具体细节。
当申请完 一个 gendisk并进行初始化之后,就可以借助add_diak将之注册到通用块层。表面上看,add_diak似乎是一个平淡无奇的函数,其实不然,它涉及到了后备存储器、kobj_map、分区、请求队列等一大堆东西,但在这里我们主要关心块设备的注册过程,所以会省略掉一些内容,有兴趣的读者可以去阅读源码,源码位置在block/genhd.c。
下面先把add_disk分段贴出,并加以我自己的理解,如有不准确或不恰当的地方,欢迎批评指正。
点击(此处)折叠或打开
- void add_disk(struct gendisk *disk)
- {
- struct backing_dev_info *bdi;
- dev_t devt;
- int retval;
-
- /* 申请设备号 */
- retval = blk_alloc_devt(&disk->part0, &devt);
- if (retval) {
- WARN_ON(1);
- return;
- }
- disk_to_dev(disk)->devt = devt; /* 记录gendisk的设备号 */
-
- /* ->major and ->first_minor aren't supposed to be
- * dereferenced from here on, but set them just in case.
- */
- disk->major = MAJOR(devt);
- disk->first_minor = MINOR(devt);
-
- disk_alloc_events(disk);
-
- /* Register BDI before referencing it from bdev */
- bdi = &disk->queue->backing_dev_info;
- bdi_register_dev(bdi, disk_devt(disk));
-
- /* 将gendisk添加到kobj_map中 */
- blk_register_region(disk_devt(disk), disk->minors, NULL,
- exact_match, exact_lock, disk);
- register_disk(disk); /* 注册gendisk到通用块层 */
- blk_register_queue(disk); /* 注册请求队列到通用块层 */
-
- /*
- * Take an extra ref on queue which will be put on disk_release()
- * so that it sticks around as long as @disk is there.
- */
- WARN_ON_ONCE(blk_get_queue(disk->queue));
-
- retval = sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj,
- "bdi");
- WARN_ON(retval);
-
- disk_add_events(disk);
- }
disk 202, 64 /dev/sda
disk 202, 65 /dev/sda1
disk 202, 66 /dev/sda2
因此次设备号的计算一般为:disk->first_minor + part->partno
Line27~29: 接下来将申请的gendisk管理起来。如何管理呢?内核采用了与字符设备相似的方法,使用了一个全局的struct kobj_map结构体bdev_map,它其实是一个struct probe指针数组,用 主设备号 作为数组的索引,如下所示:
点击(此处)折叠或打开
- struct kobj_map {
- struct probe *probes[255];
- struct mutex *lock;
- };
-
- struct probe {
- struct probe *next; /* 下一个probe */
- dev_t dev; /* 起始设备号 */
-
- /* 设备号的范围,如起始设备号是12,range是10,
- * 那么该结构体关联设备号为[12, 22)的所有设备
- */
- unsigned long range;
- struct module *owner;
- kobj_probe_t *get; /* 用于获取该设备内嵌的kobj */
- int (*lock)(dev_t, void *);
- void *data; /* 一般指向设备的实际结构体 */
- };
对于一个块设备,它唯一的对应于一个struct probe结构体,该结构体包含了它所有必需的信息,如以上代码所示。所以,我们只需要知道设备的设备号就可以从 bdev_map中还原回真正的设备结构体(probe结构体中的data),这也真是函数kobj_lookup完成的功能。
Line30:register_disk(disk)注册gendisk到通用块层,如果深究这将是一个相对复杂的函数。
点击(此处)折叠或打开
- void register_disk(struct gendisk *disk)
- {
- struct device *ddev = disk_to_dev(disk);
- struct block_device *bdev;
- struct disk_part_iter piter;
- struct hd_struct *part;
- int err;
-
- /* No minors to use for partitions */
- if (!disk_part_scan_enabled(disk)) {
- goto exit; /* 该设备不支持分区或强制不扫描分区 */
- }
-
- /* No such device (e.g., media were just removed) */
- if (!get_capacity(disk))
- goto exit;
-
- bdev = bdget_disk(disk, 0);
- if (!bdev)
- goto exit;
-
- bdev->bd_invalidated = 1;
- err = blkdev_get(bdev, FMODE_READ, NULL);
- if (err < 0)
- goto exit;
- blkdev_put(bdev, FMODE_READ);
-
- exit:
- /* announce disk after possible partitions are created */
- dev_set_uevent_suppress(ddev, 0);
- kobject_uevent(&ddev->kobj, KOBJ_ADD);
-
- /* announce possible partitions */
- disk_part_iter_init(&piter, disk, 0);
- while ((part = disk_part_iter_next(&piter)))
- kobject_uevent(&part_to_dev(part)->kobj, KOBJ_ADD);
- disk_part_iter_exit(&piter);
- }
1. bdget_disk():为gendisk分配block_device结构体,作为gendisk 在bdevfs中的抽象。该函数最终借助bdget()实现,具体的实现细节 可参考我的另一篇博文- bdget()函数详解 。
2. blkdev_get():以只读方式打开该设备,进行分区扫描,并设置block_device与gendisk、hd_struct之间的关联,以及gendisk的block_device与hd_struct的block_device之间的关联。关于该函数的详细分析,感兴趣的读者可自行分析。
我们再回到add_disk()函数:
Line31 :通过blk_register_queue()将该gendisk的请求队列request_queue注册到通用块层。请求队列主要在数据的读写时用到,涉及到request合并、电梯算法等一系列的内容,这里不详细讨论,如果感兴趣可参考这位仁兄的博客 - blk_register_queue()函数学习。这里只说明一点:request_queue里面包含了许多与底层设备相关的内容,比如扇区大小,该queue可容纳的最大扇区数等等,可通过blk_queue_logical_block_size()设置扇区的大小,比如blk_queue_logical_block_size(gendisk->queue, 4096),设置gendisk使用4k大小的扇区,该设置对新型设备(如flash等)往往是必须的,因为它们可操作的最小单元就是4k。
这样整个函数的主要部分就分析完了。当该函数执行完成后,你期望的设备就会乖乖的出现在/dev目录下,比如/dev/sdb, /dev/ramdisk,而不需要像注册字符设备那样再搞一个class。
断断续续分几次才把这篇文章写完,难免会有不足之处,如果发现欢迎批评指正。
本文乃原创文章,请勿随意转载,如需转载请详细标明转载出处。