Chinaunix首页 | 论坛 | 博客
  • 博客访问: 83793
  • 博文数量: 12
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 331
  • 用 户 组: 普通用户
  • 注册时间: 2013-05-17 19:34
文章分类

全部博文(12)

文章存档

2014年(1)

2013年(11)

我的朋友

分类: LINUX

2013-06-26 19:31:06

1、分配bio结构
    当向通用块层提交一个IO操作请求的时候,假设被请求的数据块在磁盘上是相邻的,并且内核要已经知道了他们的物理位置。那么首先第一步就是执行bio_alloc()函数分配 一个新的bio描述符,然后内核通过设置一些字段的值来初始化bio描述符。该函数主要做的工作如下:
.将bi_sector字段设置为数据的起始扇区号(如果块设备被分成了几个分区,那么扇区号是相对于分区的起始位置)。
.将bi_size字段设置为涵盖整个数据的扇区数目。
.将bi_bdev设置为块设备描述符的地址(这个是block_device的对象,代表的是一个分区或者是主设备)。
.将bi_io_vec设为bio_vec结构数组的其实地址,数组中的每个元素描述了io操作中的一个段(内存缓存),此外,将bi_vcnt设置为bio中总的段数。
.将bi_rw字段设置为被请求的操作的标志。
.将bi_end_io字段设置成为当bio上的IO操作完成时所执行的完成程序的地址。

以上就是初始化bio结构的主要部分。一旦对一个IO请求所对应的bio结构初始化完成之后,接下来就是提交bio结构了。
2、提交bio结构
   递交一个bio的主要工作是从generic_make_request()函数开始的,我们以此为入口来分析一个bio的递交过程。在每个进程的task_struct中,都包含有两个变量----struct bio *bio_list, **bio_tail,generic_make_request()的主要工作就是用这两个变量来维护当前待添加的bio链表,实际的提交操作会由generic_make_request()调用__generic_make_request()函数完成。而在__generic_make_request()中,会调用到request_queue中定义的make_request_fn函数,也就是特定于设备的提交请求函数来完成后续的工作。在这里便会有一些问题,大部分设备的make_request_fn都可以直接定义为内核实现的__make_request函数,而一些设备需要使用自己的make_request_fn,而自行实现的make_request_fn有可能会递归调用gerneric_make_request(),由于内核的堆栈十分有限,因此在generic_make_request()的实现中,玩了一些小把戏,使得递归的深度不会超过一层。我们注意到bio_tail是一个二级指针,这个值最初是NULL,当有bio添加进来,bio_tail将会指向bio->bi_next(如果bio全都递交上去了,则bio_tail将会指向bio_list),也就是说除了第一次调用外,其他每次递归调用generic_make_request()函数都会出现bio_tail不为NULL的情形,因此当bio_tail不为NULL时,则只将bio添加到由bio_list和bio_tail维护的链表中,然后直接返回,而不调用__generic_make_request(),这样便防止了多重递归的产生。
下面来看generic_make_request()函数的源码实现:

