1. 程式人生 > >【原創】(四)Linux程序排程-組排程及頻寬控制

【原創】(四)Linux程序排程-組排程及頻寬控制

# 背景 - `Read the fucking source code!` --By 魯迅 - `A picture is worth a thousand words.` --By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex-A53,雙核 3. 使用工具:Source Insight 3.5, Visio # 1. 概述 組排程(`task_group`)是使用Linux `cgroup(control group)`的cpu子系統來實現的,可以將程序進行分組,按組來分配CPU資源等。 比如,看一個實際的例子: A和B兩個使用者使用同一臺機器,A使用者16個程序,B使用者2個程序,如果按照程序的個數來分配CPU資源,顯然A使用者會佔據大量的CPU時間,這對於B使用者是不公平的。組排程就可以解決這個問題,分別將A、B使用者程序劃分成組,並將兩組的權重設定成佔比50%即可。 頻寬(`bandwidth`)控制,是用於控制使用者組(`task_group`)的CPU頻寬,通過設定每個使用者組的限額值,可以調整CPU的排程分配。在給定週期內,當用戶組消耗CPU的時間超過了限額值,該使用者組內的任務將會受到限制。 由於組排程和頻寬控制緊密聯絡,因此本文將探討這兩個主題,本文的討論都基於CFS排程器,開始吧。 # 2. task_group - 組排程,在核心中是通過`struct task_group`來組織的,`task_group`本身支援`cfs組排程`和`rt組排程`,本文主要分析`cfs組排程`。 - CFS排程器管理的是`sched_entity`排程實體,`task_struct(代表程序)`和`task_group(代表程序組)`中分別包含`sched_entity`,進而來參與排程; 關於組排程的相關資料結構,組織如下: ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310213939298-386758453.png) - 核心維護了一個全域性連結串列`task_groups`,建立的`task_group`會新增到這個連結串列中; - 核心定義了`root_task_group`全域性結構,充當`task_group`的根節點,以它為根構建樹狀結構; - `struct task_group`的子節點,會加入到父節點的`siblings`連結串列中; - 每個`struct task_group`會分配執行佇列陣列和排程實體陣列(以CFS為例,RT排程類似),其中陣列的個數為系統CPU的個數,也就是為每個CPU都分配了執行佇列和排程實體; 對應到實際的執行中,如下: ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214009477-225815245.png) - `struct cfs_rq`包含了紅黑樹結構,`sched_entity`排程實體參與排程時,都會掛入到紅黑樹中,`task_struct`和`task_group`都屬於被排程物件; - `task_group`會為每個CPU再維護一個`cfs_rq`,這個`cfs_rq`用於組織掛在這個任務組上的任務以及子任務組,參考圖中的`Group A`; - 排程器在排程的時候,比如呼叫`pick_next_task_fair`時,會從遍歷佇列,選擇`sched_entity`,如果發現`sched_entity`對應的是`task_group`,則會繼續往下選擇; - 由於`sched_entity`結構中存在`parent`指標,指向它的父結構,因此,系統的執行也能從下而上的進行遍歷操作,通常使用函式`walk_tg_tree_from`進行遍歷; ## 2.2 task_group權重 - 程序或程序組都有權重的概念,排程器會根據權重來分配CPU的時間。 - 程序組的權重設定,可以通過`/sys`檔案系統進行設定,比如操作`/sys/fs/cgoup/cpu/A/shares`; 呼叫流程如下圖: ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214059270-591255805.png) - `sched_group_set_shares`來完成最終的設定; - `task_group`為每個CPU都分配了一個`sched_entity`,針對當前`sched_entity`設定更新完後,往上對`sched_entity->parent`設定更新,直到根節點; - `shares`的值計算與`load`相關,因此也需要呼叫`update_load_avg`進行更新計算; 看一下實際的效果圖吧: ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214119139-113782827.png) - 寫節點操作可以通過`echo XXX > /sys/fs/cgroup/cpu/A/B/cpu.shares`; - 橙色的線代表傳入引數指向的物件; - 紫色的線代表每次更新涉及到的物件,包括三個部分; - 處理完`sched_entity`後,繼續按同樣的流程處理`sched_entity->parent`; # 3. cfs_bandwidth 先看一下`/sys/fs/cgroup/cpu`下的內容吧: ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214139609-150165770.png) - 有兩個關鍵的欄位:`cfs_period_us`和`cfs_quota_us`,這兩個與cfs_bandwidth息息相關; - `period`表示週期,`quota`表示限額,也就是在`period`期間內,使用者組的CPU限額為`quota`值,當超過這個值的時候,使用者組將會被限制執行(`throttle`),等到下一個週期開始被解除限制(`unthrottle`); 來一張圖直觀理解一下: ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214212348-1048763810.png) - 在每個週期內限制在`quota`的配額下,超過了就`throttle`,下一個週期重新開始; ## 3.1 資料結構 核心中使用`struct cfs_bandwidth`來描述頻寬,該結構包含在`struct task_group`中。 此外,`struct cfs_rq`中也有與頻寬控制相關的欄位。 還是來看一下程式碼吧: ```c struct cfs_bandwidth { #ifdef CONFIG_CFS_BANDWIDTH raw_spinlock_t lock; ktime_t period; u64 quota, runtime; s64 hierarchical_quota; u64 runtime_expires; int idle, period_active; struct hrtimer period_timer, slack_timer; struct list_head throttled_cfs_rq; /* statistics */ int nr_periods, nr_throttled; u64 throttled_time; #endif }; ``` - period:週期值; - quota:限額值; - runtime:記錄限額剩餘時間,會使用quota值來週期性賦值; - hierarchical_quota:層級管理任務組的限額比率; - runtime_expires:每個週期的到期時間; - idle:空閒狀態,不需要執行時分配; - period_active:週期性計時已經啟動; - period_timer:高精度週期性定時器,用於重新填充執行時間消耗; - slack_timer:延遲定時器,在任務出列時,將剩餘的執行時間返回到全域性池裡; - throttled_cfs_rq:限流執行佇列列表; - nr_periods/nr_throttled/throttled_time:統計值; `struct cfs_rq`結構中相關欄位如下: ```c struct cfs_rq { ... #ifdef CONFIG_CFS_BANDWIDTH int runtime_enabled; u64 runtime_expires; s64 runtime_remaining; u64 throttled_clock, throttled_clock_task; u64 throttled_clock_task_time; int throttled, throttle_count; struct list_head throttled_list; #endif /* CONFIG_CFS_BANDWIDTH */ ... } ``` - runtime_enabled:週期計時器使能; - runtime_expires:週期計時器到期時間; - runtime_remaining:剩餘的執行時間; ## 3.2 流程分析 ### 3.2.1 初始化流程 先看一下初始化的操作,初始化函式`init_cfs_bandwidth`本身比較簡單,完成的工作就是將`struct cfs_bandwidth`結構體程序初始化。 ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214259138-947344133.png) - 註冊兩個高精度定時器:`period_timer`和`slack_timer`; - `period_timer`定時器,用於在時間到期時重新填充關聯的任務組的限額,並在適當的時候`unthrottle`cfs執行佇列; - `slack_timer`定時器,`slack_period`週期預設為5ms,在該定時器函式中也會呼叫`distribute_cfs_runtime`從全域性執行時間中分配runtime; - `start_cfs_bandwidth`和`start_cfs_slack_bandwidth`分別用於啟動定時器執行,其中可以看出在`dequeue_entity`的時候會去利用`slack_timer`,將執行佇列的剩餘時間返回給`tg->cfs_b`這個`runtime pool`; - `unthrottle_cfs_rq`函式,會將`throttled_list`中的對應`cfs_rq`刪除,並且從下往上遍歷任務組,針對每個任務組呼叫`tg_unthrottle_up`處理,最後也會根據`cfs_rq`對應的`sched_entity`從下往上遍歷處理,如果`sched_entity`不在執行佇列上,那就重新`enqueue_entity`以便參與排程執行,這個也就完成了解除限制的操作; `do_sched_cfs_period_timer`函式與`do_sched_cfs_slack_timer()`函式都呼叫了`distrbute_cfs_runtime()`,該函式用於分發`tg->cfs_b`的全域性執行時間`runtime`,用於在該`task_group`中平衡各個CPU上的`cfs_rq`的執行時間`runtime`,來一張示意圖: ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214327034-866469658.png) - 系統中兩個CPU,因此`task_group`針對每個cpu都維護了一個`cfs_rq`,這些`cfs_rq`來共享該`task_group`的限額執行時間; - CPU0上的執行時間,淺黃色模組表示超額了,那麼在下一個週期的定時器點上會進行彌補處理; ### 3.2.2 使用者設定流程 使用者可以通過操作`/sys`中的節點來進行設定: ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214408643-1763244978.png) - 操作`/sys/fs/cgroup/cpu/`下的`cfs_quota_us/cfs_period_us`節點,最終會呼叫到`tg_set_cfs_bandwidth`函式; - `tg_set_cfs_bandwidth`會從`root_task_group`根節點開始,遍歷組排程樹,並逐個設定限額比率 ; - 更新`cfs_bandwidth`的`runtime`資訊; - 如果使能了`cfs_bandwidth`功能,則啟動頻寬定時器; - 遍歷`task_group`中的每個`cfs_rq`佇列,設定`runtime_remaining`值,如果`cfs_rq`佇列限流了,則需要進行解除限流操作; ### 3.2.3 `throttle`限流操作 `cfs_rq`執行佇列被限制,是在`throttle_cfs_rq`函式中實現的,其中呼叫關係如下圖: ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214423221-158953219.png) - 排程實體`sched_entity`入列時,進行檢測是否執行時間已經達到限額,達到則進行限制處理; - `pick_next_task_fair/put_prev_task_fair`在選擇任務排程時,也需要進行檢測判斷; ### 3.2.4 總結 總體來說,頻寬控制的原理就是通過`task_group`中的`cfs_bandwidth`來管理一個全域性的時間池,分配給屬於這個任務組的執行佇列,當超過限額的時候則限制佇列的排程。同時,`cfs_bandwidth`維護兩個定時器,一個用於週期性的填充限額並進行時間分發處理,一個用於將未用完的時間再返回到時間池中,大抵如此。 組排程和頻寬控制就先分析到此,下篇文章將分析`CFS排程器`了,敬請期待。 ![](https://img2020.cnblogs.com/blog/1771657/202003/1771657-20200310214440102-4577156