CFQ调度器是四种IO Scheduler中最复杂的一个,redhat有个文档可以做为入门的文档先了解下 red-hat-enterprise-linux-5-io-tuning-guide.pdf
The cfq scheduler maintains a maximum of 64 internal request queues; each process running on the
system is assigned to any of these queues. Each time a process submits a synchronous I/
O request, it is moved to the assigned internal queue. Asynchronous requests from all processes are
batched together according to their process's I/O priority; for example, all asynchronous requests from
processes with a scheduling priority of "idle" (3) are put into one queue.
During each cycle, requests are moved from each non-empty internal request queue into one dispatch
queue. in a round-robin fashion. Once in the dispatch queue, requests are ordered to minimize disk
seeks and serviced accordingly.
To illustrate: let's say that the 64 internal queues contain 10 I/O request seach, and quantum is set to
8. In the first cycle, the cfq scheduler will take one request from each of the first 8 internal queues.
Those 8 requests are moved to the dispatch queue. In the next cycle (given that there are 8 free slots
in the dispatch queue) the cfq scheduler will take one request from each of the next batches of 8
internal queues.
这里不得不提下io prority,我们可以用ionice来指定io priority,其中有三种class: idle(3), best effort(2), real time(1),具体用法man ionice
real time 和 best effort 内部都有 0-7 一共8个优先级,对于real time而言,由于优先级高,有可能会饿死其他进程,对于 best effort 而言,2.6.26之后的内核如不指定io priority,那就有io priority = cpu nice
关于io优先级,有如下comments: I/O priorities are supported for reads and for synchronous (O_DIRECT, O_SYNC) writes. I/O priorities are not supported for asynchronous writes because they are issued outside the context of the program dirtying the memory, and thus program-specific priorities do not apply
言归正传,开始分析cfq的代码
struct cfq_io_context
cfq_io_context可以理解为io_context的子类,代表一个task_struct在cfq里的view,可以看到里面有两个cfq_queue结构的数组,cfq_queue[0]表示进程异步io请求对应的cfq_queue队列,cfq_queue[1]表示进程同步io请求对应的cfq_queue队列
struct cfq_queue
cfq_queue是个进程相关的数据结构,会和一个cfq_io_context关联,sort_list 是这个queue里面 pending requests 构成的红黑树,而 fifo 则是这个sort_list里面的pending requests形成的fifo链表,这个设计有点类似deadline io scheduler。
对于某个进程而言,其io class(rt, be, idle), io priority, io type(sync, async), 进程所属的cgroup 随时都会变化,因此cfq_queue的成员也会跟着变化
struct cfq_rb_root* service_tree
struct rb_node rb_node
其中service_tree指向cfq_data对应的cfq_rb_tree,下面可以看到每个cfq_group对应7个cfq_rb_root为头结点的service_tree,代表了不同的io class, io type;而rb_node则是这个红黑树上的结点
struct rb_node* p_node
struct rb_root* p_root
cfq_data有个成员prio_trees,代表了8个红黑树,每个cfq_queue都根据其io priority对应一个prio_trees成员,ioprio, ioprio_class记录了这些信息
struct cfq_group* cfqg
cfqg记录了cfq_queue对应的cgroup
struct cfq_data
这是和块设备队列相关的一个数据结构。cfq_data的指针是作为一个blkio_cgroup的哈希表的key,而对应的value则是cfq_group;同时也是io_context的radix_tree的一个key,对应的value是cfq_io_context,这样cfq_data,blkio_group和cfq_io_context就一一对应起来了。
cfq_data还有全局性的成员,专门针对异步io的
struct cfq_queue* async_cfqq[2][IOPRIO_BE_NR]
struct cfq_queue* async_idle_cfqq
其中async_cfqq代表了RT/BE各8个优先级的cfq_queue队列,async_idle_cfqq代表了idle的cfq_queue队列。
关于异步同步的请求,有一种说法是,只有page cache write back(pdflush)线程的请求才是内核唯一的异步请求,其他不论用户态是同步还是异步,不论是libaio还是内核native aio,到了调度队列之后都是同步请求??
struct hlist_head cfqg_list
指向该block device上挂载的所有cgroup,对应的hlist_node可以在struct cfq_group的cfqd_node成员中获取
struct rb_root prio_trees[CFQ_PRIO_LISTS]
prio_trees代表了8个红黑树,从priority 0 - 7
struct cfq_rb_root grp_service_tree
struct cfq_group root_cgroup
grp_service_tree为cfq_data所对应的block device上所有的cfq_group构成的红黑树的树根,可以看到cfq_group的成员rb_node就是这个红黑树(cfq_rb_root)的结点
root_cgroup为所有cgroup之中的根cgroup
cfq_group
这是per cgroup对应的数据结构,成员rb_node指向了其在cgroup红黑树中的结点
vdisktime
cfq_group->vdisktime可以理解为一个虚拟磁盘时间
在cfq_group_served函数中,当某个cfq_group被服务完之后,需要更新cfq_group->vdisktime,之后放回到service tree中。cfqg->vdisktime += cfq_scale_slice(charge, cfqg)是更新vdisktime的函数,其中charge是使用掉的time slice时间,cfq_scale_slice实现如下:
static inline u64 cfq_scale_slice(unsigned long delta, struct cfq_group *cfqg)
{
u64 d = delta << CFQ_SERVICE_SHIFT;
d = d * BLKIO_WEIGHT_DEFAULT;
do_div(d, cfqg->weight);
return d;
}
简单的说,weight越大的cfq_group,在消耗了同样的time slice之后,cfq_scle_slice返回的值相对较小,由于service tree每次都选择红黑树中vdisktime最小的cfq_group来服务,这就保证weight值大的cfq_group有更大几率会被再次选择服务
service tree会保存一个红黑树中当前最小的vdisktime值,存在min_vdisktime中
关于busy_queues_avg的解释如下:
* Per group busy queues average. Useful for workload slice calc. We
* create the array for each prio class but at run time it is used
* only for RT and BE class and slot for IDLE class remains unused.
* This is primarily done to avoid confusion and a gcc warning.
struct cfq_rb_root service_trees[2][3]
struct cfq_rb_root service_tree_idle
这两个成员在cgroup的patch出现之后从cfq_data被移到了新的cfq_group结构体中(2.6.33内核),我们可以看下代码里的注释如下:
* rr lists of queues with requests. We maintain service trees for
* RT and BE classes. These trees are subdivided in subclasses
* of SYNC, SYNC_NOIDLE and ASYNC based on workload type. For IDLE
* class there is no subclassification and all the cfq queues go on
* a single tree service_tree_idle.
* Counts are embedded in the cfq_rb_root
这里有一个我一直不明白的地方,service_trees已经是cfq_queue的红黑树根,首先分为BE, RT两大类(IDLE的全部在service_tree_idle里),然后又分为SYNC, SYNC_IDLE, ASYNC这三类,这是很奇怪的地方,因为service_trees本身应该都是NO IDLE的分类了,为什么又来个SYNC_IDLE呢
struct hlist_node cfqd_node
cfqd_node是一个哈希项,其哈希表头hlist_head为cfq_data->cfqg_list,这个cfq_data->cfqg_list的哈希表代表了这个block device上所有的cfq_group
struct blkio_group blkcg
通过blkio_group可以找到对应的blkio_cgroup的结构,关于blkio_cgroup请参考之前关于cgroup的文章。注意这里有两个相似的数据结构:blkio_cgroup和blkio_group,那么这两个数据结构有什么区别呢?我的猜测是,blkio_cgroup对应一个cgroup,可以从blkio_cgroup.css这个cgroup_subsys_state得到对应的cgroup结构。而blkio_group结构则是blkio_cgroup.blkg_list这个哈希表的结点,通过blkio_group.blkcg_node相关联。
注意到blkio_group里有一个成员dev,据此猜测blkio_group是cgroup在不同块设备上应用的数据结构,e.g. 有四个进程ABCD,其中AB在sda上读写,CD在sdb上读写,这样就会有两个blkio_group结构,分别对应sda和sdb,这也和cfq_group对应起来,每个cfq_group对应一个 per cgroup per device的数据结构,因此有一个blkio_group的成员变量把两者关联起来
这些数据结构之间的关系是怎样的?我大概归纳了一下,不一定准确:
cfq_group正如代码中的解释那样,是一个 /* This is per cgroup per device grouping structure */ 的结构,比如某个cgroup有两个tasks,一个对/dev/sda写,一个对/dev/sdb写,那么就对应着两个cfq_group结构,但如果两个tasks都是写/dev/sda,那么这两个进程都在同一个cfq_group中