转载:谢谢原作者: 块设备驱动实战基础篇二 (继续完善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: [email protected]
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一下该设备是否可以正常进行读写操作了,至此一个基本的块设备驱动我们已经完成,接下来我们继续介绍请求转发过滤后到请求真正处理完成后的回调处理过程,进一步完整的走完请求处理全过程。