[IO系统]17 IO调度器-DEADLINE

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YoungerChina

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值