Linux deadline调度算法源码分析

一 重要的数据结构

1 每一种具体的调度算法主要工作是实现这些接口
在这里插入图片描述

2 该结构抽象了每种具体调度算法,最重要的成员是ops,实现了上面定义的接口
在这里插入图片描述

3 对应于deadline调度算法的结构为

在这里插入图片描述

4 电梯调度队列,存储了具体调度类型以及该类型用到的存储结构.具体调度算法用到的存储结构,因调度算法而异, 因此是void *

在这里插入图片描述

5 deadline调度算法的存储结构,初始化时被存储在elevator_queue->elevator_data中
在这里插入图片描述

可以看到这有4个队列
struct rb_root sort_list[2];
struct list_head fifo_list[2];
sort_list是红黑树的根,用于对io请求按起始扇区编号进行排序,这里有两颗树,一颗读请求树,一颗写请求树。

fifo_list:存储request的FIFO队列,为了解决request饿死问题,每个request也按照读写方向被分别存放在两个FIFO链表中;

二 deadline初始化

与调度算法相关的结构是伴随着请求队列request_queue的初始化而被设置的。

      --->blk_init_queue()
        --->blk_init_queue_node()
          --->blk_init_allocated_queue()                        
            --->elevator_init()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmdHHv5r-1611570005094)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210125145536307.png)]

根据具体的调度算法名字找到对应的elevator_type结构后,再分配内存,然后调用具体调度算法的elevator_init_fn()方法。
对于我们这里关注的deadline调度算法来说,该方法被实例化为deadline_init_queue

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mBUfI5rV-1611570005096)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210125145839839.png)]

从初始化中可以看到跟deadline相关的几种可变参数:

**read_expire:**读请求的超时时间设置,单位为ms。当一个读请求入队deadline的时候,其过期时间将被设置为当前时间+read_expire,并放倒fifo_list中进行排序。

**write_expire:**写请求的超时时间设置,单位为ms。功能根读请求类似。

**fifo_batch:**在顺序(sort_list)请求进行处理的时候,deadline将以batch为单位进行处理。每一个batch处理的请求个数为这个参数所限制的个数。在一个batch处理的过程中,不会产生是否超时的检查,也就不会产生额外的磁盘寻道时间。这个参数可以用来平衡顺序处理和饥饿时间的矛盾,当饥饿时间需要尽可能的符合预期的时候,我们可以调小这个值,以便尽可能多的检查是否有饥饿产生并及时处理。增大这个值当然也会增大吞吐量,但是会导致处理饥饿请求的延时变长。

**writes_starved:**这个值是在上述deadline出队处理第一步时做检查用的。用来判断当读队列不为空时,写队列的饥饿程度是否足够高,以时deadline放弃读请求的处理而处理写请求。当检查存在有写请求的时候,deadline并不会立即对写请求进行处理,而是给相关数据结构中的starved进行累计,如果这是第一次检查到有写请求进行处理,那么这个计数就为1。如果此时writes_starved值为2,则我们认为此时饥饿程度还不足够高,所以继续处理读请求。只有当starved >= writes_starved的时候,deadline才回去处理写请求。可以认为这个值是用来平衡deadline对读写请求处理优先级状态的,这个值越大,则写请求越被滞后处理,越小,写请求就越可以获得趋近于读请求的优先级。

**front_merges:**当一个新请求进入队列的时候,如果其请求的扇区距离当前扇区很近,那么它就是可以被合并处理的。而这个合并可能有两种情况,一个是向当前位置后合并,另一种是向前合并。在某些场景下,向前合并是不必要的,那么我们就可以通过这个参数关闭向前合并。默认deadline支持向前合并,设置为0关闭。

1 在提交bio的时候可能会调用该接口ELEVATOR_MERGE_FN,而电梯调度算法中该接口被实例化为***deadline_merge***。一个bio首先尝试被合并到当前进程的plug_list的某个request,如果无法合并,那就尝试向request_queue中合并

在这里插入图片描述

if (dd->front_merges)
如果front_merges == 0 那么,就不会去做前端合并了。所以我们可以通过设置这个参数为0来关闭前端合并

sector_t sector = bio_end_sector(bio);
得到bio请求的结束扇区地址

