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中