点击(此处)折叠或打开

  1. void generic_make_request(struct bio *bio)
  2. {
  3.     if (current->bio_tail) {//current->bio_tail不为空则表明有bio正在提交,也就是说是处于递归调用
  4.         /* make_request is active */
  5.         bio->bi_next = NULL;
  6.         /*这里current->tail有两种情况,当current的bio链表为空时,bio_tail指向的是bio_list
  7.         当current的bio链表不为空时,bio_tail指向的是最后一个bio的bi_next指针,因此
  8.         这句的实际作用就是将bio添加到了current的bio链表的尾部*/
  9.         *(current->bio_tail) = bio;
  10.         current->bio_tail = &bio->bi_next;
  11.         /*这里直接返回,遍历并且提交bio的工作永远都是交给最先调用的generic_make_request来处理的,避免了多重递归*/
  12.         return;
  13.     }
  14.     /* following loop may be a bit non-obvious, and so deserves some
  15.      * explanation.
  16.      * Before entering the loop, bio->bi_next is NULL (as all callers
  17.      * ensure that) so we have a list with a single bio.
  18.      * We pretend that we have just taken it off a longer list, so
  19.      * we assign bio_list to the next (which is NULL) and bio_tail
  20.      * to &bio_list, thus initialising the bio_list of new bios to be
  21.      * added. __generic_make_request may indeed add some more bios
  22.      * through a recursive call to generic_make_request. If it
  23.      * did, we find a non-NULL value in bio_list and re-enter the loop
  24.      * from the top. In this case we really did just take the bio
  25.      * of the top of the list (no pretending) and so fixup bio_list and
  26.      * bio_tail or bi_next, and call into __generic_make_request again.
  27.      *
  28.      * The loop was structured like this to make only one call to
  29.      * __generic_make_request (which is important as it is large and
  30.      * inlined) and to keep the structure simple.
  31.      */
  32.     BUG_ON(bio->bi_next);
  33.     do {
  34.         current->bio_list = bio->bi_next;//这里取current的待提交bio链表的下一个bio
  35.         if (bio->bi_next == NULL)//bi_next为空,也就是说待提交链表已经空了,只剩下最后一个bio了
  36.             current->bio_tail = ¤t->bio_list;//bio_tail指向bio_list
  37.         else
  38.             bio->bi_next = NULL;//否则将bio提取出来
  39.         __generic_make_request(bio);//提交bio
  40.         bio = current->bio_list;//取新的待提交bio
  41.     } while (bio);
  42.     current->bio_tail = NULL; /* deactivate */
  43. }
上面的do。。。。while循环中做的工作就是从bio开始沿着bio->bi_next这个链不断地一次取一个bio来进行提交,具体真正完成提交工作是由函数__generic_make_request()来完成的。__generic_make_request()首先由bio对应的block_device获取等待队列q(获取q的过程主要是bio->bi_bdev->bd_disk->queue来进行获取请求队列q的),然后要检查对应的设备是不是分区,如果是分区的话要将扇区地址进行重新计算,最后调用make_request_fn完成bio的递交。其中__generic_make_request()函数的代码如下:

点击(此处)折叠或打开

  1. static inline void __generic_make_request(struct bio *bio)
  2. {
  3.     struct request_queue *q;
  4.     sector_t old_sector;
  5.     int ret, nr_sectors = bio_sectors(bio);//提取bio的大小,以扇区为单位 ,细节为:bio->bi_size>>9(即相当于bio->bi_size/512)
  6.     dev_t old_dev;
  7.     int err = -EIO;
  8.   
  9.     might_sleep();
  10.   
  11.     //这里检查bio的传输起始扇区是否超过设备的最大扇区,并且两者之间的差不能小于nr_sector
  12.     if (bio_check_eod(bio, nr_sectors)) //其中设备的最大扇区数通过:bio->bi_bdev->bd_inode->i_size>>9来计算的
  13.         goto end_io;
  14.   
  15.     /*
  16.      * Resolve the mapping until finished. (drivers are
  17.      * still free to implement/resolve their own stacking
  18.      * by explicitly returning 0)
  19.      *
  20.      * NOTE: we don't repeat the blk_size check for each new device.
  21.      * Stacking drivers are expected to know what they are doing.
  22.      */
  23.     old_sector = -1;
  24.     old_dev = 0;
  25.     do {
  26.         char b[BDEVNAME_SIZE];
  27.   
  28.         q = bdev_get_queue(bio->bi_bdev);//获取对应设备的请求队列 ,通过:bio->bi_bdev->bd_disk->queue来获取gendisk中的请求队列q
  29.         if (unlikely(!q)) {
  30.             printk(KERN_ERR
  31.                    "generic_make_request: Trying to access "
  32.                 "nonexistent block-device %s (%Lu)\n",
  33.                 bdevname(bio->bi_bdev, b),
  34.                 (long long) bio->bi_sector);
  35.             goto end_io;
  36.         }
  37.           
  38.         /*下面做一些必要的检查*/
  39.         if (unlikely(!bio_rw_flagged(bio, BIO_RW_DISCARD) &&
  40.                  nr_sectors > queue_max_hw_sectors(q))) {
  41.             printk(KERN_ERR "bio too big device %s (%u > %u)\n",
  42.                    bdevname(bio->bi_bdev, b),
  43.                    bio_sectors(bio),
  44.                    queue_max_hw_sectors(q));
  45.             goto end_io;
  46.         }
  47.   
  48.         if (unlikely(test_bit(QUEUE_FLAG_DEAD, &q->queue_flags)))
  49.             goto end_io;
  50.   
  51.         if (should_fail_request(bio))
  52.             goto end_io;
  53.   
  54.         /*
  55.          * If this device has partitions, remap block n
  56.          * of partition p to block n+start(p) of the disk.
  57.          */
  58.          //如果bio指定的是一个分区,则传输点要重新进行计算 。判断是不是分区的方法是看:bio->bi_bdev->bd_contains字段是不是指向的设备本身的block_device对象。如果指向的自身的block_device表示不是分区,否则表示的是分区。
  59.         blk_partition_remap(bio); //该函数比较重要,在下面的会进一步介绍,主要功能是如果是分区需要把bio的扇区换成相对于主设备的绝对扇区号,同时把bio的设备改成相应的主设备
  60.   
  61.         if (bio_integrity_enabled(bio) && bio_integrity_prep(bio))
  62.             goto end_io;
  63.   
  64.         if (old_sector != -1)
  65.             trace_block_remap(q, bio, old_dev, old_sector);
  66.   
  67.         old_sector = bio->bi_sector;
  68.         old_dev = bio->bi_bdev->bd_dev;
  69.   
  70.         if (bio_check_eod(bio, nr_sectors))
  71.             goto end_io;
  72.   
  73.         if (bio_rw_flagged(bio, BIO_RW_DISCARD) &&
  74.             !blk_queue_discard(q)) {
  75.             err = -EOPNOTSUPP;
  76.             goto end_io;
  77.         }
  78.   
  79.         trace_block_bio_queue(q, bio);
  80.   
  81.         ret = q->make_request_fn(q, bio);//这里是关键,调用请求队列q中的make_request_fn函数处理请求
  82.     } while (ret);
  83.   
  84.     return;
  85.   
  86. end_io:
  87.     bio_endio(bio, err);
  88. }
