通用块层是一个内核组件,处理来自系统中的所有块设备发出的请求,接着上一层,处理提交过来的缓存区头。如果上一章address_space是关键的数据结构,这一章是bio和bio_vec,这两个数据结构
一、 两个重要的数据结构
struct bio {
01 sector_t bi_sector;
02 struct bio *bi_next; /* request queue link */
03 struct block_device *bi_bdev;
04 unsigned long bi_flags; /* status, command, etc */
05 unsigned long bi_rw; /* bottom bits READ/WRITE,
06 unsigned short bi_vcnt; /* how many bio_vec's */
07 unsigned short bi_idx; /* current index into bvl_vec */
08 unsigned int bi_phys_segments;
09 unsigned int bi_size; /* residual I/O count */
10 unsigned int bi_seg_front_size;
11 unsigned int bi_seg_back_size;
12 unsigned int bi_max_vecs; /* max bvl_vecs we can hold */
13 unsigned int bi_comp_cpu; /* completion CPU */
14 atomic_t bi_cnt; /* pin count */
15 struct bio_vec *bi_io_vec; /* the actual vec list */
16 bio_end_io_t *bi_end_io;
17 void *bi_private;
18 bio_destructor_t *bi_destructor; /* destructor */
19 struct bio_vec bi_inline_vecs[0];
20};
第1行块I/O操作的第一个磁盘扇区
第2行链接到请求队列中的下一个bio
第3行指向块设备描述符的指针
第4行bio的状态标志
第5行I/O操作标志
第6行bio的bio_vec数组中段的数目
第7行bio的bio_vec数组中段的当前索引值
第8行合并之后bio中物理段的数目
第9行需要传送的字节数
第10行硬件段合并算法使用
第11行硬件段合并算法使用
第12行bio的bio_vec数组中允许的最大段数
第15行指向bio的bio_vec数组中的段的指针
第16行bio的I/O操作结束时调用的方法
第17行通用块层和块设备驱动程序的I/O完成方法使用的指针
第18行释放bio时调用的析构方法
bio_vec的数据结构
struct bio_vec {
1 struct page *bv_page;
2 unsigned int bv_len;
3 unsigned int bv_offset;
4};
第1行指向段的页框中页描述符的指针
第2行段的字节长度
第3行页框中段数据的偏移量
二、 两者之间的关系
引用一张图
三、 提交请求
当通用块层提交一个I/O操作请求时,内核所执行的步骤如下。分配一个bio描述符,然后对它进行初始化。主要的入口函数是generic_make_request函数
void generic_make_request(struct bio *bio)
{
01 if (current->bio_tail) {
02 /* make_request is active */
03 *(current->bio_tail) = bio;
04 bio->bi_next = NULL;
05 current->bio_tail = &bio->bi_next;
06 return;
07 }
08 BUG_ON(bio->bi_next);
09 do {
10 current->bio_list = bio->bi_next;
11 if (bio->bi_next == NULL)
12 current->bio_tail = ¤t->bio_list;
13 else
14 bio->bi_next = NULL;
15 __generic_make_request(bio);
16 bio = current->bio_list;
17 } while (bio);
18 current->bio_tail = NULL; /* deactivate */
19}
第1行判断是否为bio的最后一个
第3行把就请求的bio放到队尾
第4行设置bio的下一个节点为空
第10行目前的bio链表指向请求bio的下一个结点
第11行如果bio的下个结点为空
第12行就把目前bio的队尾的指针指向目前bio的链表。
第13-15行否则的话,设置bio的下个结点为空,调用generic_make_request函数
static inline void __generic_make_request(struct bio *bio)
{
01 struct request_queue *q;
02 sector_t old_sector;
03 int ret, nr_sectors = bio_sectors(bio);
04 dev_t old_dev;
05 int err = -EIO;
06
07 might_sleep();
08
09 if (bio_check_eod(bio, nr_sectors))
10 goto end_io;
11 old_sector = -1;
12 old_dev = 0;
13 do {
14 char b[BDEVNAME_SIZE];
15
16 q = bdev_get_queue(bio->bi_bdev);
17 if (unlikely(!q)) {
18 printk(KERN_ERR
19 "generic_make_request: Trying to access "
20 "nonexistent block-device %s (%Lu)\n",
21 bdevname(bio->bi_bdev, b),
22 (long long) bio->bi_sector);
23 goto end_io;
24 }
25 if (unlikely(nr_sectors > q->max_hw_sectors)) {
26 printk(KERN_ERR "bio too big device %s (%u > %u)\n",
27 bdevname(bio->bi_bdev, b),
28 bio_sectors(bio),
29 q->max_hw_sectors);
30 goto end_io;
31 }
32 if (unlikely(test_bit(QUEUE_FLAG_DEAD, &q->queue_flags)))
33 goto end_io;
34
35 if (should_fail_request(bio))
36 goto end_io;
37 blk_partition_remap(bio);
38
39 if (bio_integrity_enabled(bio) && bio_integrity_prep(bio))
40 goto end_io;
41
42 if (old_sector != -1)
43 trace_block_remap(q, bio, old_dev, bio->bi_sector,
44 old_sector);
45
46 trace_block_bio_queue(q, bio);
47
48 old_sector = bio->bi_sector;
49 old_dev = bio->bi_bdev->bd_dev;
50
51 if (bio_check_eod(bio, nr_sectors))
52 goto end_io;
53
54 if (bio_discard(bio) && !q->prepare_discard_fn) {
55 err = -EOPNOTSUPP;
56 goto end_io;
57 }
58
59 ret = q->make_request_fn(q, bio);
60 } while (ret);
61
62 return;
63
64end_io:
65 bio_endio(bio, err);
66}
67
第3行获得扇区数
第9行检查bio->bi_sector没有超过块设备的扇区数。如果超过,将bio->bi_flags设置为BIO_EOF标志。调用bio_endio()函数,并终止。
第16行获取与块设备相关的请求队列q;其地址存放在块设备描述符的bd_disk字段中。
第37行调用blk_partition_remap函数检查块设备是否指的是一个磁盘分区。如果是,则从bio->bi_bdev获取分区的hd_struct描述符。从而执行下面的子操作
a、 根据数据传送的方向,更新hd_struct描述符中的read_sectors和reads值,或write_sectors和writes值
b、 调整bio->bi_sector值使得把相对于分区的起始扇区号转变为相对于整个磁盘的盲区号。
c、 将bio->bi_bdev设置为整个磁盘的块设备描述符
第59行调用q->make_request_fn访求将bio请求插入请求队列q中。
四、 总结
通用块层主要做的工作,就是初始化一个bio,然后把它插入到请求队列中。同时可以将缓冲区放在高端内存中,实现”零-复制”等一些功能。