IO调度层

来源:互联网 发布:乐视视频 mac版本 编辑:程序博客网 时间:2024/06/04 18:39
 一、 IO调度的两个重要的数据结构
request_queue数据结构如下
struct request_queue{01 struct list_head queue_head;02 struct request  *last_merge;03 struct elevator_queue *elevator;04 struct request_list rq;0506 request_fn_proc  *request_fn;07 make_request_fn  *make_request_fn;08 prep_rq_fn  *prep_rq_fn;09 unplug_fn  *unplug_fn;10 prepare_discard_fn *prepare_discard_fn;11 merge_bvec_fn  *merge_bvec_fn;12 prepare_flush_fn *prepare_flush_fn;13 softirq_done_fn  *softirq_done_fn;14 rq_timed_out_fn  *rq_timed_out_fn;15 dma_drain_needed_fn *dma_drain_needed;16 lld_busy_fn  *lld_busy_fn;17 sector_t  end_sector;18 struct request  *boundary_rq;19 struct timer_list unplug_timer;20 int   unplug_thresh; /* After this many requests */21 unsigned long  unplug_delay; /* After this many jiffies */22 struct work_struct unplug_work;2324 struct backing_dev_info backing_dev_info;25 void   *queuedata;26 unsigned long  bounce_pfn;27 gfp_t   bounce_gfp;28 unsigned long  queue_flags;29 spinlock_t  __queue_lock;30 spinlock_t  *queue_lock;31 struct kobject kobj;32 unsigned long  nr_requests; /* Max # of requests */33 unsigned int  nr_congestion_on;34 unsigned int  nr_congestion_off;35 unsigned int  nr_batching;3637 unsigned int  max_sectors;38 unsigned int  max_hw_sectors;39 unsigned short  max_phys_segments;40 unsigned short  max_hw_segments;41 unsigned short  hardsect_size;42 unsigned int  max_segment_size;43 unsigned long  seg_boundary_mask;44 void   *dma_drain_buffer;45 unsigned int  dma_drain_size;46 unsigned int  dma_pad_mask;47 unsigned int  dma_alignment;48 struct blk_queue_tag *queue_tags;49 struct list_head tag_busy_list;50 unsigned int  nr_sorted;51 unsigned int  in_flight;52 unsigned int  rq_timeout;53 struct timer_list timeout;54 struct list_head timeout_list;55 unsigned int  sg_timeout;56 unsigned int  sg_reserved_size;57 int   node;58 unsigned int  ordered, next_ordered, ordseq;59 int   orderr, ordcolor;60 struct request  pre_flush_rq, bar_rq, post_flush_rq;61 struct request  *orig_bar_rq;62 struct mutex  sysfs_lock;63 struct blk_cmd_filter cmd_filter;64};

第1行待处理请求的链表
第2行指向队列中首先可能合并的请求描述符
第3行指向elevator对象的指针
第4行为分配请求描述符所使用的数据结构
第6行实现驱动程序的策略例程入口点的方法
第8行该方法把这个处理请求的命令发送给硬件设备
第9行去掉块设备的方法
第11行当增加一个新段时,该方法返回可插入到某个已存在的bio结构中的字节数。
第20行如果请求队列中待处理请求数大于该值,将立即去掉请求设备
第21行去掉设备之前的时间延迟
第22行去掉设备时使用的操作队列
第25行指向块设备驱动程序的私有数据的指针
第26行在大于该页框号时必须使用缓冲区回弹
第28行回弹缓冲区的内存分配标志
第29行描述请求队列状态的标志。
第32行请求队列中允许的最大请求数
第33行如果待处理请求数超出了该阈值,则认为该队列是拥挤的。
第34行如果待处理请求数在这个阈值的范围内,则认为该队列不拥护
第35行即使队列已满,仍可以由特殊进程提交的待处理请求的最大值
第37行单个请求所能处理的最大扇区数
第38行单个请求所能处理的最大扇区数(硬约束)
第39行单个请求所能处理的最大物理段数
第40行单个请求所能处理的最大硬段数。
第41行扇区中以字节为单位的大小
第42行物理段的最大长度
第43行段合并的内存边界屏蔽字
第48行空闲/忙标记的位图
第56行基本上没有使用

request的数据结构