辅助函数blk_partition_remap():

点击(此处)折叠或打开

  1. static inline void blk_partition_remap(struct bio *bio)
  2. {
  3.     struct block_device *bdev = bio->bi_bdev; //通过bio获取该bio所在的block_device对象
  4.   
  5.     /*首先要保证传输的大小不能小于1个扇区并且bdev确实是分区*/
  6.     if (bio_sectors(bio) && bdev != bdev->bd_contains) { //判断是不是分区
  7.         struct hd_struct *p = bdev->bd_part;//获取该分区的分区信息
  8.   
  9.         bio->bi_sector += p->start_sect;//在传输起点的原基础上加上分区的起始扇区号 ,换成相对于主设备的绝对扇区号
  10.         bio->bi_bdev = bdev->bd_contains;//将bio的bdev置为主设备 ,把bio的bi_dbev指向主设备
  11.   
  12.         trace_block_remap(bdev_get_queue(bio->bi_bdev), bio,
  13.                     bdev->bd_dev,
  14.                     bio->bi_sector - p->start_sect);
  15.     }
  16. }
可以看到这里将bio的参考对象设置为了主设备,而不是分区,因此对应的扇区起始号也要计算为扇区的绝对值。


大多数的make_request_fn函数都可以直接定义为__make_request(),我们通过这个函数来分析递交bio的关键操作:(该函数是IO调度层的入口函数,做的主要工作就是尝试着将通用块层传下来的bio合并到相应的request中,要么插在某个request的尾部、要么插在某个request的头部,如果不能和任何request进行合并的话,就申请一个新的request,然后通过bio来初始化该request,最后把request插入到request_queue中。)

