转载:谢谢原作者: 块设备驱动实战基础篇二 (继续完善170行过滤驱动代码至200行)
1.3塊設備驅動關鍵數據結構及函數API詳細剖析
經過上節四個步驟我們已經熟悉并實戰了一個最基本的過濾塊設備驅動的設計技巧,我們這一節先不繼續實戰,我們本節把上節170行代碼中接觸到的塊設備核心數據結構和API接口剖析一下,把這部分掌握和理解一下。
?
我們把上節涉及的六個數據結構和相關API接口羅列一下:
?塊設備核心數據結構
| gendisk | 塊設備倉庫 |
| hd_struct | 塊設備分區 |
| block_device | 文件系統層使用的塊設備描述符 |
| request_queue | 倉庫的關卡(請求隊列) |
| request | 包含多個bio的大請求 |
| bio | 單個請求 |
塊設備核心API接口
| register_blkdev | 注冊并申請門牌號 |
| alloc_disk | 申請倉庫 |
| blk_alloc_queue | 申請倉庫的關卡 |
| blk_queue_make_request | 注冊倉庫的加工處理函數 |
| add_disk | 將申請的倉庫注冊到內核中,成為合法倉庫 |
?
?
?
結合上節塊設備在Linux中的總體結構圖來看,我們再貼一下這個圖,根據這個圖我們將請求從文件系統層構建出bio開始,直到進入到請求處理函數,分析一下其過程,這個過程會擴展描述到核心數據結構中的幾個關鍵字段,大家先試著熟悉,然后我們會給出核心數據結構的嵌套關系圖,讓大家更清楚的認識一下各個核心數據結構之間的關系,最后我們會詳細剖析各個數據結構和API接口功能。
?
?
?
上層文件系統發來I/O請求時,我們在塊設備驅動的請求處理函數make_request上接收到的是bio,每個bio結構中都包含了一個bio_vec數組。bio_vec是用于記錄一段連續內存空間位置信息的數據結構,包括描述這段內存連續空間的頁指針描述符bv_page,數據長度bv_len,數據在一個頁中的開始位置bv_offset。如此分析我們知道bio請求包含了一個bio_vec數組,意味著包含了一組內存連續空間。
?
接下來文件系統層調用通用塊層的generic_make_request函數,將請求插入到倉庫的關卡即請求隊列上request_queue,如果隊列沒有使用,則繼續調用到我們注冊的請求處理函數make_request上。
?
請求隊列request_queue中的每一個元素是一個請求集合request,request包含了多個bio請求,同時多個request通過鏈表鏈接在一起,鏈表頭在request_queue上;同樣request中的多個bio也通過鏈表鏈接在一起。
?
磁盤描述符gendisk通過指向該磁盤的請求隊列的指針queue與其請求隊列關聯起來。內核用結構block_device代表一個塊設備對象,它是文件系統層使用的數據結構,如:整個硬盤或特定分區都是一個塊設備對象。如果該結構代表一個分區,則其成員bd_part指向設備的分區結構;如果該結構代表設備,則其成員bd_disk指向設備的通用硬盤結構gendisk。
?
?????? 根據上面的描述,我們把數據結構關系畫一下,如下,大家可以更清楚的看一下各個結構之間的關系,也更加能夠從整體上把握IO請求在操作系統內核中的描述和處理。
?
?
?
?
?
根據上面這個圖,讓我們可以繼續總結一下,充分把握好它們的結構關系。塊設備會有一個倉庫描述gendisk,如果倉庫有分區,則分區由hd_struct描述,同時文件系統會對倉庫及分區都用一個獨立的block_device進行描述;文件系統產生bio請求,多個bio會組裝成一個request,多個request會組裝到request_queue請求隊列上。
?
好了,至此相信大家能夠很牢固的記住各結構之間的關系了,并且能夠根據上圖從整體上把握好應用層數據讀寫請求在操作系統內核中的處理關系,下面我們詳細剖析一下各個數據結構及API的功能,大家可以作為一個參考,再后面實戰時可以繼續回來進行查閱學習。
?
?
?? block_device關鍵成員剖析
| 類型 | 字段 | 說明 |
| dev_t | bd_dev | 塊設備的主設備號和次設備號 |
| struct inode* | bd_inode | 指向bdev文件系統中塊設備對應的文件索引節點的指針 |
| int | bd_openers | 計數器,統計塊設備已經被打開了多少次 |
| struct mutex | bd_mutex | 打開或關閉的互斥量 |
| struct list_head | bd_inodes | 已打開的塊設備文件的索引節點鏈表的首部 |
| void* | bd_holders | 塊設備描述符的當前所有者 |
| struct block_device* | bd_contains | 如果塊設備是一個分區,則指向整個磁盤的塊設備描述符;否則,指向該塊設備描述符 |
| unsigned | bd_block_size | 塊大小 |
| struct hd_struct* | bd_part | 指向分區描述符的指針(如果該塊設備不是一個分區,則為NULL) |
| unsigned | bd_part_count | 計數器,統計包含在塊設備中的分區已經被打開了多少次 |
| struct gendisk* | bd_disk | 指向塊設備中基本磁盤的gendisk結構的指針 |
| struct list_head | bd_list | 用于塊設備描述符鏈表的指針 |
| unsigned long | bd_private | 指向塊設備持有者的私有數據的指針 |
?
?? hd_struct關鍵成員剖析
| 類型 | 字段 | 說明 |
| sector_t | start_sect | 磁盤中分區的起始扇區 |
| sector_t | nr_sects | 分區的長度(總共的扇區數) |
| int | policy | 如果分區是只讀的,則置為1;否則為0 |
| int | partno | 磁盤中分區的相對索引 |
?
?? gendisk關鍵成員剖析
| 類型 | 字段 | 說明 |
| int | major | 磁盤主設備號, 每個塊設備都有唯一的主設備號,在這個塊設備上建立的分區都使用這個相同的主設備號。具有相同主設備號的設備,使用相同的驅動程序。 |
| int | first_minor | 與磁盤關聯的第一個次設備號。在某一個設備上首先創建的設備的初始次設備號為0,在名稱中不顯示,如sda;在這個設備上依次建立的其他設備,此設備號在0基礎上依次加1,并在名稱中顯示,如sda1,sda2。 |
| int | minors | 與磁盤關聯的次設備號范圍。規定了可以在這個設備上創建多少個分設備(分區)。當次設備號數量是1時,表示這個設備不能被分區。 |
| char | disk_name | 磁盤的標準命名(通常是相應設備文件的規范名稱) |
| struct hd_struct | part0 | 磁盤的分區信息 |
| const struct block_device_operations * | fops | 指向塊設備操作函數集的指針 |
| struct request_queue * | queue | 指向磁盤請求隊列的指針 |
| void * | private_data | 塊設備驅動程序的私有數據 |
| int | flags | 描述磁盤類型的標志 |
?? 塊設備gendisk fops函數指針集
| 類型 | 方法 | 參數 | 觸發事件 |
| int | (*open) | struct block_device*, fmode_t | 打開塊設備文件,增加引用計數 |
| int | (*release) | struct gendisk*, fmode_t | 關閉對塊設備文件的最后一個引用,減少引用計數 |
| int | (*ioctl) | struct block_device*, fmode_t, unsigned,unsigned long | 在塊設備文件上發出ioctl()系統調用 |
?? request_queue請求隊列描述符中的關鍵字段
| 類型 | 字段 | 說明 |
| struct list_head | queue_head | 待處理請求的鏈表 |
| make_request_fn* | make_request_fn | 設備驅動程序的請求處理函數 |
?? request描述符的關鍵字段
| 類型 | 字段 | 說明 |
| struct list_head | queuelist | 請求隊列鏈表的指針 |
| struct bio* | bio | 請求中第一個沒有完成傳送操作的bio,不能直接對該成員進行訪問;而要使用rq_for_each_bio訪問 |
| struct bio* | biotail | 請求鏈表中末尾的bio |
?? bio結構中的關鍵字段
| 類型 | 字段 | 說明 |
| sector_t | bi_sector | 塊I/O操作的第一個磁盤扇區 |
| struct bio* | bi_next | 鏈接到請求隊列中的下一個bio |
| struct block_device * | bi_bdev | 指向塊設備描述符的指針 |
| unsigned long | bi_flags | bio的狀態標志 |
| unsigned long | bi_rw | I/O操作標志 |
| unsigned short | bi_vcnt | bio的bio_vec數組中段的數目 |
| unsigned short | bi_idx | bio的bio_vec數組中段的當前索引值 |
| unsigned int | bi_phys_segments | 合并之后bio中物理段的數目 |
| unsigned int | bi_size | 需要傳送的字節數 |
| unsigned int | bi_seg_front_size | 第一個可合并的段大小 |
| unsigned int | bi_seg_back_size | 最后一個可合并的段大小 |
| unsigned int | bi_max_vecs | bio的bio_vec數組中允許的最大段數 |
| struct bio_vec* | bi_io_vec | 指向bio的bio_vec數組中的段的指針 |
| atomic_t | bi_cnt | bio的引用計數 |
| bio_end_io_t* | bi_end_io | bio的I/O操作結束時調用的方法 |
| void* | bi_private | 通用塊層和塊設備驅動程序的I/O完成方法使用的指針 |
?? bio_vec結構中的字段
| 類型 | 字段 | 說明 |
| struct page* | bv_page | 指向段的頁框中頁描述符的指針 |
| unsigned int | bv_len | 段的字節長度 |
| unsigned int | bv_offset | 頁框中中段數據的偏移量 |
?? 核心API函數
| 類型 | 函數名 | 輸入參數 | 返回值 | 說明 | ? |
| int | register_blkdev | unsigned int major, const char* name | 成功返回主設備號,失敗返回一個負數。 | 向系統申請注冊一個名為“name”的主設備號,當主設備號設為0時由內核自動分配一個可用的主設備號;若自己指定時,需要確保不與已有設備沖突。 | ? |
| struct gendisk* | alloc_disk | int minors | 成功返回一個指向gendisk描述符的指針,失敗返回NULL | 分配一個gendisk結構,minors為次設備號的總數,一般也就是磁盤分區的數量,為1時表示該設備不能被分區,此后minors不能被修改 | ? |
| struct request_queue* | blk_alloc_queue | gfp_t gfp_mask | 成功返回一個指向request_queue的指針,失敗時返回NULL | 申請請求隊列,并給隊列分配空間,該隊列需要用戶自己去調用blk_queue_make_request函數進行初始化,其中的make_request_fn函數也需要用戶自己實現 | ? |
| void | blk_queue_make_request | struct request_queue* q, make_request_fn* mfn | 無返回 | 初始化一個設備的請求隊列,其參數make_request_fn需要我們自己實現 | ? |
| void | add_disk | struct gendisk *disk | 無返回 | 將gendisk添加到系統中。調用該函數后,會在“/dev/”下顯示出塊設備名字,設備名字即是disk->disk_name的內容。對add_disk()的調用必須發生在驅動程序的初始化工作完成并能響應磁盤的請求之后。 | ? |
| void | del_gendisk | struct gendisk* disk | 無返回 | 將gendisk從系統中刪除。gendisk是一個引用計數結構,通常對del_gendisk的調用會刪除gendisk中的最終計數,但是沒有機制能保證其肯定發生。因此當調用此函數后,該結構可能繼續存在(而且內核可能會調用我們提供的各種方法)。 | |
| void | put_disk | struct gendisk* disk | 無返回 | 釋放驅動分配的gendisk結構 | |
| void | unregister_blkdev | unsigned int major, const char *name | 無返回 | 注銷驅動程序,釋放申請的主設備號 | |
| void | blk_cleanup_queue | struct request_queue *q | 無返回 | 釋放分配的請求隊列 | |
| void | bio_endio | struct bio *bio, int error | 無返回 | 返回對bio請求的處理結果,第一個參數即為被處理的bio指針,第二個參數成功時為0,失敗時為-ERRNO。 | |
?
?
?
1.4繼續完善170行代碼至200行 - 加入bio過濾功能
?
剛才我們已經看到了一個真時的虛擬塊設備驅動,我們看看這個塊設備加載到系統中,linux內核的IO棧發生了怎樣的變化。
?
?
?
看到有什么問題嗎?對了,請求到我們這塊就結束了,為啥,通道被堵上了,那怎么打通呢?這是我們接下來增加30行代碼至200行,來把通道打開,注意僅僅是修改了make_request這個函數,并且我們增加了新的東東,請大家跟這我們進一步學習。
?
?
?
??????
?????? 區別在于下半部,請求達到fbd_dev1和fbd_dev2后會繼續被過濾到更底層的塊設備/dev/sdb和/dev/sdc上,接下來我們看看這一步是如何在新增的30行代碼中做到的。
?
仍然是fbd_driver.c fbd_driver.h Makefile三個文件,Makefile文件內容不變,我們不再貼出,然后頭文件我們先貼一下。
?
? 1#ifndef? _FBD_DRIVER_H
? 2#define? _FBD_DRIVER_H
? 3#include <linux/init.h>
? 4#include <linux/module.h>
? 5#include <linux/blkdev.h>
? 6#include <linux/bio.h>
? 7#include <linux/genhd.h>
? 8
? 9#define SECTOR_BITS???????????? (9)
?10#define DEV_NAME_LEN??????????? 32
?11
?12#define DRIVER_NAME????????????"filter driver"
?13
?14#define DEVICE1_NAME???????????"fbd1_dev"
?15#define DEVICE1_MINOR?????????? 0
?16#define DEVICE2_NAME???????????"fbd2_dev"
?17#define DEVICE2_MINOR?????????? 1
?18
?19struct fbd_dev {
?20???????? struct request_queue *queue;
?21????????struct gendisk *disk;
?22????????sector_t size;????????? /* devicesize in Bytes */
?23????????char lower_dev_name[DEV_NAME_LEN];
?24????????struct block_device *lower_bdev;
?25};
?26#endif
?
頭文件基本沒有變化,包括仍然包含5個基本頭文件,主要是新增加了數據結構fbd_dev的三個成員,從22行到24行,如下:
?22???????? sector_t size;????????? /* device size in Bytes */
?23????????char lower_dev_name[DEV_NAME_LEN];
?24????????struct block_device *lower_bdev;
?
?????? size記錄將來要創建的fbd_dev設備的容量大小,在第一節中我們是定義了一個常量宏即512M,現在我們既然要加入BIO過濾功能,這個容量需要保持與fbd_dev底層設備大小一致,這個容量的獲取我們在后面會分析到。然后是lower_dev_name記錄了底層設備的文件名字,lower_bdev則保存著底層設備的block_device描述符,這個指針的用處后面我們也會講解。
?
?
?
我們把完善后的fbd_driver.c再貼一下,來分析一下如果設計make_request函數,讓請求可以通過我們的過濾塊設備后被提交到真正的底層設備上去,而不是直接在我們這一層返回退出。
?
? 1 /**
? 2 ?*?fbd-driver - filter block device driver
? 3?*? Author: Talk@studio
? 4 **/
? 5 #include "fbd_driver.h"
? 6
? 7 static int fbd_driver_major = 0;
? 8
? 9 static struct fbd_dev fbd_dev1 =
?10 {
?11????????.queue = NULL,
?12????????.disk = NULL,
?13 ????????.lower_dev_name = "/dev/sdb",
?14????????.lower_bdev = NULL,
?15????????.size = 0
?16 };
?17
?18 static struct fbd_dev fbd_dev2 =
?19 {
?20????????.queue = NULL,
?21????????.disk = NULL,
?22????????.lower_dev_name = "/dev/sdc",
?23????????.lower_bdev = NULL,
?24????????.size = 0
?25 };
?26
?27 static int fbddev_open(struct inode *inode,struct file *file);
?28 static int fbddev_close(struct inode*inode, struct file *file);
?29
?30 static struct block_device_operationsdisk_fops = {
?31?? ??????.open = fbddev_open,
?32????????.release = fbddev_close,
?33????????.owner = THIS_MODULE,
?34 };
?35
?36 static int fbddev_open(struct inode *inode,struct file *file)
37 {
?38????????printk("device is opened by:[%s]\n", current->comm);
?39????? ???return 0;
?40 }
?41
?42 static int fbddev_close(struct inode*inode, struct file *file)
?43 {
?44????????printk("device is closed by:[%s]\n", current->comm);
?45????????return 0;
?46 }
?47
?48 static int make_request(structrequest_queue *q, struct bio *bio)
?49 {
?50????????struct fbd_dev *dev = (struct fbd_dev *)q->queuedata;
?51????????printk("device [%s] recevied [%s] io request, "
?52???????????????? "access on dev sector[%llu], length is [%u] sectors.\n",
?53???????????????? dev->disk->disk_name,
?54???????????????? bio_data_dir(bio) == READ ?"read" : "write",
?55???????????????? bio->bi_sector,
?56???????????????? bio_sectors(bio));
?57
?58????????bio->bi_bdev = dev->lower_bdev;
?59????????submit_bio(bio_rw(bio), bio);
?60????????return 0;
?61 }
?62
?63 static int dev_create(struct fbd_dev *dev,char *dev_name, int major, int minor)
?64 {
?65????????int ret = 0;
?66
?67????????/* init fbd_dev */
?68????????dev->disk = alloc_disk(1);
?69????????if (!dev->disk) {
?70???????????????? printk("alloc diskerror");
?71???????????????? ret = -ENOMEM;
?72???????????????? goto err_out1;
73???????? }
?74
?75????????dev->queue = blk_alloc_queue(GFP_KERNEL);
?76????????if (!dev->queue) {
?77???????????????? printk("alloc queueerror");
?78?? ??????????????ret = -ENOMEM;
?79???????????????? goto err_out2;
?80????????}
?81
?82????????/* init queue */
?83????????blk_queue_make_request(dev->queue, make_request);
?84????????dev->queue->queuedata = dev;
?85
?86????????/* init gendisk */
?87? ???????strncpy(dev->disk->disk_name,dev_name, DEV_NAME_LEN);
?88????????dev->disk->major = major;
?89????????dev->disk->first_minor = minor;
?90????????dev->disk->fops = &disk_fops;
91
?92????????dev->lower_bdev = open_bdev_excl(dev->lower_dev_name, FMODE_WRITE| FMODE_READ, dev->lower_bdev);
?93????????if (IS_ERR(dev->lower_bdev)) {
?94???????????????? printk("Open thedevice[%s]'s lower dev [%s] failed!\n", dev_name, dev->lower_dev_name);
?95???????????????? ret = -ENOENT;
?96???????????????? goto err_out3;
?97????????}
?98
?99????????dev->size = get_capacity(dev->lower_bdev->bd_disk) <<SECTOR_BITS;
100
101???????? set_capacity(dev->disk,(dev->size >> SECTOR_BITS));
102
103???????? /* bind queue to disk */
104???????? dev->disk->queue = dev->queue;
105
106???????? /* add disk to kernel */
107???????? add_disk(dev->disk);
108???????? return 0;
109err_out3:
110???????? blk_cleanup_queue(dev->queue);
111err_out2:
112???????? put_disk(dev->disk);
113err_out1:
114???????? return ret;
115 }
116
117 staticvoid dev_delete(struct fbd_dev *dev, char *name)
118 {
119???????? printk("delete the device[%s]!\n", name);
120???????? close_bdev_excl(dev->lower_bdev);
121
122???????? blk_cleanup_queue(dev->queue);
123???????? del_gendisk(dev->disk);
124 ????????put_disk(dev->disk);
125 }
126
127 staticint __init fbd_driver_init(void)
128 {
129???????? int ret;
130
131???????? /* register fbd driver, get the drivermajor number*/
132???????? fbd_driver_major =register_blkdev(fbd_driver_major, DRIVER_NAME);
133???????? if (fbd_driver_major < 0) {
134???????????????? printk("get majorfail");
135???????????????? ret = -EIO;
136???????????????? goto err_out1;
137???????? }
138
139? ???????/* create the first device */
140???????? ret = dev_create(&fbd_dev1,DEVICE1_NAME, fbd_driver_major, DEVICE1_MINOR);
141???????? if (ret) {
142???????????????? printk("create device[%s] failed!\n", DEVICE1_NAME);
143???????????????? goto err_out2;
144???????? }
145
146???????? /* create the second device */
147???????? ret = dev_create(&fbd_dev2,DEVICE2_NAME, fbd_driver_major, DEVICE2_MINOR);
148???????? if (ret) {
149???????????????? printk("create device[%s] failed!\n", DEVICE2_NAME);
150???? ????????????goto err_out3;
151???????? }
152???????? return ret;
153err_out3:
154???????? dev_delete(&fbd_dev1,DEVICE1_NAME);
155err_out2:
156???????? unregister_blkdev(fbd_driver_major,DRIVER_NAME);
157err_out1:
158???????? return ret;
159 }
160
161 staticvoid __exit fbd_driver_exit(void)
162 {
163???????? /* delete the two devices */
164???????? dev_delete(&fbd_dev2,DEVICE2_NAME);
165???????? dev_delete(&fbd_dev1,DEVICE1_NAME);
166
167???????? /* unregister fbd driver */
168???????? unregister_blkdev(fbd_driver_major,DRIVER_NAME);
169???????? printk("block device driver exitsuccessfuly!\n");
170 }
171
172module_init(fbd_driver_init);
173module_exit(fbd_driver_exit);
174MODULE_LICENSE("GPL");
?
?
?????? 為了迅速看出fbd_driver.c相對于第一個版本的差異,大家可以比較一下,可以用我們在上冊內核編譯時介紹的diff 命令對比一下第一節中的fbd_driver.c和本節的fbd_driver.c有什么差異,我們把patch文件也貼一下,并繼續分析一下fbd_driver.c代碼。
?
Patch文件:
---fbd_driver_stage1/fbd_driver.c?????2013-02-25 22:45:23.000000000 -0800
+++fbd_driver_stage2/fbd_driver.c?????2013-02-26 19:45:05.000000000 -0800
@@ -6,8 +6,23 @@
?
?static int fbd_driver_major = 0;
?
-staticstruct fbd_dev fbd_dev1 = {NULL};
-staticstruct fbd_dev fbd_dev2 = {NULL};
+staticstruct fbd_dev fbd_dev1 =
+{
+?????? .queue = NULL,
+?????? .disk = NULL,
+?????? .lower_dev_name = "/dev/sdb",
+?????? .lower_bdev = NULL,
+?????? .size = 0
+};
+
+staticstruct fbd_dev fbd_dev2 =
+{
+?????? .queue = NULL,
+?????? .disk = NULL,
+?????? .lower_dev_name = "/dev/sdc",
+?????? .lower_bdev = NULL,
+?????? .size = 0
+};
?
?static int fbddev_open(struct inode *inode,struct file *file);
?static int fbddev_close(struct inode *inode,struct file *file);
@@ -40,7 +55,8 @@
??????????????? bio->bi_sector,
??????????????? bio_sectors(bio));
?
-?????? bio_endio(bio, bio->bi_size, 0);
+?????? bio->bi_bdev = dev->lower_bdev;
+?????? submit_bio(bio_rw(bio), bio);??
??????? return 0;
}
?
@@ -49,7 +65,6 @@
??????? int ret = 0;
?
??????? /* init fbd_dev */
-?????? dev->size = DEV_SIZE;
??????? dev->disk = alloc_disk(1);
??????? if (!dev->disk) {
??????????????? printk("alloc diskerror");
@@ -73,6 +88,16 @@
??????? dev->disk->major = major;
??????? dev->disk->first_minor = minor;
??????? dev->disk->fops = &disk_fops;
+??????
+?????? dev->lower_bdev =open_bdev_excl(dev->lower_dev_name, FMODE_WRITE | FMODE_READ, dev->lower_bdev);
+?????? if (IS_ERR(dev->lower_bdev)) {
+?????????????? printk("Open thedevice[%s]'s lower dev [%s] failed!\n", dev_name, dev->lower_dev_name);
+?????????????? ret = -ENOENT;?
+?????????????? goto err_out3;
+?????? }
+
+?????? dev->size = get_capacity(dev->lower_bdev->bd_disk)<< SECTOR_BITS;
+??????
??????? set_capacity(dev->disk,(dev->size >> SECTOR_BITS));
?
??????? /* bind queue to disk */
@@ -81,6+106,8 @@
??????? /* add disk to kernel */
??????? add_disk(dev->disk);
??????? return 0;
+err_out3:
+?????? blk_cleanup_queue(dev->queue);
?err_out2:
??????? put_disk(dev->disk);
?err_out1:
@@ -90,6 +117,8 @@
?static void dev_delete(struct fbd_dev *dev,char *name)
?{
??????? printk("delete the device[%s]!\n", name);
+?????? close_bdev_excl(dev->lower_bdev);
+
??????? blk_cleanup_queue(dev->queue);
??????? del_gendisk(dev->disk);
??????? put_disk(dev->disk);
?
首先看一下9-25行代碼,這里我們將fbd_dev1和fbd_dev2兩個設備的描述符初始化了一下,與第一版本不同,我們針對每個成員都進行了賦值,如下:
?
? 9 static struct fbd_dev fbd_dev1 =
?10 {
?11????????.queue = NULL,
?12????????.disk = NULL,
?13????????.lower_dev_name = "/dev/sdb",
?14????????.lower_bdev = NULL,
?15????????.size = 0
?16 };
?17
?18 static struct fbd_dev fbd_dev2 =
?19 {
?20????????.queue = NULL,
?21????????.disk = NULL,
?22????????.lower_dev_name = "/dev/sdc",
?23????????.lower_bdev = NULL,
?24????????.size = 0
?25 };
?
以fbd_dev1為例,成員queue和disk指針依然賦值為NULL,表示還沒有為它們分配好數據結構,然后lower-_dev_name指定為”/dev/sdb”,表示fbd_dev1這個塊設備底層的塊設備是sdb設備,由此我們會想到,后續進入fbd_dev1設備的請求,我們會將其過濾轉發到sdb上,在此我們通過簡單靜態賦值的方式實現,在后面我們的項目實戰訓練中,我們會帶領大家實現如何完善我們的代碼做到靈活的動態指定底層設備;lower_bdev指針也被賦值為NULL,后面我們就會講到如何獲取sdb設備的block_device描述符,最后size初始化為0。同樣fbd_dev2也進行了一樣的初始化操作。通過初始化我們將fbd_dev1和fbd_dev2兩個設備各自綁定了其底層的設備,這部分也是為后續真正在IO路徑上實現請求過濾轉發做好了基本準備,但是大家一定會清楚,現在的準備工作其實還很簡單,fbd_dev設備還沒有真正與sd#設備建立聯系,我們繼續往下分析。
?
?
直接看92-97行,這一段代碼完成了fbd_dev中lower_bdev和size成員的最終賦值操作,我們看到lower-_bdev指針通過調用open_bdev_excl函數獲得,該函數是內核用于打開指定路徑的塊設備并返回block_device數據結構指針的函數,通過該函數我們獲取到了底層/dev/sd#設備的block_device地址,最終與sd#設備真正的建立了聯系,然后99行,通過調用get_capacity函數我們獲取到了底層設備的容量大小,然后在101行把該容量也設置進fbd_dev設備的gendisk描述符中,至此過濾功能的準備工作徹底完成。以上也是fbd_dev設備創建過程中新增加的處理邏輯。
?
?
然而設備創建好,還需在make_request請求處理函數上重新進行了一下設計,才能真正做到在IO路徑上對請求進行過濾轉發,我們看48-61行的make_request函數發生了哪些改變,如下,我們再貼一下。
?
?48 static int make_request(structrequest_queue *q, struct bio *bio)
?49 {
?50????????struct fbd_dev *dev = (struct fbd_dev *)q->queuedata;
?51????????printk("device [%s] recevied [%s] io request, "
?52???????????????? "access on dev sector[%llu], length is [%u] sectors.\n",
?53???????????????? dev->disk->disk_name,
?54???? ????????????bio_data_dir(bio) == READ ?"read" : "write",
?55???????????????? bio->bi_sector,
?56???????????????? bio_sectors(bio));
?57
?58????????bio->bi_bdev = dev->lower_bdev;
?59????????submit_bio(bio_rw(bio), bio);
?60????????return 0;
?61 }
?
?
關鍵的地方是58-59行,不像第一節的代碼,我們這里的請求處理函數完成了一個重要的功能,它把傳入參數bio重新修飾了,58這一行中我們把bio->bi_bdev賦值為底層設備的指針lower_bdev,而lower_bdev就是我們剛才介紹的在設備初始化中通過調用open_bdev_excl函數獲得的,也就是說我們告訴請求的下一站地址去哪,聯系我們舉的圖書館的例子,請求就這樣被一層一層傳遞下去了,最后59行,我們調用請求提交函數把我們修飾完的請求繼續提交給底層的塊設備驅動了,這個函數是submit_bio,不同于第一個版本中調用bio_endio直接掐掉請求終止結束,這里通過submit_bio繼續把請求轉發下去了。
?
?????? 至此我們的過濾塊設備驅動真正做到了轉發請求,我們的驅動程序終于具備了一個正常的塊設備的基本功能了,我們把bio在內核棧中的流動過程再畫一張圖描述一下。
?
?
?????? 我們趕緊試試吧,make完成后,我們加載fbd_driver.ko模塊看看,然后在/dev/下找到我們的設備文件fbd_disk,然后dd一下該設備是否可以正常進行讀寫操作了,至此一個基本的塊設備驅動我們已經完成,接下來我們繼續介紹請求轉發過濾后到請求真正處理完成后的回調處理過程,進一步完整的走完請求處理全過程。
總結
以上是生活随笔為你收集整理的转载:谢谢原作者: 块设备驱动实战基础篇二 (继续完善170行过滤驱动代码至200行)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 转载:谢谢原作者:块设备驱动实战基础篇一
- 下一篇: 转载:谢谢原作者:块设备驱动实战基础篇三