2.6.32内核的generic_make_request()解读

/****************************************************************
    *  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_list,因此相当于把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().
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值