[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算法实现是比较简单的


原创粉丝点击