点击(此处)折叠或打开

  1. static int __make_request(struct request_queue *q, struct bio *bio)
  2. {
  3.     struct request *req;
  4.     int el_ret;
  5.     unsigned int bytes = bio->bi_size;//要传输的大小,以字节为单位的
  6.     const unsigned short prio = bio_prio(bio);
  7.     const bool sync = bio_rw_flagged(bio, BIO_RW_SYNCIO);
  8.     const bool unplug = bio_rw_flagged(bio, BIO_RW_UNPLUG);
  9.     const unsigned int ff = bio->bi_rw & REQ_FAILFAST_MASK;
  10.     int rw_flags;
  11.   
  12.     /*如果BIO_RW_BARRIER被置位(表示必须得让请求队列中的所有bio传递完毕才处理自己)
  13.       但是不支持hardbarrier,不能进行bio的提交*/
  14.     if (bio_rw_flagged(bio, BIO_RW_BARRIER) &&
  15.         (q->next_ordered == QUEUE_ORDERED_NONE)) {
  16.         bio_endio(bio, -EOPNOTSUPP);
  17.         return 0;
  18.     }
  19.     /*
  20.      * low level driver can indicate that it wants pages above a
  21.      * certain limit bounced to low memory (ie for highmem, or even
  22.      * ISA dma in theory)
  23.      */
  24.     blk_queue_bounce(q, &bio);
  25.   
  26.     spin_lock_irq(q->queue_lock);
  27.   
  28.     //如果BIO_RW_BARRIER被置位或者请求队列为空,则情况比较简单,不用进行bio的合并,跳转到get_rq处处理
  29.     if (unlikely(bio_rw_flagged(bio, BIO_RW_BARRIER)) || elv_queue_empty(q))
  30.         goto get_rq;
  31.   
  32.     /**请求队列不为空**/
  33.       
  34.     /*elv_merge()试图寻找一个已存在的request,将bio并入其中*/
  35.     el_ret = elv_merge(q, &req, bio);
  36.       
  37.     switch (el_ret) {
  38.     case ELEVATOR_BACK_MERGE:
  39.         BUG_ON(!rq_mergeable(req));
  40.   
  41.         /*相关检查*/
  42.         if (!ll_back_merge_fn(q, req, bio))
  43.             break;
  44.   
  45.         trace_block_bio_backmerge(q, bio);
  46.   
  47.         if ((req->cmd_flags & REQ_FAILFAST_MASK) != ff)
  48.             blk_rq_set_mixed_merge(req);
  49.   
  50.         /*这里将bio插入到request尾部*/
  51.         req->biotail->bi_next = bio;
  52.         req->biotail = bio;
  53.         req->__data_len += bytes;
  54.         req->ioprio = ioprio_best(req->ioprio, prio);
  55.         if (!blk_rq_cpu_valid(req))
  56.             req->cpu = bio->bi_comp_cpu;
  57.         drive_stat_acct(req, 0);
  58.         if (!attempt_back_merge(q, req))
  59.             elv_merged_request(q, req, el_ret);
  60.         goto out;
  61.   
  62.     case ELEVATOR_FRONT_MERGE:
  63.         BUG_ON(!rq_mergeable(req));
  64.   
  65.         if (!ll_front_merge_fn(q, req, bio))
  66.             break;
  67.   
  68.         trace_block_bio_frontmerge(q, bio);
  69.   
  70.         if ((req->cmd_flags & REQ_FAILFAST_MASK) != ff) {
  71.             blk_rq_set_mixed_merge(req);
  72.             req->cmd_flags &= ~REQ_FAILFAST_MASK;
  73.             req->cmd_flags |= ff;
  74.         }
  75.   
  76.         /*这里将bio插入到request的头部*/
  77.         bio->bi_next = req->bio;
  78.         req->bio = bio;
  79.   
  80.         /*
  81.          * may not be valid. if the low level driver said
  82.          * it didn't need a bounce buffer then it better
  83.          * not touch req->buffer either...
  84.          */
  85.         req->buffer = bio_data(bio);
  86.         req->__sector = bio->bi_sector;
  87.         req->__data_len += bytes;
  88.         req->ioprio = ioprio_best(req->ioprio, prio);
  89.         if (!blk_rq_cpu_valid(req))
  90.             req->cpu = bio->bi_comp_cpu;
  91.         drive_stat_acct(req, 0);
  92.         if (!attempt_front_merge(q, req))
  93.             elv_merged_request(q, req, el_ret);
  94.         goto out;
  95.   
  96.     /* ELV_NO_MERGE: elevator says don't/can't merge. */
  97.     default:
  98.         ;
  99.     }
  100.   
  101. get_rq:/**下面的代码对应请求队列为空的情况,需要先分配一个request,再将bio插入***/
  102.     /*
  103.      * This sync check and mask will be re-done in init_request_from_bio(),
  104.      * but we need to set it earlier to expose the sync flag to the
  105.      * rq allocator and io schedulers.
  106.      */
  107.     rw_flags = bio_data_dir(bio);//确定读写标识
  108.     if (sync)
  109.         rw_flags |= REQ_RW_SYNC;
  110.   
  111.     /*
  112.      * Grab a free request. This is might sleep but can not fail.
  113.      * Returns with the queue unlocked.
  114.      */
  115.     req = get_request_wait(q, rw_flags, bio);//分配一个新的request
  116.   
  117.     /*
  118.      * After dropping the lock and possibly sleeping here, our request
  119.      * may now be mergeable after it had proven unmergeable (above).
  120.      * We don't worry about that case for efficiency. It won't happen
  121.      * often, and the elevators are able to handle it.
  122.      */
  123.      //根据bio初始化新分配的request,并将bio插入到request中
  124.     init_request_from_bio(req, bio);
  125.   
  126.     spin_lock_irq(q->queue_lock);
  127.     if (test_bit(QUEUE_FLAG_SAME_COMP, &q->queue_flags) ||
  128.         bio_flagged(bio, BIO_CPU_AFFINE))
  129.         req->cpu = blk_cpu_to_group(smp_processor_id());
  130.     if (queue_should_plug(q) && elv_queue_empty(q))
  131.         blk_plug_device(q);
  132.     add_request(q, req);//将request插入到请求队列
  133. out:
  134.     if (unplug || !queue_should_plug(q))
  135.         __generic_unplug_device(q);
  136.     spin_unlock_irq(q->queue_lock);
  137.     return 0;
  138. }

