在談到具體實現之前,還需要明確幾個任務調度相關的概念。從2.6內核以後,為了緩解多用戶下進程調度不公平的情況(舉個例子:A用戶有個實時進程,B用戶有個非交互進程,因為A的優先級比較高,那麼A佔用了大量的CPU時間,那麼對B就不夠公平)被調度的對象就從單一進程變成了任務組,這樣一來可以每個用戶對應一個任務組,從而分配得到一定的CPU時間,在這個組裡不同的任務再去獲取相應的CPU時間。這樣的任務組叫做task_group,
定義在kernel/sched/sched.h:363中:
struct task_group {#ifdef CONFIG_FAIR_GROUP_SCHED
struct sched_entity **se; struct cfs_rq **cfs_rq;#endifstruct cfs_bandwidth cfs_bandwidth; }其中包含了一個類型為sched_entity的成員。sched_entity也即調度實體,它可以是進程,也可以是進程組,甚至用戶。這些調度實體按照vruntime時間序,以紅黑樹的形式組織,並由cfs_rq管理,同樣是task_group的欄位,它的定義在:kernel/sched/sched.h:488:
struct cfs_rq {#ifdef CONFIG_FAIR_GROUP_SCHEDstruct rq *rq; };它包含了一個rq結構的成員,這是該cfs_rq附著到每個CPU run queue的指針。
在回到task_group的定義裡,還可以看到cfs_bandwidth的結構體成員,它的定義在:`kernel/sched/sched.h:337`:
struct cfs_bandwidth {#ifdef CONFIG_CFS_BANDWIDTHktime_t period; u64 quota; #endif};可以看到period和quota正是cfs_bandwidth的成員。那麼綜上所述,CPU帶寬控制的對象是task_group,配置好的period和quota都是作用在task_group上,然後再分配給task_group下的cfs_rq。可以以下圖作為示例:
進一步說,可以認為給task_group分配的quota是一個global quota pool,然後假定每個cfs_rq又擁有一個local quota pool,那麼cfs_rq要從global quota pool中申請一定數量的CPU時間,這個時間叫做slice,可以通過/proc/sys/kernel/sched_cfs_bandwidth_slice_us,目前默認值為5ms。見下圖:
當某個cfs_rq申請到slice以後就會開始執行,當前slice消耗完後就會繼續申請。當在一個period的時間global quota pool已經分配乾淨,此時該cfs_rq就會進入一個叫做throttled的狀態,在這個狀態中任務因為無法獲取CPU時間所以無法被執行。這個throttle的統計狀態可以通過cpu.stat cgroup進行查看。當該period結束後,global quota pool會被刷新並可以分配slice,如此進入下一個周期。
那麼回到前面所說的cfs_bandwidth定義裡:
struct cfs_bandwidth {#ifdef CONFIG_CFS_BANDWIDTHktime_t period; u64 quota; u64 runtime;
struct hrtimer period_timer; struct hrtimer slack_timer; struct list_head throttled_cfs_rq; int nr_periods; int nr_throttled; u64 throttled_time; #endif};可以了解到period的周期性由一個高精度定時器period_timer實現,被throttle的cfs_rq會掛到throttle_cfs_rq鍊表中。runtime的值最初跟quota一樣,但隨著任務執行會逐步減少。cpu.stats的值來自廈門的nr_periods、nr_throttled和throttled_time。