[IO系统]17 IO调度器-DEADLINE
来源:互联网 发布:波士顿矩阵图的优缺点 编辑:程序博客网 时间:2024/06/08 11:03
Deadline算法的核心在于保证每个IO请求在一定的时间内一定要被服务到,以此来避免某个请求饥饿。
1.1 原理
Deadline 这种调度器对读写 request 进行了分类管理,并且在调度处理的过程中读请求具有较高优先级。这主要是因为读请求往往是同步操作,对延迟时间比较敏感,而写操作往往是异步操作,可以尽可能的将相邻访问地址的请求进行合并,但是,合并的效率越高,延迟时间会越长。因此,为了区别对待读写请求类型, deadline 采用两条链表对读写请求进行分类管理。但是,引入分类管理之后,在读优先的情况下,写请求如果长时间得到不到调度,会出现饿死的情况,因此, deadline 算法考虑了写饿死的情况,从而保证在读优先调度的情况下,写请求不会被饿死。
Deadline 这种调度算法的基本思想可以采用下图进行描述:
读写请求被分成了两个队列,并且采用两种方式将这些 request 管理起来。一种是采用红黑树( RB tree )的方式将所有 request 组织起来,通过 request 的访问地址作为索引;另一种方式是采用队列的方式将 request 管理起来,所有的 request 采用先来后到的方式进行排序,即 FIFO 队列。每个 request 会被分配一个 time stamp ,这样就可以知道这个 request 是否已经长时间没有得到调度,需要优先处理。在请求调度的过程中,读队列是优先得到处理的,除非写队列长时间没有得到调度,存在饿死的状况。
在请求处理的过程中, deadline 算法会优先处理那些访问地址临近的请求,这样可以最大程度的减少磁盘抖动的可能性。只有在有些 request 即将被饿死的时候,或者没有办法进行磁盘顺序化操作的时候, deadline 才会放弃地址优先策略,转而处理那些即将被饿死的 request 。
总体来讲, deadline 算法对 request 进行了优先权控制调度,主要表现在如下几个方面:
1) 读写请求分离,读请求具有高优先调度权,除非写请求即将被饿死的时候,才会去调度处理写请求。这种处理可以保证读请求的延迟时间最小化。
2) 对请求的顺序批量处理。对那些地址临近的顺序化请求, deadline 给予了高优先级处理权。例如一个写请求得到调度后,其临近的 request 会在紧接着的调度过程中被处理掉。这种顺序批量处理的方法可以最大程度的减少磁盘抖动。
3) 保证每个请求的延迟时间。每个请求都赋予了一个最大延迟时间,如果达到延迟时间的上限,那么这个请求就会被提前处理掉,此时,会破坏磁盘访问的顺序化特征,回影响性能,但是,保证了每个请求的最大延迟时间。
1.2 源码分析
调度器使用的相关数据结构,Deadline调度器需要处理的核心数据结构是deadline_data,该结构描述如下:
struct deadline_data { /* * run time data */ /* * requests(deadline_rq s) are present on both sort_list and fifo_list */ /* 采用红黑树管理所有的request,请求地址作为索引值 */ struct rb_rootsort_list[2]; /* 采用FIFO队列管理所有的request,所有请求按照时间先后次序排列 */ struct list_headfifo_list[2]; /* * next in sortorder. read, write or both are NULL */ /* 批量处理请求过程中,需要处理的下一个request */ struct request*next_rq[2]; /* 计数器:统计当前已经批量处理完成的request */ unsigned int batching; /* number ofsequential requests made */ sector_tlast_sector; /* head position */ /* 计数器:统计写队列是否即将饿死 */ unsigned int starved; /* timesreads have starved writes */ /* * settings thatchange how the i/o scheduler behaves */ /* 配置信息:读写请求的超时时间值 */ int fifo_expire[2]; /* 配置信息:批量处理的request数量 */ int fifo_batch; /* 配置信息:写饥饿值 */ int writes_starved; int front_merges;};
sort_list:读写请求的红黑树,以请求的起始扇区来排序
fifo_list:读写请求的链表,以请求的响应期限来排序
next_rq:下一个读(写)请求,当确定一个批量传输时,通过该指针直接获取下一个请求
batching:批量传输的当前值
last_sector:处理的rq的末尾扇区号
starved: 标识着当前是第starved批读请求传输
fifo_expire:读写请求的期限值
fifo_batch:批量传输的请求数
writes_starved:写请求的饿死线,传输了writes_starved批读请求后,必须传输写请求
front_merges:是否使能frontmerge的检查
DeadlineScheduler的定义:
static struct elevator_type iosched_deadline = { .ops = { .elevator_merge_fn= deadline_merge, .elevator_merged_fn= deadline_merged_request, .elevator_merge_req_fn= deadline_merged_requests, .elevator_dispatch_fn= deadline_dispatch_requests, .elevator_add_req_fn= deadline_add_request, .elevator_queue_empty_fn= deadline_queue_empty, .elevator_former_req_fn= elv_rb_former_request, .elevator_latter_req_fn= elv_rb_latter_request, .elevator_init_fn= deadline_init_queue, .elevator_exit_fn= deadline_exit_queue, }, .elevator_attrs= deadline_attrs, .elevator_name= "deadline", .elevator_owner= THIS_MODULE,};
初始化函数deadline_init_queue()用于初始化struct deadline_data中的数据,没有太多好说的,先来看检查一个bio是否能合并到request中的函数deadline_merge()
static intdeadline_merge(struct request_queue *q, structrequest **req, struct bio *bio){ structdeadline_data *dd = q->elevator->elevator_data; structrequest *__rq; int ret; /* * check for front merge */ if(dd->front_merges) {//在deadline scheduler使能了front_merges的情况下才会进行front merge的检查 sector_tsector = bio->bi_sector + bio_sectors(bio);//取bio的最后一个扇区 //从红黑树中查找起始扇区号与sector相同的request __rq =elv_rb_find(&dd->sort_list[bio_data_dir(bio)], sector); if(__rq) {//查找成功 BUG_ON(sector!= blk_rq_pos(__rq)); if(elv_rq_merge_ok(__rq, bio)) {//各项属性的检查,确定bio可以插入 ret= ELEVATOR_FRONT_MERGE;//设置状态 gotoout; } } } returnELEVATOR_NO_MERGE;out: *req =__rq; returnret;//将检查的结果返回给通用层}
函数deadline_merged_request进行bio插入的善后工作。。主要是考虑前插入改变了原红黑树节点的值,所以要将节点删除再重新进行插入
static void deadline_merged_request(structrequest_queue *q, struct request *req, int type){ structdeadline_data *dd = q->elevator->elevator_data; /* * if the merge was a front merge, we need toreposition request */ if (type== ELEVATOR_FRONT_MERGE) {//如果是是将bio插入request的bio链表的前面则要进行request的重定位 elv_rb_del(deadline_rb_root(dd,req), req);//将request从红黑树中删除 deadline_add_rq_rb(dd,req);//重新添加至红黑树 }}
在通用层进行request的合并后,deadline_merged_requests()函数负责善后,注意合并时都是保留前request,舍弃后request
static voiddeadline_merged_requests(struct request_queue *q,struct request *req, struct request *next){ /* * if next expires before rq, assign its expiretime to rq * and move into next position (next will bedeleted) in fifo */ /*首先要保证两个请求的所属的队列不为空,然后根据req和next的响应期限时间长短,来选择保留哪个,如果 后者比前者的期限时间短,也就是先响应,那就要将next的期限时间赋给req,并且将req放置到next在fifo的 位置,因为next将要被删除*/ if(!list_empty(&req->queuelist) && !list_empty(&next->queuelist)){ //如果next的期限时间小于req if(time_before(rq_fifo_time(next), rq_fifo_time(req))) { list_move(&req->queuelist,&next->queuelist);//调整req在fifo的位置 rq_set_fifo_time(req,rq_fifo_time(next));//重置req的期限时间 } } /* * kill knowledge of next, this one is a goner */ //将next从链表和红黑树中删除 deadline_remove_request(q,next);}
deadline_add_request()用于将一个新请求添加至调度器,主要是插入各个数据结构,并设置期限值
static voiddeadline_add_request(struct request_queue *q,struct request *rq){ structdeadline_data *dd = q->elevator->elevator_data; const intdata_dir = rq_data_dir(rq);//获取request的读写方向 deadline_add_rq_rb(dd,rq);//将rq插入红黑树 /* * set expire time and add to fifo list */ //设置期限时间 rq_set_fifo_time(rq,jiffies + dd->fifo_expire[data_dir]); //将rq插入fifo_list list_add_tail(&rq->queuelist,&dd->fifo_list[data_dir]);}
最后要分析的一个函数,也是最重要的一个--deadline_dispatch_requests 调度器如何选择request,分派给request_queue:
static intdeadline_dispatch_requests(struct request_queue *q, intforce){ struct deadline_data*dd = q->elevator->elevator_data; const intreads = !list_empty(&dd->fifo_list[READ]); const intwrites = !list_empty(&dd->fifo_list[WRITE]); struct request*rq; int data_dir; /* * batches are currently reads XOR writes 请求批量处理入口 */ if (dd->next_rq[WRITE]) rq = dd->next_rq[WRITE]; else rq =dd->next_rq[READ]; /* 如果批量请求处理存在,并且还没有达到批量请求处理的上限值,那么继续请求的批量处理 */ if (rq && dd->batching <dd->fifo_batch) /* we have a next request are still entitled to batch */ gotodispatch_request; /* * at this point weare not running a batch. select the appropriate * data direction(read / write) */ /* 优先处理读请求队列 */ if (reads) { BUG_ON(RB_EMPTY_ROOT(&dd->sort_list[READ])); /* 如果写请求队列存在饿死的现象,那么优先处理写请求队列 */ if (writes && (dd->starved++ >=dd->writes_starved)) goto dispatch_writes; data_dir= READ; gotodispatch_find_request; } /* * there are eitherno reads or writes have been starved */ /* 没有读请求需要处理,或者写请求队列存在饿死现象 */ if (writes) {dispatch_writes: BUG_ON(RB_EMPTY_ROOT(&dd->sort_list[WRITE])); dd->starved= 0; data_dir= WRITE; gotodispatch_find_request; } return 0;dispatch_find_request: /* * we are notrunning a batch, find best request for selected data_dir */ if (deadline_check_fifo(dd, data_dir) ||!dd->next_rq[data_dir]) { /* 如果请求队列中存在即将饿死的request,或者不存在需要批量处理的请求,那么从FIFO队列头获取一个request */ /* * A deadline hasexpired, the last request was in the other * direction, or wehave run out of higher-sectored requests. * Start again fromthe request with the earliest expiry time. */ rq =rq_entry_fifo(dd->fifo_list[data_dir].next); } else { /* 继续批量处理,获取需要批量处理的下一个request */ /* * The last req wasthe same dir and we have a next request in * sort order. Noexpired requests so continue on from here. */ rq =dd->next_rq[data_dir]; } dd->batching= 0; dispatch_request: /* 将request从调度器中移出,发送至设备 */ /* * rq is theselected appropriate request. */ dd->batching++; deadline_move_request(dd,rq); return 1;}
总体而言,Noop和Deadline算法实现是比较简单的
- [IO系统]17 IO调度器-DEADLINE
- Linux deadline io 调度算法
- 内核IO电梯调度算法-Deadline追踪
- [IO系统]16 IO调度器-NOOP
- [IO系统]18 IO调度器
- 通用块层IO调度算法之deadline算法
- Linux IO Scheduler -- Deadline
- [IO系统]14 IO调度层
- linux IO调度器
- IO调度器
- IO调度器
- Linux IO调度器
- linux IO调度器
- btrfs cfq, noop, deadline三种IO调度策略下的IO性能表现(gp针对grup.conf配置)
- IO调度器原理介绍
- IO调度器原理介绍
- 修改Linux IO调度器
- IO队列和IO调度
- Linux-四-常见符号-(20170516)
- Java多线程并发器之AbstractQueuedSynchronizer分析
- 爱测未来开发-Zabbix的使用 API的调用
- FlashFXP5.4 注册码
- linux下建站软件安装
- [IO系统]17 IO调度器-DEADLINE
- Linux: android studio AVD emulator启动不了 | openGL | glx
- android textView设置粗体
- Visual Studio调试报错不自动断点,显示程序已结束运行
- 【数据结构作业】实现任意三种静态或动态查找
- 【数据结构作业】实现任意三种静态或动态查找
- rabbit安装步骤
- 【OpenCV图像处理】二十一、形态学滤波角点的提取
- POJ