/****************************************************************
* 2.6.32内核的generic_make_request解读
* Blog : noshape.cublog.cn
***************************************************************/
当文件系统向通用块层提交一个I/O请求时,bio_alloc()函数会被调用分配一个新的bio描述符,并且会设置bio描述符的一些字段值,如bi_sector/bi_size/bi_io_vec/bi_rw,然后会调用generic_make_request()函数。下面重点介绍这个函数。
1490 /*
1491 * We only want one ->make_request_fn to be active at a time,
1492 * else stack usage with stacked devices could be a problem.
1493 * So use current->bio_{list,tail} to keep a list of requests
1494 * submited by a make_request_fn function.
1495 * current->bio_tail is also used as a flag to say if
1496 * generic_make_request is currently active in this task or not.
1497 * If it is NULL, then no make_request is active. If it is non-NULL,
1498 * then a make_request is active, and new requests should be added
1499 * at the tail
1500 */
上面一大段注释的大概意思是说,用task_struct中的bio_{list,tail}来保存了由某个make_request_fn()函数提交的的request 链表,这样做的目的是内核需要保证在某个时候只允许一个make_request_fn()在执行;bio_tail还有一个作用是用来在当前task中,generic_make_request()是否是active还是inactive.如果current->bio_tail为NULL,则表明没有任何的make_request是active.否则,有某个make_request是active,所有新的new requests都应该加到bio_tail中。
struct task_struct {
......
struct bio *bio_list,**bio_tail;
......
};
1501 void generic_make_request(struct bio *bio)
1502 {
1503 if (current->bio_tail) {
1504 /* make_request is active */
1505 *(current->bio_tail) = bio;
1506 bio->bi_next = NULL;
1507 current->bio_tail = &bio->bi_next;
1508 return;
1509 }
从1503至1509行,我们看到了利用指针的指针来添加把bio添加到链表的方式,千万不要被那么多指针给搞晕,在进入1504之前,我们先假设bio_tail存放着某个bio的bio_next地址,也就下列语句所示。
struct bio bio_pre;
struct bio *bio_pre_p = &bio_pre;
current->bio_tail = &bio_pre_p->bi_next;
该处其实就是*(current->bio_tail)和bio_pre->bi_next是相等的。因此1504至1506可以下面比较容易理解的方式表达出来:
bio_pre_p->bi_next = bio;
bio->next = NULL;
因此,其实就是把bio加入到链表中来,并把该bio设为链表的最后一个单元。1507行重新更新了bio_tail,让它指向链表中最后一个bio的next指针。
到这里,我们的一个疑问是,current->bio_tail如果非NULL的话,那么接下来的所有generic_make_request()调用都会在这里返回,bio只是很简单地加入到当前进程的bio_tail就返回,那么什么时候generic_make_request()的后半部分才会被调用呢?让我们继续往下。
1510 /* following loop may be a bit non-obvious, and so deserves some
1511 * explanation.
1512 * Before entering the loop, bio->bi_next is NULL (as all callers
1513 * ensure that) so we have a list with a single bio.
1514 * We pretend that we have just taken it off a longer list, so
1515 * we assign bio_list to the next (which is NULL) and bio_tail
1516 * to &bio_list, thus initialising the bio_list of new bios to be
1517 * added. __generic_make_request may indeed add some more bios
1518 * through a recursive call to generic_make_request. If it
1519 * did, we find a non-NULL value in bio_list and re-enter the loop
1520 * from the top. In this case we really did just take the bio
1521 * of the top of the list (no pretending) and so fixup bio_list and
1522 * bio_tail or bi_next, and call into __generic_make_request again.
1523 *
1524 * The loop was structured like this to make only one call to
1525 * __generic_make_request (which is important as it is large and
1526 * inlined) and to keep the structure simple.
1527 */
接着的又是一大段注释,我们需要注意的是,在继续后面的do-while循环前,bio->bi_next为NULL(这是由所有的调用者都必须保证),因此这个链表只有唯一一个bio成员。我们假设这个bio是从一串很长的链表中取下来的,所以我们bio_list设为bio->bi_next(实质为NULL)和bio_tail设为&bio_tail,因此相当于把bio_list初始化成一个将被添加到链表的新bio. __generic_make_request()可能会真的会通过递归的方式来添加更多的bio,在这种情况下,我们会发现一个none-NULL的bio_list,并且会重新进入这个loop循环。这样我们就会真的是从链表头取下bio并且更新bio_list/bio_tail或者bi_next,然后再次进入__generic_make_request.
1528 BUG_ON(bio->bi_next);
1529 do {
1530 current->bio_list = bio->bi_next;
1531 if (bio->bi_next == NULL)
1532 current->bio_tail = ¤t->bio_list;
1533 else
1534 bio->bi_next = NULL;
1535 __generic_make_request(bio);
1536 bio = current->bio_list;
1537 } while (bio);
1538 current->bio_tail = NULL; /* deactivate */
1539 }
我们结合在刚开始提出的generic_make_request()什么时候才会调用的问题以及上面的注释和代码来分析,从注释中我们得到一个很重要的线索就是,在__generic_make_request()可能会递归调用generic_make_request().下面我通过一个假设的提交bio步骤来解释下.
1. generic_make_request()第一次进入,current->bio_tail为NULL,因此直接往do-while()循环而来。
2. bio->next为NULL,因此current->bio_list被赋值为NULL,而current->bio_tail被赋值为current->bio_list的地址。
3. 调用__generic_make_request(),在该函数中再次递归调用generic_make_request().
4. 再此进入generic_make_request()时,从步骤2我们可以知道current->bio_tail实质上指向current->bio_list地址,因此,一个新的bio加入到bio_list/bio_tail的链表中来,然后递归第二层的generic_make_request()退出至__generic_make_request()中,而__gneric_make_request()退出至第一层的generic_make_request()的do-while()循环中来。
5. 行1536把步骤4添加的bio从current->bio_list取下,重新开始至步骤2.直至__generic_make_request()不再递归调用generic_make_request().
generic_make_request()函数到此结束。再次回首程序的1503至1509,这一部分的代码主要就是用来出来递归的情况,只是简单地把bio挂到bio_list/bio_tail链表然后直接退出留后面的do-while()循环处理。而task_struct中的bio_list/bio_tail则是用来保存递归调用加入的bio。程序的构思是非常的巧妙,但是实际上__generic_make_request并没有递归调用到generic_make_request()函数,或者是为了后面版本的考虑。
__generic_make_request()做的事情很简单,调用blk_partition_remap()函数检查该块设备是否是一个磁盘分区,如果是个分区,则需要做一些转化,特别是要把bio->bio_sector值,因为bio_sector的值是相对于分区的起始扇区号,我们需要把他转华为相对于整个磁盘的扇区号。然后再调用q->make_request_fn()把bio请求插入到请求队列中。
q->make_request_fn()把通用块层和I/O调度层连接起来,这个函数的指定是在外围的block驱动(如mmc block,mtd block)里
调用blk_init_queue()时指定的,整个调用流程大概似乎如此:
blk_init_queue()
--> blk_init_queue_node()
--> blk_queue_make_request(q,__make_request)
--> q->make_request_fn = mfn
由此看出,q->make_request_fn()实际上调用的就是__make_request().