塊裝置剖析之塊設備註冊
阿新 • • 發佈:2019-01-10
本文所有內容基於核心版本Linux-v3.2.40。
add_disk()是塊設備註冊的核心介面,是塊裝置驅動的最後一步,也是最關鍵的一步,下面就分析一下該函式實現的具體細節。
當申請完一個gendisk並進行初始化之後,就可以藉助add_diak將之註冊到通用塊層。表面上看,add_diak似乎是一個平淡無奇的函式,其實不然,它涉及到了後備儲存器、kobj_map、分割槽、請求佇列等一大堆東西,但在這裡我們主要關心塊裝置的註冊過程,所以會省略掉一些內容,有興趣的讀者可以去閱讀原始碼,原始碼位置在block/genhd.c。
下面先把add_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 probe結構體,該結構體包含了它所有必需的資訊,如以上程式碼所示。所以,我們只需要知道裝置的裝置號就可以從bdev_map中還原回真正的裝置結構體(probe結構體中的data),這也真是函式kobj_lookup完成的功能。
Line30:register_disk(disk)註冊gendisk到通用塊層,如果深究這將是一個相對複雜的函式。
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合併、電梯演算法等一系列的內容,這裡不詳細討論,如果感興趣可參考這位仁兄的部落格 - 。這裡只說明一點: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。
斷斷續續分幾次才把這篇文章寫完,難免會有不足之處,如果發現歡迎批評指正。
本文乃原創文章,請勿隨意轉載,如需轉載請詳細標明轉載出處。
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 = &
-
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合併、電梯演算法等一系列的內容,這裡不詳細討論,如果感興趣可參考這位仁兄的部落格 - 。這裡只說明一點: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。
斷斷續續分幾次才把這篇文章寫完,難免會有不足之處,如果發現歡迎批評指正。
本文乃原創文章,請勿隨意轉載,如需轉載請詳細標明轉載出處。