__rq = elv_rb_find(&dd->sort_list[bio_data_dir(bio)], sector);
在同方向(读或写)的红黑树上去找请求的起始扇区地址与带合并请求的结束扇区地址相同的请求。两个请求的都是读或写,而且还在地址上连续,就可以合并成一个.

如果找到了就合并(elv_rq_merge_ok(__rq, bio)),如果合并成功了,就返回ELEVATOR_FRONT_MERGE,表示进行了前端合并。
为什么只有前端合并,elevator会自己做后端合并,在调用elevator_merge_fn之前就做了这个事情,并且后端合并优先于前端合并。

在这里插入图片描述

bio_attempt_back_merge:调用该函数先将bio merge到request中,因为是后向合并,因此实现上只是将该bio插入到request的bio链表尾部;

elv_bio_merged:调用具体调度算法的elevator_bio_merged_fn来通知当前merge了一个bio,对于deadline电梯调度算法,该接口被实例化为null,即未实现;

attempt_back_merge:因为该request刚刚后向合并进来了一个bio,该request就有可能和调度队列中的其他request再次进行合并,因此,我们必须再做这样的尝试,如果该request被再次合并了,函数返回1,否则返回0;

如果该request未被合并至其他request,由于该request被merge了bio,可能需要导致该request在具体的调度队列中的存储位置的调整(比如RB树中调整位置),因此调用elv_merged_request,而这里又调用了具体调度算法的elevator_merged_fn,在deadline调度算法中,该接口被实例化为deadline_merged_request。在调用完成这个后,如果后向合并,还得调整该request在hash表中的位置(因为是以request的last sector为hash key,后向合并中修改了这个值)。

如果做了前端合并,还会调用

在这里插入图片描述

如果作了前向merge,根据RB树的原理,该request在RB树种的位置需要被调整:先删除该节点,再添加。

2 一个bio如果不可以被合并至调度队列的某个request,这时候我们就得为其新分配一个request,并插入当前进程的plug_list或者调度队列。

在这里插入图片描述

deadline_add_rq_rb(dd, rq);
将请求加入到deadline调度器的sort_list红黑树中,注意这个函数会去判断rq的方向(读或写)来决定加入那棵树。

rq_set_fifo_time(rq, jiffies + dd->fifo_expire[data_dir]);
设置请求超时的时间,也就是说这个请求在这个时间到了必须得到响应。这就参与调度了。
这里出现了一个data_dir表示读方向或写方向。

list_add_tail(&rq->queuelist, &dd->fifo_list[data_dir]);
将请求加入deadline调度器的list_fifo链表中。

3 调度队列中的request最终是如何被派发到底层设备的

scsi_request_fn
    ----> __elv_next_request
              ----> elevator_dispatch_fn

而elevator_dispatch_fn则是待具体调度算法实现的接口,deadline调度算法将其实例化为deadline_dispatch_requests。

在这里插入图片描述

在deadline的数据结构体中是这么定义的 struct request *next_rq[2];
这两个指针一个用于指向读方向上的下一个请求,另一个指向写方向上的下一个请求。取得下一个请求,如果rq不为空指针,并且进一步的batching < fifo_batch, 那么就继续将下一个请求发送到系统的request_queue上去。

在这里插入图片描述

先看读方向的fifo队列是不是空的,如果不是就先满足读请求,但是如果写队列不为空,并且starved++ >= writes_starved, 就把方向改为写,这里writes_starved是写写请求最大可被饥饿的次数,而starved表示写请求已经被饥饿的次数,只有当starved >= writeds_starved,才会去处理写请求。
在这里插入图片描述

如果读方向上的fifo队列是空的,或者写请求被饥饿的次数已达上限,代码就会走到这里,进行写请求的处理。
先把starved置0,写请求即将被处理,代表不饥饿了。

在这里插入图片描述

如果 data_dir方向的fifo队列已经有饥饿请求 或者 电梯移动到头了(找不到该方向的next_rq了)
就取FIFO队列的头部request处理

将batching置0了,表示开始新的一批io请求。

首先batching++,表示这批请求已经有1个了,记个数。
然后调用deadline_move_request,这个函数会开始对next_rq赋值,所以第一次进这个函数时,next_rq[方向]是空的,从第二次开始就不一定了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值