IO调度算法分析--deadline算法分析

这部分是分析特定的IO调度算法--deadline, 这些算法的实现函数都是在通用层中被调用的, 所以应该对照着上面通用层的操作去理解.

IO调度算法总的来说实现了两个功能, 1是IO的合并, 包括bio合并到request, 还有两个request的合并, 2是查找最合适的request, 交到分发队头去, 让驱动去优先处理,这样就产生了两个作用: 1为增加系统的吞吐量, 2, 同时减少了系统的响应时间

1. 调度算法的注册

static struct elv_fs_entry deadline_attrs[] = {
    DD_ATTR(read_expire),
    DD_ATTR(write_expire),
    DD_ATTR(writes_starved),
    DD_ATTR(front_merges),
    DD_ATTR(fifo_batch),
    __ATTR_NULL
};

//deadline实现的部分函数

static struct elevator_type iosched_deadline = {
    .ops = {
        //request合并bio的操作
        .elevator_merge_fn =   deadline_merge,

        //合并了一个bio后要做的附加调整
        .elevator_merged_fn =   deadline_merged_request,

        //合并两个request操作
        .elevator_merge_req_fn =  deadline_merged_requests,

        //取最合适的request的操作
        .elevator_dispatch_fn =   deadline_dispatch_requests,

        //插入一个新的request操作
        .elevator_add_req_fn =   deadline_add_request,
        //判断是否为空
        .elevator_queue_empty_fn =  deadline_queue_empty,
        //取前/后一个request的操作
        .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,
};

//注册
static int __init deadline_init(void){
    return elv_register(&iosched_deadline);
}

static void __exit deadline_exit(void){
    elv_unregister(&iosched_deadline);
}

2.
/*
* See Documentation/block/deadline-iosched.txt
*/
static const int read_expire = HZ / 2; /* max time before a read is submitted. */
static const int write_expire = 5 * HZ; /* ditto for writes, these limits are SOFT! */
static const int writes_starved = 2;    /* max times reads can starve a write */
static const int fifo_batch = 16;       /* # of sequential requests treated as one
                     by the above parameters. For throughput. */

struct deadline_data {
    /*
    * run time data
    */

    /*
    * requests (deadline_rq s) are present on both sort_list and fifo_list
    */
    struct rb_root sort_list[2];   
    struct list_head fifo_list[2];
   
    /*
    * next in sort order. read, write or both are NULL
    */
    struct request *next_rq[2];
    unsigned int batching;        /* number of sequential requests made */
    sector_t last_sector;        /* head position */
    unsigned int starved;        /* times reads have starved writes */

    /*
    * settings that change how the i/o scheduler behaves
    */
    int fifo_expire[2];
    int fifo_batch;
    int writes_starved;
    int front_merges;
};

deadline算法的基本特点是, 它维护了5个队列, 分别是

struct rb_root sort_list[2];   //顺序队列, 读/写
struct list_head fifo_list[2]; //先进先出队列, 读/写

还有一个就是通用层的分发队列了, 驱动层就是从这个队列读取数据的.

对于一个request, 它会被置于两个特殊的队列里面, 如下:
/*
* add rq to rbtree and fifo
*/
static void
deadline_add_request(struct request_queue *q, struct request *rq){
    struct deadline_data *dd = q->elevator->elevator_data;
    const int data_dir = rq_data_dir(rq);

    //放入顺序队列中, 这个顺序是sector在磁盘中的顺序
    deadline_add_rq_rb(dd, rq);

    /*
    * set expire time (only used for reads) and add to fifo list
    */
    //这里就是将请求放入FIFO队列中, 这个队列就是为了保证deadline的
    rq_set_fifo_time(rq, jiffies + dd->fifo_expire[data_dir]); //deadline时间设置
    list_add_tail(&rq->queuelist, &dd->fifo_list[data_dir]);
}

IO调度算法的一个最重要的功能就是, 在队列中找出最合适的request, 然后将它置于分发队列的队头让驱动去读取.所以这里, 调度算法的主要功能就是判断那个是最合适的. 每个算法有自己的判断准则, 这里先看看deadline算法的判断方法.

调度算法一个目的就是在提高系统的吞吐能力的同时不过于提高系统的响应时间. 所以当在一段时间内有多个request到来, 我们分别将它们按各自在磁盘的位置顺序插入顺序队列, 这个队列是一个红黑二叉树结构, 用于快速查找. 再根据各个请求到来的时间顺序, 将他们插入顺序FIFO队列中, 这是一个线性链表. 而且读写请求分别使用不同的队列.

deadline调度算法的做法就是, 为了提高磁盘的读取性能, 它优先从顺序队列中去读取数据, 这样就可以保证磁盘可以顺序的先前移动,而不是来回移动磁头的响应系统的请求, 但是如果只是使用这一个简单的算法, 假设有一个请求在磁盘的末端, 即使它是最先提出请求的, 但是由于在磁盘上的位置被置于队列的末尾, 且还会不断的有新的请求插进她的前面,就会造成这个请求得不到服务被饿死, 在用户看来, 这个系统的响应能力是很差的. 所以这种情况应当避免.

这就是FIFO队列的作用了. deadline首先判断这个队列中的队头的请求有没有超时, 如果有, 就不能一味的为了系统的吞吐能力而忽视系统的响应能力了, 就想服务这个请求, 否则, 去从顺序队列中取一个请求出来, 就是上面的做法了.

这就是deadline算法的基本特征--保证请求在deadline的期限内得到服务, 就是说她保证了系统的响应能力的情况下,提高系统的吞吐能力.

deadline算法还有一个特点就是, 读请求和写请求是分开来处理的, 而且读的请求的响应时间应该比写的要短:

/*
* See Documentation/block/deadline-iosched.txt
*/
static const int read_expire = HZ / 2; /* max time before a read is submitted. */
static const int write_expire = 5 * HZ; /* ditto for writes, these limits are SOFT! */
static const int writes_starved = 2;    /* max times reads can starve a write */
static const int fifo_batch = 16;       /* # of sequential requests treated as one
                     by the above parameters. For throughput. */

我们看到, 读的是0.5s, 写的是5s, 这些也可以根据需要在系统运行的过程中在sysfs中重新设置的.

还有一个参数writes_starved, 表示, 当同时有读写请求是, 系统会优先处理读请求, 因为写请求服务一般来说用户是看不到的, 而用户发出一个读请求时, 他通常就需要等待这个请求完成, 所以得尽快去完成.但也是不能一味的让写请求让位与读请求, 应该有个限度, 这个writes_starved参数就是说明了应该让读请求优先多少次, 缺省是2.

调度算法还有一部分工作要做的是合并操作. 通用层做一个合并操作是, 因为被合并的请求有可能在这个算法的特殊队列里, 所以它就要处理这个请求, 所以就有了合并操作的一写钩子函数.

来源:http://blog.chinaunix.net/uid-27156212-id-3304883.html

上一篇:1008 Elevator (20 分)


下一篇:OO第二单元总结