struct request {01 struct list_head queuelist;02 struct call_single_data csd;03 int cpu;04 struct request_queue *q;05 unsigned int cmd_flags;06 enum rq_cmd_type_bits cmd_type;07 unsigned long atomic_flags;08 sector_t sector;  /* next sector to submit */09 sector_t hard_sector;  /* next sector to complete */10 unsigned long nr_sectors; /* no. of sectors left to submit */11 unsigned long hard_nr_sectors; /* no. of sectors left to complete */12 unsigned int current_nr_sectors;13 unsigned int hard_cur_sectors;14 struct bio *bio;15 struct bio *biotail;16 struct hlist_node hash; /* merge hash */17 union {18  struct rb_node rb_node; /* sort/lookup */19  void *completion_data;20 };21 void *elevator_private;22 void *elevator_private2;23 struct gendisk *rq_disk;24 unsigned long start_time;25 unsigned short nr_phys_segments;26 unsigned short ioprio;27 void *special;28 char *buffer;29 int tag;30 int errors;31 int ref_count;32 unsigned short cmd_len;33 unsigned char __cmd[BLK_MAX_CDB];34 unsigned char *cmd;3536 unsigned int data_len;37 unsigned int extra_len; /* length of alignment and padding */38 unsigned int sense_len;39 void *data;40 void *sense;41 unsigned long deadline;42 struct list_head timeout_list;43 unsigned int timeout;44 int retries;45 rq_end_io_fn *end_io;46 void *end_io_data;47 struct request *next_rq;48};

第1行请求队列链表的指针
第8行要传送的下一个扇区号
第10行整个请求中要传送的扇区数
第11行整个请求中要传送的扇区数
第12行当前bio的当前段中要传送的扇区数
第13行当前bio的当前段中要传送的扇区数
第14行请求中第一个没有完成传送操作的bio
第15行请求链表中末尾的bio
第21行指向I/O调度程序私有数据的指针
第23行请求所引用的磁盘描述符
第24行请求的起始时间
第25行请求的物理段数
第31行请求的引用计数器
第32行cmd字段中命令的长度
第36行通常,由data字段指向的缓冲区中数据的长度
第39行设备驱动程序为了跟踪所传送的数据而使用的指针
第40行指向输出sense命令的缓冲区的指针
第43行请求的超时
每个请求包含一个或多个bio结构。通用块层创建一个仅包含一个bio结构的请求。然后,I/O调度程序要么向初始的bio中增加一个新段,要么将另一个bio结构链接到请求中,从而”扩展”请求。
二、两者之间的关系
三、IO调度常用的算法
(1) noop算法
 它没有排序的队列:新的请求通常被插在调度队列的开头或末尾,下一个要处理的请求总是队列中的第一个请求。
(2) CFQ算法
这个算法的主要目标是在触发I/O请求的所有进程中确保磁盘I/O带宽的公平分配。算法使用了排序队列,它们存放了不同进程发出的请求。当算法处理一个请求时,内核调用一个散列函数将当前进程的线程组标识符转换为队列的索引值;然后,算法将一个新的请求插入该队列的末尾。
(3) 最后期限 算法
使用了四个队列。其中的两个排序队列分别包含读请求和写请求,其中的请求是根据起始扇区数排序的。另外两个最后期限队列包含相同的读和写请求。引入这些队列是为了避免请求饿死。请求的最后期限本质上就是一个超时定时器。
(4) 预期算法
是“最后期限”算法的一个演变,借用了“最后期限”算法的基本机制,不做介绍。
四、向I/O调试程序发出请求
在通用块层”提交请求”后,generic_make_request()函数使用请求队列描述符的make_request_fn方法向I/O调度发送一个请求。该方法是由__make_request()函数来实现,其函数如下:

