真实的I/O调度层处理
来源:互联网 发布:java qq停止服务 编辑:程序博客网 时间:2024/05/21 17:34
1.5.6 真实的I/O调度层处理
现在我们块设备也有了,队列也有了,要提交请求也就可以开始提交了。那就让我们回到generic_make_request来研究一下如何提交请求如何处理请求吧。我们看到,函数最后调用q->make_request_fn(q, bio)。对 make_request_fn 函数的调用可以认为是 IO调度层的入口,该函数用于向请求队列中添加请求。该函数是在创建请求队列时指定的,代码如下(blk_init_queue 函数中):
q->request_fn = rfn;
blk_queue_make_request(q, __make_request);
前面看到函数 blk_queue_make_request 将函数 __make_request 的地址赋予了请求队列 q 的 make_request_fn 成员:
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
{
q->nr_requests = BLKDEV_MAX_RQ;
q->make_request_fn = mfn;
……
那么,__make_request函数才是IO调度层的真实入口,来自block/ll_rw_blk.c:
2846static int __make_request(request_queue_t *q, struct bio *bio)
2847{
2848 struct request *req;
2849 int el_ret, rw, nr_sectors, cur_nr_sectors, barrier, err, sync;
2850 unsigned short prio;
2851 sector_t sector;
2852
2853 sector = bio->bi_sector;
2854 nr_sectors = bio_sectors(bio);
2855 cur_nr_sectors = bio_cur_sectors(bio);
2856 prio = bio_prio(bio);
2857
2858 rw = bio_data_dir(bio);
2859 sync = bio_sync(bio);
2860
2861 /*
2862 * low level driver can indicate that it wants pages above a
2863 * certain limit bounced to low memory (ie for highmem, or even
2864 * ISA dma in theory)
2865 */
2866 blk_queue_bounce(q, &bio);
2867
2868 spin_lock_prefetch(q->queue_lock);
2869
2870 barrier = bio_barrier(bio);
2871 if (unlikely(barrier) && (q->next_ordered == QUEUE_ORDERED_NONE)) {
2872 err = -EOPNOTSUPP;
2873 goto end_io;
2874 }
2875
2876 spin_lock_irq(q->queue_lock);
2877
2878 if (unlikely(barrier) || elv_queue_empty(q))
2879 goto get_rq;
2880
2881 el_ret = elv_merge(q, &req, bio);
2882 switch (el_ret) {
2883 case ELEVATOR_BACK_MERGE:
2884 BUG_ON(!rq_mergeable(req));
2885
2886 if (!q->back_merge_fn(q, req, bio))
2887 break;
2888
2889 blk_add_trace_bio(q, bio, BLK_TA_BACKMERGE);
2890
2891 req->biotail->bi_next = bio;
2892 req->biotail = bio;
2893 req->nr_sectors = req->hard_nr_sectors += nr_sectors;
2894 req->ioprio = ioprio_best(req->ioprio, prio);
2895 drive_stat_acct(req, nr_sectors, 0);
2896 if (!attempt_back_merge(q, req))
2897 elv_merged_request(q, req);
2898 goto out;
2899
2900 case ELEVATOR_FRONT_MERGE:
2901 BUG_ON(!rq_mergeable(req));
2902
2903 if (!q->front_merge_fn(q, req, bio))
2904 break;
2905
2906 blk_add_trace_bio(q, bio, BLK_TA_FRONTMERGE);
2907
2908 bio->bi_next = req->bio;
2909 req->bio = bio;
2910
2911 /*
2912 * may not be valid. if the low level driver said
2913 * it didn't need a bounce buffer then it better
2914 * not touch req->buffer either...
2915 */
2916 req->buffer = bio_data(bio);
2917 req->current_nr_sectors = cur_nr_sectors;
2918 req->hard_cur_sectors = cur_nr_sectors;
2919 req->sector = req->hard_sector = sector;
2920 req->nr_sectors = req->hard_nr_sectors += nr_sectors;
2921 req->ioprio = ioprio_best(req->ioprio, prio);
2922 drive_stat_acct(req, nr_sectors, 0);
2923 if (!attempt_front_merge(q, req))
2924 elv_merged_request(q, req);
2925 goto out;
2926
2927 /* ELV_NO_MERGE: elevator says don't/can't merge. */
2928 default:
2929 ;
2930 }
2931
2932get_rq:
2933 /*
2934 * Grab a free request. This is might sleep but can not fail.
2935 * Returns with the queue unlocked.
2936 */
2937 req = get_request_wait(q, rw, bio);
2938
2939 /*
2940 * After dropping the lock and possibly sleeping here, our request
2941 * may now be mergeable after it had proven unmergeable (above).
2942 * We don't worry about that case for efficiency. It won't happen
2943 * often, and the elevators are able to handle it.
2944 */
2945 init_request_from_bio(req, bio);
2946
2947 spin_lock_irq(q->queue_lock);
2948 if (elv_queue_empty(q))
2949 blk_plug_device(q);
2950 add_request(q, req);
2951out:
2952 if (sync)
2953 __generic_unplug_device(q);
2954
2955 spin_unlock_irq(q->queue_lock);
2956 return 0;
2957
2958end_io:
2959 bio_endio(bio, nr_sectors << 9, err);
2960 return 0;
2961}
__make_request 函数比较复杂,它接收request_queue类型的描述符q和一个bio结构的描述符bio作为其参数,然后执行如下操作:
2853内部变量sector赋值为bio的bi_sector字段,即将传送的bio的第一个扇区。2854行通过bio_sectors(bio)宏得到需要传送多少个连续的扇区,并赋值给内部变量nr_sectors。bio_sectors宏来自include/linux/bio.h
#define bio_sectors(bio) ((bio)->bi_size >> 9)
bio->bi_size我们知道,在前面如果一个页中的4个块内容连续,则do_mpage_readpage通过调用bio_add_page将4个块的大小4096赋值给bio的bi_size字段,那么nr_sectors就是4096>>9,等于8,表示这个bio一共8个扇区待传输。
2866行,如果需要,调用blk_cqueue_bounce()函数建立一个回弹缓冲区。如果回弹缓冲区被建立,__make_request()函数将对该缓冲区而不是原先的bio结构进行操作。关于回弹缓冲区的相关知识,请查阅相关资料,这里我们不做过多的介绍。
2878行,调用I/O调度程序的elv_queue_empty()函数检查请求队列中是否存在待处理请求——注意,调度队列可能是空的,但是I/O调度程序的其他队列可能包含待处理请求。如果没有待处理请求,那么调用blk_plug_device()函数插入请求队列,然后跳转到2932行的get_rq标号处。
如果插入的请求队列包含待处理请求,则走到2881行调用I/O调度程序的elv_merge()函数检查新的bio结构是否可以并入已存在的请求中。
该函数将返回三个可能值:
1. ELEVATOR_NO_MERGE:已经存在的请求中不能包含bio结构;这种情况下,跳转到2932行的get_rq标号处。
2. ELEVATOR_BACK_MERGE:bio结构可作为末尾的bio而插入到某个请求req中;这种情形下,函数调用q->back_merge_fn方法检查是否可以扩展该请求。如果不行,则跳转到2932行的get_rq标号处。否则,将bio描述符插入req链表的末尾并更新req的相应字段值。然后,函数试图将该请求与其后面的请求合并(新的bio可能填充在两个请求之间)。
3. ELEVATOR_FRONT_MERGE:bio结构可作为某个请求req的第一个bio被插入;这种情形下,函数调用q->front_merge_fn方法检查是否可以扩展该请求。如果不行,则跳转到2932行的get_rq标号处。否则,将bio描述符插入req链表的首部并更新req的相应字段值。然后,试图将该请求与其前面的请求合并。
不管是ELEVATOR_BACK_MERGE还是ELEVATOR_FRONT_MERGE,说明bio已经被并入存在的请求中,跳转到2951行out标号处终止函数。
下面来看2932行的get_rq标号处,bio必须被插人到一个新的请求中。那么通过get_request_wait给我们这个分区的请求队列q分配一个新的请求描述符request。如果没有空闲的内存,get_request_wait函数将调用io_schedule挂起当前进程,直到设置了bio->bi_rw中的BIO_RW_AHEAD标志,该标志表明这个I/O操作是一次预读;在这种情形下,调用bio_endio()并终止:此时将不会执行数据传送。
然后调用2945行的init_request_from_bio(req, bio)初始化请求描述符中的字段。主要有:
a) 根据bio描述符的内容初始化各个字段,包括扇区数、当前bio以及当前段。
b) 设置flags字段中的REQ_CMD标志(说明这次request是一个标准的读或写操作)。
c) 如果第一个bio段的页框存放在低端内存,则将buffer字段设置为缓冲区的线性地址。
d) 将rc_disk字段设置为bio->bi_bdev->bd_disk的地址。
e) 将bio插入请求链表。
f) 将start_time字段设置为jiffies的值。
回到__make_request的2948-2949行,再次调用elv_queue_empty检查一下请求队列中是否存在待处理请求。如果没有待处理请求,那么调用blk_plug_device()函数插入请求队列。不管怎样,都会执行2950行的add_request函数:
static inline void add_request(request_queue_t * q, struct request * req)
{
drive_stat_acct(req, req->nr_sectors, 1);
if (q->activity_fn)
q->activity_fn(q->activity_data, rq_data_dir(req));
/*
* elevator indicated where it wants this request to be
* inserted at elevator_merge time
*/
__elv_add_request(q, req, ELEVATOR_INSERT_SORT, 0);
}
add_request函数本质上调用__elv_add_request函数通过电梯算法把这个新的request插入到对应requesr_queue合适的位置。在介绍__elv_add_request函数之前,我们先介绍几个宏,来自include/linux/elevator.h:
155 /*
156 * Insertion selection
157 */
158 #define ELEVATOR_INSERT_FRONT 1
159 #define ELEVATOR_INSERT_BACK 2
160 #define ELEVATOR_INSERT_SORT 3
161 #define ELEVATOR_INSERT_REQUEUE 4
很明显,在add_request函数中传递进来的是ELEVATOR_INSERT_SORT,表示从前面插入。那么带着这个where我们进入下一个函数,即__elv_add_request。来自block/elevator.c:
646 void __elv_add_request(request_queue_t *q, struct request *rq, int where,
647 int plug)
648 {
649 if (q->ordcolor)
650 rq->cmd_flags |= REQ_ORDERED_COLOR;
651
652 if (rq->cmd_flags & (REQ_SOFTBARRIER | REQ_HARDBARRIER)) {
653 /*
654 * toggle ordered color
655 */
656 if (blk_barrier_rq(rq))
657 q->ordcolor ^= 1;
658
659 /*
660 * barriers implicitly indicate back insertion
661 */
662 if (where == ELEVATOR_INSERT_SORT)
663 where = ELEVATOR_INSERT_BACK;
664
665 /*
666 * this request is scheduling boundary, update
667 * end_sector
668 */
669 if (blk_fs_request(rq)) {
670 q->end_sector = rq_end_sector(rq);
671 q->boundary_rq = rq;
672 }
673 } else if (!(rq->cmd_flags & REQ_ELVPRIV) && where == ELEVATOR_INSERT_SORT)
674 where = ELEVATOR_INSERT_BACK;
675
676 if (plug)
677 blk_plug_device(q);
678
679 elv_insert(q, rq, where);
680 }
传入的参数plug等于0,所以blk_plug_device()不会被执行。很明显,一路走来我们根本没有设置什么REQ_SOFTBARRIER、REQ_HARDBARRIER或REQ_ELVPRIV标识,所以前面都和我们无关,直接跳到最后一行这个elv_insert():
548 void elv_insert(request_queue_t *q, struct request *rq, int where)
549 {
550 struct list_head *pos;
551 unsigned ordseq;
552 int unplug_it = 1;
553
554 blk_add_trace_rq(q, rq, BLK_TA_INSERT);
555
556 rq->q = q;
557
558 switch (where) {
559 case ELEVATOR_INSERT_FRONT:
560 rq->cmd_flags |= REQ_SOFTBARRIER;
561
562 list_add(&rq->queuelist, &q->queue_head);
563 break;
564
565 case ELEVATOR_INSERT_BACK:
566 rq->cmd_flags |= REQ_SOFTBARRIER;
567 elv_drain_elevator(q);
568 list_add_tail(&rq->queuelist, &q->queue_head);
569 /*
570 * We kick the queue here for the following reasons.
571 * - The elevator might have returned NULL previously
572 * to delay requests and returned them now. As the
573 * queue wasn't empty before this request, ll_rw_blk
574 * won't run the queue on return, resulting in hang.
575 * - Usually, back inserted requests won't be merged
576 * with anything. There's no point in delaying queue
577 * processing.
578 */
579 blk_remove_plug(q);
580 q->request_fn(q);
581 break;
582
583 case ELEVATOR_INSERT_SORT:
584 BUG_ON(!blk_fs_request(rq));
585 rq->cmd_flags |= REQ_SORTED;
586 q->nr_sorted++;
587 if (rq_mergeable(rq)) {
588 elv_rqhash_add(q, rq);
589 if (!q->last_merge)
590 q->last_merge = rq;
591 }
592
593 /*
594 * Some ioscheds (cfq) run q->request_fn directly, so
595 * rq cannot be accessed after calling
596 * elevator_add_req_fn.
597 */
598 q->elevator->ops->elevator_add_req_fn(q, rq);
599 break;
600
601 case ELEVATOR_INSERT_REQUEUE:
602 /*
603 * If ordered flush isn't in progress, we do front
604 * insertion; otherwise, requests should be requeued
605 * in ordseq order.
606 */
607 rq->cmd_flags |= REQ_SOFTBARRIER;
608
609 /*
610 * Most requeues happen because of a busy condition,
611 * don't force unplug of the queue for that case.
612 */
613 unplug_it = 0;
614
615 if (q->ordseq == 0) {
616 list_add(&rq->queuelist, &q->queue_head);
617 break;
618 }
619
620 ordseq = blk_ordered_req_seq(rq);
621
622 list_for_each(pos, &q->queue_head) {
623 struct request *pos_rq = list_entry_rq(pos);
624 if (ordseq <= blk_ordered_req_seq(pos_rq))
625 break;
626 }
627
628 list_add_tail(&rq->queuelist, pos);
629 break;
630
631 default:
632 printk(KERN_ERR "%s: bad insertion point %d/n",
633 __FUNCTION__, where);
634 BUG();
635 }
636
637 if (unplug_it && blk_queue_plugged(q)) {
638 int nrq = q->rq.count[READ] + q->rq.count[WRITE]
639 - q->in_flight;
640
641 if (nrq >= q->unplug_thresh)
642 __generic_unplug_device(q);
643 }
644 }
由于我们是从前面插,所以我们执行562行这个list_add,struct request有一个成员struct list_head queuelist,而struct request_queue有一个成员struct list_head queue_head,所以我们就把前者插入到后者所代表的这个队伍中来。然后咱们就返回了。
以上所有操作全部完成后,在终止之前,2952行检查是否设置了bio->bi_rw中的BIO_RW_SYNC标志。如果是,则对“请求队列”调用generic_unplug_device()函数以卸载设备驱动程序,并直接调用q->request_fn(q),这个函数是什么,马上会看到。
如果在调用__make_request()函数之前请求队列不是空的,那么说明该请求队列要么已经被拔掉过,要么很快将被拔掉——因为每个拥有待处理请求的插入请求队列q都有一个正在运行的动态定时器q->unplug_timer。另一方面,如果请求队列是空的,则__make_request()函数插入请求队列。或迟(最坏的情况是当拔出的定时器到期了)或最早(从__make_request()中退出时,如果设置了bio的BIO_RW_SYNC标志),该请求队列都会被拔掉。任何情形下,块设备驱动程序的策略例程最后都将处理调度队列中的请求。
当generic_make_request执行完scsi磁盘设备对应请求队列的q->make_request_fn方法,也就是刚才分析的__make_request以后,块设备的调度层就结束了。至于包含该bio的request放入到请求队列中后,何时被处理就由 IO 调度器的调度算法决定了。一旦该请求能够被处理,便调用请求队列中request_fn 字段所指向的函数处理。这个成员的初始化也是在创建请求队列时设置的:
1590 struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)
1591 {
1592 struct request_queue *q;
1593
1594 q = __scsi_alloc_queue(sdev->host, scsi_request_fn);
1595 if (!q)
1596 return NULL;
1597
1598 blk_queue_prep_rq(q, scsi_prep_fn);
1599 blk_queue_issue_flush_fn(q, scsi_issue_flush_fn);
1600 blk_queue_softirq_done(q, scsi_softirq_done);
1601 return q;
1602 }
我们看到,给scsi设备创建request_queue的时候,是把scsi_request_fn作为他的request_fn 字段所指向的函数地址,所以这个scsi_request_fn就是scsi底层驱动的入口。
- 真实的I/O调度层处理
- 真实的I/O调度层处理
- 块I/O层(I/O调度)
- 通用块层和SCSI层--I/O的顺序控制 及 调度算法
- Linux内核学习笔记十一——I/O层和I/O调度机制
- 通用块层和SCSI层--I/O处理过程
- Linux I/O调度算法的选择
- mysql使用的 I/O 调度方法
- I/O调度程序
- I/O调度模式
- Linux I/O调度
- Linux I/O调度
- Linux I/O调度
- Linux I/O 调度
- Linux I/O 调度方法
- Linux I/O 调度方法
- Linux I/O调度策略
- 块I/O调度程序
- 关联block_device结构
- 为设备建立请求队列
- android刷机小白教程
- H.264 高度压缩数字视频编解码器标准
- 块设备I/O调度程序
- 真实的I/O调度层处理
- #pragma的作用
- 虚数的理解
- 半年工作小结
- Matroska 一种新的多媒体封装格式
- scsi总线驱动的初始化
- sicily 2012
- VFW Microsoft推出的关于数字视频软件开发包 AVI文件标准
- scsi设备驱动体系架构