elv_merge()是执行合并的关键所在,执行完后会有三种情况:
1.bio添加到了一个request的bio链表尾部
2.bio添加到了一个request的bio链表首部
3.未能找到一个request可以添加,将重新分配一个request

点击(此处)折叠或打开

  1. int elv_merge(struct request_queue *q, struct request **req, struct bio *bio)
  2. {
  3.     struct elevator_queue *e = q->elevator;
  4.     struct request *__rq;
  5.     int ret;
  6.   
  7.     /*
  8.      * First try one-hit cache.
  9.      */
  10.     //last_merge指向了最近进行合并操作的request,最先试图将bio合并到该request中
  11.     if (q->last_merge) {
  12.         ret = elv_try_merge(q->last_merge, bio);
  13.         if (ret != ELEVATOR_NO_MERGE) {
  14.             *req = q->last_merge;
  15.             return ret;
  16.         }
  17.     }
  18.   
  19.     if (blk_queue_nomerges(q))//请求队列不允许合并请求,则返回NO_MERGE
  20.         return ELEVATOR_NO_MERGE;
  21.   
  22.     /*
  23.      * See if our hash lookup can find a potential backmerge.
  24.      */
  25.      //根据bio的起始扇区号,通过rq的哈希表寻找一个request,可以将bio合并到request的尾部
  26.     __rq = elv_rqhash_find(q, bio->bi_sector);
  27.     if (__rq && elv_rq_merge_ok(__rq, bio)) {
  28.         *req = __rq;
  29.         return ELEVATOR_BACK_MERGE;
  30.     }
  31.   
  32.     /*如果以上的方法不成功,则调用特定于io调度器的elevator_merge_fn函数寻找一个合适的request*/
  33.     if (e->ops->elevator_merge_fn)
  34.         return e->ops->elevator_merge_fn(q, req, bio);
  35.   
  36.     return ELEVATOR_NO_MERGE;
  37. }
在下面在详细的了解有关__make_request()函数的细节。

3、总结
    提交IO请求过程中函数调用的过程;首先generic_make_request()函数,该函数主要的工作就是不断的取出bio结构,然后每取出一个bio结构就调用__generic_make_request()函数来对bio进行相应的处理,比如根据bio结构获取相应的请求队列q,再如如果bio对应的block_device对象是一个分区的,则改变bio的扇区号为与主设备对应的绝对扇区号。然后__generic_make_request()函数调用q->make_request_fn(q, bio)函数来真正的对bio进行处理。而q->make_request_fn()函数主要是通过__make_request()函数来实现的。具体层次为:

   generic_make_request()------>____generic_make_request()------->q->make_request_fn()---------->__make_request()函数。




阅读(3551) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~