static int __make_request(struct request_queue *q, struct bio *bio){01 struct request *req;02 int el_ret, nr_sectors;03 const unsigned short prio = bio_prio(bio);04 const int sync = bio_sync(bio);05 const int unplug = bio_unplug(bio);06 int rw_flags;07 nr_sectors = bio_sectors(bio);08 if (bio_barrier(bio) && bio_has_data(bio) &&09     (q->next_ordered == QUEUE_ORDERED_NONE)) {10  bio_endio(bio, -EOPNOTSUPP);11  return 0;12 }13 blk_queue_bounce(q, &bio);14 spin_lock_irq(q->queue_lock);15 if (unlikely(bio_barrier(bio)) || elv_queue_empty(q))16  goto get_rq;17 el_ret = elv_merge(q, &req, bio);18 switch (el_ret) {19 case ELEVATOR_BACK_MERGE:20  BUG_ON(!rq_mergeable(req));21  if (!ll_back_merge_fn(q, req, bio))22   break;23  trace_block_bio_backmerge(q, bio);24  req->biotail->bi_next = bio;25  req->biotail = bio;26  req->nr_sectors = req->hard_nr_sectors += nr_sectors;27  req->ioprio = ioprio_best(req->ioprio, prio);28  if (!blk_rq_cpu_valid(req))29   req->cpu = bio->bi_comp_cpu;30  drive_stat_acct(req, 0);31  if (!attempt_back_merge(q, req))32   elv_merged_request(q, req, el_ret);33  goto out;34 case ELEVATOR_FRONT_MERGE:35  BUG_ON(!rq_mergeable(req));36  if (!ll_front_merge_fn(q, req, bio))37   break;38  trace_block_bio_frontmerge(q, bio);39  bio->bi_next = req->bio;40  req->bio = bio;41  req->buffer = bio_data(bio);42  req->current_nr_sectors = bio_cur_sectors(bio);43  req->hard_cur_sectors = req->current_nr_sectors;44  req->sector = req->hard_sector = bio->bi_sector;45  req->nr_sectors = req->hard_nr_sectors += nr_sectors;46  req->ioprio = ioprio_best(req->ioprio, prio);47  if (!blk_rq_cpu_valid(req))48   req->cpu = bio->bi_comp_cpu;49  drive_stat_acct(req, 0);50  if (!attempt_front_merge(q, req))51   elv_merged_request(q, req, el_ret);52  goto out;53 default:54  ;55 }5657get_rq:58 rw_flags = bio_data_dir(bio);59 if (sync)60  rw_flags |= REQ_RW_SYNC;61 req = get_request_wait(q, rw_flags, bio);62 init_request_from_bio(req, bio);63 spin_lock_irq(q->queue_lock);64 if (test_bit(QUEUE_FLAG_SAME_COMP, &q->queue_flags) ||65     bio_flagged(bio, BIO_CPU_AFFINE))66  req->cpu = blk_cpu_to_group(smp_processor_id());67 if (queue_should_plug(q) && elv_queue_empty(q))68  blk_plug_device(q);69 add_request(q, req);70out:71 if (unplug || !queue_should_plug(q))72  __generic_unplug_device(q);73 spin_unlock_irq(q->queue_lock);74 return 0;75}

第13行如果需要,调用blk_queue_bounce函数建立一个回弹缓冲区。如果回弹缓冲区被建立,__make_request函数将对该缓冲区而不是原先的bio结构进行操作。
第15行调用I/O调度程序的elv_queue_empty函数检查请求队列中是否存在待处理请求—调度队列可能是空的,但是I/O调度程序的其它队列可能包含等处理请求。
第17行插入的请求队列包含待处理请求。调用I/O调度程序的elv_merge()检查新的bio结构是否可以并入已存在的请求中。该函数将返回三个可能值:
a、ELEVATOR_NO_MERGE:已经存在的请求中不能包含bio结构;这种下情况,跳转到第x行代码。
b、ELEVATOR_BACK_MERGE:bio结构可作为末尾的bio而插入到某个请求req中;这种情形下,函数调用q->back_merge_fn方法检查是否可以扩展该请求。如果不行,也将跳到x行代码。
c、ELEVATOR_FRONT_MERGE:bio结构可作为某个请求req的第一个bio被插入;这种情形下,函数调用q->front_merge_fn方法检查是否可以扩展该请求。如果不行,则跳转到第x行代码。
第20-30行用来初始化请求。
a、根据bio描述符的内容初始化各个字段,包括扇区数,当前bio以及当前段。
b、设备flags字段中的REQ_CMD标志
c、如果第一个bio段的页框存放在低端内存,则将buffer字段设置为缓冲区的线性地址
d、将rq_disk字段设置为bio->bi_bdev->bd_disk地址。
e、将bio插入请求链表。
f、将start_time字段设置为jiffies的值
第72行所有操作全部完成。但是,在终止之前,检查是否设置了bio->bi_rw中的BIO_RW_SYNC标志。如果是,则对”请求队列”调用generic_unplug_device()函数以卸载驱动程序。
第75行函数终止。
五、总结
I/O调用层主要是把bio,插入到请求队列中,如果里面有请求了,就加到请求后面,如果没有请求,就加创建一个新的请求。把请求插入到请求队列中,然后有IO调度器,选择哪个请求来运行。IO调度器常用的算法用noop,cfq,最后期限,预期算法。