or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
Linux 核心設計: Scheduler(5): EEVDF Scheduler/1
回顧 CFS
CFS 在設計上最大的目標即名稱中提到的 "fair" – 公平。排程器會追蹤每個 process 的已運行時間,並根據各自的權重來確保它們公平的共享 CPU 資源。保證 CPU 資源的公平性固然對於排程很重要,然而除此之外,CFS 還是存在其他可改進的地方。例如,process 在 CPU 之間的移動除了考慮 CPU 的負載平衡和公平性,也應最大程度的發揮 cache 的價值。或者在電源管理層面上,排程器可能也需有能力在 throughput 和省電之間做出取捨,在 Arm big.LITTLE 等類似的混合架構上更是讓排程器的需求更加複雜。
一個重要的議題是: CFS 主要著眼於以權重方式將 CPU 時間公平分配,但對延遲要求的處理卻不夠完善。舉例來說,有些任務的性質上並不需要大量的 CPU 時間,但一旦有需求時,則期望可以盡快獲得之。反之,有些任務雖然對 CPU 需求很大,但必要的時候可以等待。而 CFS 中的 nice 值僅能賦予任務更多 CPU 時間,卻無法表達任務對獲得 CPU 資源之延遲的期待。既便在 CFS 中針對延遲敏感的任務是可以選擇 realtime class(
rt_sched_class
),但後者是屬於 privileged 的選項,意味著過度的使用之反而會造成系統其他部分的不良影響。何謂 EEVDF?
EEVDF 的設計源自於 Earliest Eligible Virtual Deadline First : A Flexible and Accurate Mechanism for Proportional Share Resource Allocation 這篇論文。讓我們簡單理解 EEVDF 的想法: 想像有 5 個 process 共享一個 CPU 的情境,則 CPU 時間被根據 nice value 按照對應的比例分配,這點與 CFS 的想法相同。假設 process 的 nice 值相同,那麼經過 1 秒鐘後,理想情況下每個 process 預期都各自使用了 200ms。不過實際上因為各種原因,每個 process 得到的時間可能多少偏移 200ms 一點。對此 EEVDF 計算實際得到之時間和 200ms 的差距,這個差異值稱為 "lag"。藉此,EEVDF 知道 lag 值為正的 process 與其他人相比得到的 CPU 時間更少,則之後它將比起 lag 為負的 process 被更早的排程。
只有一個 process 計算得到的 lag 大於或等於零時,才被視為符合資格,反之,任何 lag 為負者都沒有資格運行。對於不符資格的 process,因為要隨著時間前進,其有權獲得的時間才會變多,要等到其有權獲得的時間時趕上實際獲得的時間後,它才能再次獲得資格。這段時間差被稱為 "eligible time"。綜上所述,如何正確且精準的計算 lag 即為 EEVDF 演算法中的一大關鍵。
另一個 EEVDF 中的重要之處是 "virtual deadline" 的概念。這代表著 process 可以獲得應得的 CPU 的最早時間,可以通過將 process 分配到的 time slice 加上 eligible time 來得到。而 EEVDF 的精神即是挑選 virtual deadline 最早的 process。
在這個框架下,排程器在分配 time slice 考慮 latency-nice 值: latency-nice 較低的 process 表示對於延遲要求嚴格,會獲得較少的 time slice,而對延遲較不關心的 process 的 latency-nice 較高,並得到較長的 time slice。注意到兩個 process 得到的 CPU 時間是相同的,只是 latency-nice 低的是以多個短的 time slice 來得到這個總量, latency-nice 高者則是以少但是長的 time slice 取得。
前面我們說過 virtual deadline 是將 time slice 加上 eligible time 的總和,因此 time slice 短就意味著 virtual deadline 早,也就可以獲得更低的排程延遲。
EEVDF 論文閱讀
節錄 Earliest Eligible Virtual Deadline First : A Flexible and Accurate Mechanism for Proportional Share Resource Allocation。
Introduction
proportional share 以權重方式等比分配資源,而 real-time 則重視每個 client 在時限前獲得資源並有所進展。
一般的 real-time scheduler 在多媒體(multimedia)或者互動式(interactive) 一類符合 event-driven model 的應用上表現得較好,但其嚴格的時間限制導致在 batch 應用的支援不佳。反之 proportional share scheduler 在重視低延遲的多媒體和互動式應用上存在不足。而 EEVDF 嘗試提出一種可以適用任何類型的統一方法。
Assumptions
process 預期得到的時間和實際得到的時間會有差距,因為包含 process 間的切換、建立新的 prcess 等等 Scheduler 相關的演算法都會佔據額外時間。而這個時間差叫做 lag。
The EEVDF Algorithm
\[ ve^{(1)} = V(t^i_0) \\ \]
\[ vd^{(k)} = ve^{(k)} + \frac{r^{(k)}}{w_i} \\ \]
\[ ve^{(k + 1)} = vd^{(k)} \]
完整的名詞概念定義和對應計算式可以參照原始論文的敘述
這裡透過論文中的範例來解釋:
假設有兩個 client,各自的 weight 都是 2(\(w_1 = w_2 = 2\)),要求的時間則是 \(r_1 = 2, r_2 = 1\)。並令 time quantum 為 1。
依此規則,我們可以判斷每個 time quantum 要分配給何者。
Fairness in Dynamic Systems
在真實場景中,新的 client 加入或離開競爭,或者改變自身的 weight 等行為都會導致前述的 "lag"。在 EEVDF 中,公平性是藉由把 lag 考量進 virtual time 的計算中來滿足。
如上圖,假設三個 client 在 \(t_0\) 時存在且此時沒有 lag,而在時間 t 時 client 3 離開競爭的行列。則在時間 t 時 client i 的 lag 為
\[ lag_i(t) = S_i(t_0,t) - s_i(t_0, t) \\ = \int_{t_0}^{t_1}f_i(\tau)d\tau - s_i(t_0, t) \\ = w_i \frac{t - t_0}{w_1 + w_2 + w_3} - s_i(t_0, t) -- (1) \]
EEVDF 設計上在有 client 存在的狀況下,排程過程中不允許出現 resource idle。則在 \([t_0, t)\) 時間中,client 1 和 client 2 實際獲得的時間總和應為 \(t - t_0 - s_3(t_0, t)\)。那麼假設 \(t^+\) 是 client 3 離開競爭的瞬間,且 \(t^+ -> t\)。則對於 client 1 和 client 2:
\[ S_i(t_0, t^+) = (t - t_0 - s_3(t_0, t)) \frac{w_i}{w_1 + w_2} -- (2) \]
而根據前面的計算式
\[ lag_3(t) = w_3 \frac{t - t_0}{w_1 + w_2 + w_3} - s_3(t_0, t) \\ s_3(t_0, t) = w_3 \frac{t - t_0}{w_1 + w_2 + w_3} - lag_3(t) -- (3) \]
將 (3) 代入 (2)
\[ S_i(t_0, t^+) = (t - t_0 - ( w_3 \frac{t - t_0}{w_1 + w_2 + w_3} - lag_3(t))) \frac{w_i}{w_1 + w_2} \\ = (t - t_0 + \frac{-w_3t + w_3 t_0}{w_1 + w_2 + w_3}) * \frac{w_i}{w_1 + w_2} + w_i\frac{lag_3(t)}{w1 + w2} \\ = (\frac{w_1 + w_2t - w_1t_0 - w_2t_0}{w_1 + w_2 + w_3}) * \frac{w_i}{w_1 + w_2} + w_i\frac{lag_3(t)}{w1 + w2} \\ = (\frac{(w_1 + w_2)(t - t_0)}{w_1 + w_2 + w_3}) * \frac{w_i}{w_1 + w_2} + w_i\frac{lag_3(t)}{w1 + w2} \\ = (\frac{(t - t_0)}{w_1 + w_2 + w_3}) * w_i + w_i\frac{lag_3(t)}{w1 + w2} \\ = w_i(V(t) - V(t_0)) + w_i\frac{lag_3(t)}{w1 + w2} \]
在算式中,我們可以看出當 client 3 離開時,EEVDF 會使得 client 1 和 client 2 按比例分攤 client 3 造成的 lag。
總結而言,當 client j 在時間 t 離開競爭,則 virtual time 的更新方式為:
\[ V(t) = V(t) + \frac{lag_j(t)}{\sum_{i\in{A(t^+)}}w_i} -- (4) \]
在同樣概念下,當 client j 在時間 t 加入競爭,則 virtual time 的更新方式為:
\[ V(t) = V(t) - \frac{lag_j(t)}{\sum_{i\in{A(t^+)}}w_i} -- (5) \]
權重的變更可以視為一個 client 推出競爭後又重新加入。假設在時間 t client j 的權重從 \(w_j\) 變成 \(w_j^{'}\):
\[ V(t) = V(t) + \frac{lag_j(t)}{\sum_{i\in{A(t^+)}}w_i - w_j} + \frac{lag_j(t)}{\sum_{i\in{A(t^+)}}w_i + w_j^{'}} -- (6) \]
另一個要考慮的問題是當一個 lag 為非 0 的 client 離開競爭後又重回競爭行列,該如何考慮其此時的 lag? 換個問法,一個 client 離開競爭時 lag 為正值的情形而言(實際得到的時間小於預期得到的),是否該在其重回隊列時對其進行補償? 實際上這並沒有確切的答案,假設我們不做補償,那麼損失的時間會隨著時間周期的進行一直累積,最後產生大量的缺時。反之,假設我們決定在 client 重新加入時進行補償,這卻反而阻礙其他 client 的進行。舉個例子說明: 假設在時間 t 之前有兩個 active client 1 和 2,並且在時間 t client 2 退出競爭並有正的 lag。接下來,在隨後的時間 t0 client 2 重新加入競爭,而另一個 client 3 這時也在競爭中,而此時 client 1 不再活動。那麼補償的時間只能間接從 client 3 來提取了,這種做法對 client 3 反而就不公平了。
Algorithm Implementation
前面我們說明了 client 重新加入是否該進行補償是沒有簡單的做法的,而論文對此提出三種策略。
老實說看完這段還是不太確定 lag 非負的情況具體要怎麼做?
Appendix A: Example: Implementation of Strategy 1
為求簡單,假設每個 request 只能做 1 個 time quantum(
QuantumSize
)。clients 的 pending request 以一個樹狀的資料結構ReqTree
維護,這樹主要支援三個操作 insertion (insert_req
), deletion (delete_req
), 以及取得擁有最早 virtual deadline 的 eligible request (get_req
)。當一個 client 想加入競爭時可以透過 join:
VirtualTime
\[ ve^{(1)} = V(t^i_0) \\ vd^{(k)} = ve^{(k)} + \frac{r^{(k)}}{w_i} \\ ve^{(k + 1)} = vd^{(k)} \]
insert_req
將 client 加入到ReqTree
之中當一個 client 想退出競爭時可以透過 leave:
VirtualTime
delete_req
將 client 從ReqTree
之中移除get_req
根據 earliest virtual deadline 找出適合的下個 req,並將資源分配給他(allocate
)update_lag
即 \(lag(t) = S_i(t^i_0,t) - s_i(t^i_0, t)\)Appendix B. Request Data Structure
樹狀結構如圖。以 root 為例:
get_req
vtime
>= 當下 node 的 \(ve\),則往樹的右子樹搜尋,否則往左insert_req
標準的 binary tree insert,區別只在要更新路徑的 minimum \(vd\)。
delete_req
刪除時也需考慮 minimum \(vd\) 的更新,
backward_update
從要被刪除的節點開始並一路到 root,將需要更新的 minimum \(vd\) 進行調整。更新的方法是透過本身和左右子樹的 \(vd\) 判斷其中的最小值。刪除包含了上述三種情況,其中 case (i) 和 (ii) 比較簡單,只需將原節點移除並看使否讓唯一的 child 繼承其原本位置就可以了。case (iii) 較為複雜,首先找到 succesor(key 大於本節點的者之中擁有最小 key 的節點),將其後移除後取代原節點的位置。
Main patches
下面嘗試針對 Peter Zijlstra 在信件中提出的 patch series 逐一做探討。方便對應,連結則對應到 v6.6-rc4 版本的程式碼。
[PATCH 01/15] sched/fair: Add avg_vruntime
EEVDF 引入兩個新的參數:
avg_vruntime
和avg_load
,兩者的代表意義可以在以下註解一探究竟。EEVDF 中滿足 \(\sum_i lag_i = 0\),即所有任務的 lag 之合為 0。每個任務的 lag 值計算方式為 \(lag_i = S_i - s_i = w_i * (V - v_i)\)。其中 \(S_i\) 代表理想上應得的時間,\(s_i\) 則是實際應到的時間。\(V\) 則表示應得的 virtual time(在 virtual time 的世界中,所有任務應得的 \(V\) 是相同的)。\(v_i\) 是實際得到的 virtual time。
\(lag_i = S - s_i\) 不太正確,應為 \(lag_i = S_i - s_i\),可參見 這裡 的討論串
則可以展開求得以下式子:
\[ \sum_i lag_i \\ = \sum_i w_i * (V - v_i) \\ = \sum_i w_i * V - w_i * v_i = 0 \]
移項後,可求得 \(V\) 為:
\[ V = \frac{\sum_i w_i * v_i}{\sum_i w_i} = \frac{\sum_i w_i * v_i}{W} \]
這裡 W 表示所有任務的權重之和。從式子中也可以看出 \(V\) 就是所有任務已歷經的 vitual runtime 之 weight average。
然而如果我們追蹤實際的 \(v_i\),計算上這很容易發生 overflow 的問題,因此我們可以引入所有任務中的最小 vruntime
min_vruntime
\(v0\) 則改寫上述的式子\[ V = \frac{\sum_i w_i * v_i}{W} \\ \]
\[ = \frac{\sum_i w_i * (v_i - v0) + \sum_i w_i * v0}{W} \\ \]
\[ = \frac{\sum_i w_i * (v_i - v0)}{W} + v0 \]
原有的 CFS 中就有對
min_vruntime
的追蹤。而為了計算上的方便,另外還要引入cfs->avg_vruntime
和cfs->avg_load
,兩者分別代表的是:cfs->avg_vruntime
: \(\sum_i w_i * (v_i - v0)\)cfs->avg_load
: W注意
cfs->avg_vruntime
和avg_vruntime
名字相同,但實際意義並不一樣EEVDF 中新增
entity_key
這個函式。計算的是內容是前面提到的 \(v_i - v0\)。avg_vruntime_add
和avg_vruntime_sub
更精準地說是將se
貢獻給 \(V\) 的影響納入計算。在做與原始論文相關的 join/leave/reweight 時會需要這些操作。隨著時間,變數
min_vruntime
勢必要跟著更新,而在更新cfs_rq->min_vruntime
時(\(v0->v0'\)),與之相關的cfs->avg_vruntime
也需更新。__update_min_vruntime
達成上述目的。\[ \sum_i w_i * (v_i - v0) - \sum_i w_i * (v0' - v0) \\ = \sum_i w_i * (v_i - v0') \]
avg_vruntime
即用來計算前面提到的 \(V\):\[ V = \frac{\sum_i w_i * (v_i - v0)}{W} + v0 \\ \]
\[ = \frac{\text{cfs_rq->avg_vruntime}}{\text{cfs_rq->avg_load}} + \text{cfs_rq->min_vruntime} \ \]
留意到
curr
對 \(V\) 的貢獻不會直接反應在cfs_rq
的參數中,在它被挑出來獲得 CPU 時相關數據會被排除,但對 \(V\) 的計算理論上仍要考慮curr
,因此這裡就另外反映上去。這邊對應一開始提到要更新 vruntime 的情境。
print_cfs_rq
是/sys/kernel/debug/sched/debug
中允許 schduler 得到排程器信息的機制。由於 scheduler 的更新,自然需要顯示的信息也會有所調整。Q:
cfs->avg_load
和scale_load_down(cfs->load.weight)
有何差異?A:
Peter Zijlstra:
[PATCH 02/15] sched/fair: Remove START_DEBIT
過去在 CFS 中有一個
START_DEBIT
的 scheduler config。作用是在一個新的se
被加入時在min_vruntime
的基礎上加入一些延遲,這避免了在 CFS 上可以透過 fork 來不斷取得 CPU,導致其他任務 starvation 之問題。EEVDF 引入後,新的任務之
se->vruntime
的初始化不再透過min_vruntime
,而是可以直接從avg_vruntime()
初始即可。如此一來,在 EEVDF 中也就不再造成上述的問題。則相關的程式碼也不再被需要。
[PATCH 03/15] sched/fair: Add lag based placement
在此 patch 中額外引入一個參數
vlag
。對於被 fork (
__sched_fork
)出來的新任務,vlag
要初始為 0。由於變數是追蹤 vlag: \({vlag}_i = (V - v_i) = lag_i/w_i\),而實際的 lag 不應該隨 weight 改變而受影響,因此 vlag 需隨 weight 的改變也進行相應調整。
根據說明,完整 lag 的算法是 \(w_i * (V - v_i)\),而
update_entity_lag
將vlag
更新為當前 \(V - v_i\) 的結果。使用時機上是在藉dequeue_entity
讓 se 進入 sleep 狀態時呼叫之。主要目的其實對應到論文在 Algorithm Implementation 一章所說的補償策略。這邊可以知道 Linux 採用的方式接近於策略一,因此在
se
暫時離開競爭時要先把當下 lag 考慮進來。place_entity
會在任務被喚醒或是剛被創建時對目標的se
使用,主要目的是針對 \(v_i\) 作校正。從程式碼中可看出,如果
PLACE_LAG
這個 feature 被採用,排程器在補償部份就會使用 Algorithm Implementation 的策略 1,若關閉則是使用策略 2。信件中指出了策略 1 隱含的問題,該策略反常的鼓勵在等待 CPU 時 spin wait 而非 sleep(suspend)。因為如果在拿到過多 service 的情況下退出(sleep),rejoin 時會需要付出 negative lag 的代價可能高於 spin wait。
假設 \(V\) 為任務 \(i\) 要被加入之前的 virtual time \(V\),當時的 \(V\) 可求得為:
\[ V = \frac{\sum_j w_j*v_j}{W} \\ W = \sum_j w_j \]
則任務 \(i\) 被加入後的 virtual time \(V'\) 應可透過 \(V\) 表示為
\[ V' = (\sum_j w_j*v_j + w_i*v_i) / (W + w_i) \\ = (W*V + w_i*(V - vl_i)) / (W + w_i) \\ = (W*V + w_i*V - w_i*vl_i) / (W + w_i) \\ = (V*(W + w_i) - w_i*l) / (W + w_i) \\ = V - w_i*vl_i / (W + w_i) \]
如此一來,我們可以將之前 \(i\) 加入後的 lag \(vl'_i\) 推導出來。
\[ vl'_i = V' - v_i \\ = V - w_i*vl_i / (W + w_i) - (V - vl_i) \\ = vl_i - w_i*vl_i / (W + w_i) \]
藉由上述式子,則可以移項或得 \(vl_i\) 和 \(vl'_i\) 的關係,求得 \(vl_i\),即對應程式碼中的 local 變數
lag
。\[ (W + w_i) vl'_i = (W + w_i) vl_i - w_i*vl_i \\ (W + w_i) vl'_i = W * vl_i \\ vl_i = \frac{(W + w_i) vl'_i}{W} \]
藉由 \(v_i = V - vl_i\) 也可以得到校正後的 vruntime。
FAIR_SLEEPERS
是繼承過去在 CFS 上原始存在的另一個補償機制。但在 v6.6-rc4 的place_entity
上可以看到這機制目前並沒有被使用,暫時先略過。最後可以看到在此 patch 中引入兩個新的 scheduler feature:
FAILR_SLEEPERS
和PLACE_LAGS
,也就對應之前在place_entity
的描述。[PATCH 04/15] rbtree: Add rb_add_augmented_cached() helper
引入
rb_add_augmented_cached
,目的是對紅黑樹的操作做一個小優化。[PATCH 05/15] sched/fair: Implement an EEVDF like policy
回顧一下 CFS 主要的問題,是機制上只有 weight 這個參數可以控制延遲。而 EEVDF 中則存在兩個參數:
因此,得引入相關的 structure member。
__sched_fork
相應的初始化。debug 訊息也做對應調整。
在 [PATCH 03/15] 展示過
update_entity_lag
的實作,但這裡進行調整。主要是考量 lag 過大的問題,藉 clamp 限制最終 lag 數值範圍。entity_eligible
判斷給定的 se 是否是 eligible 的。具體來說,是判斷 \(lag_i = w_i*(V - v_i)\) 是否是非負整數(>=0),也就是 \(V >= V_i\) 是否成立。回顧前面說的 \(V = \frac{\sum_i w_i * (v_i - v0)}{W} + v0\),則:
\[ V >= v_i \\ \]
\[ -> \frac{\sum_k w_k * (v_k - v0)}{W} + v0 >= v_i \\ \]
\[ -> \frac{\sum_k w_k * (v_k - v0)}{W} >= v_i - v0 \\ \]
\[ -> \sum_k w_k * (v_k - v0) >= (v_i - v0) * W \]
相當於是在判斷最後一個式子。
這裡我們再次看見
if (curr && curr->on_rq)
狀況時需要考慮額外的 weight 和avg_runtime
,前面有解釋原因,但考慮到計算上的成本,如果反過來直接在curr && curr->on_rq
狀態變化當下更新好avg_vruntime
/avg_load
,並在不需要包含curr
時減去相關值不曉得會不會更好理解/更有效率改為透過
__pick_first_entity
從 leftmost 的rb_node
去取得對應的 se,整體邏輯則沒有變動。在 Appendix B. Request Data Structure 一章我們知道樹架構上需要維護每個子樹的
min_deadline
。具體方法是以 RB_DECLARE_CALLBACKS 定義出 callback functionmin_deadline_cb
。這 callback 會在rb_add_augmented_cached
/rb_erase_augmented_cached
中應用min_deadline_update
,以對在相關路徑的 node 做到類似之前論文中backward_update
的效果。跟 CFS 在 insert 時只要追蹤 lefmost 的改變不同,也需要對沿途走訪的節點更新 min deadline,因此使用上從
rb_add_cached
改成rb_add_augmented_cached
。insert 的時候理論上可以直接沿途更新即可,不必再 back update?
delete 時也是相似調整,要改成有 augmented 的版本。
在這個 patch 中暫且保留了 CFS 和 EEVDF 的並存狀況(EEVDF 是一個 sched_feat)。針對 CFS 的部分,和以前相同是直接挑選擁有最小 vruntime 者。
如果是透過 EEVDF 挑選下一個該被排程的 se,參照註解進行的說明。任務被挑選的方式是:
結合兩點,我們知道 EEVDF 與 CFS 的一個不同是,並不一定總是挑選紅黑樹上的 leftmost。且應用上由於是使用 augment 的紅黑樹,樹對 eligible time 雖是 binary tree,但對 deadline 可以視為 heap(priority queue)。
關於
pick_eevdf
的實作。首先確認目前的cfs_rq->curr
狀態,如果其不在 queue 中或者不 eligible,是不可以被挑選的。否則的話,從 root 開始 traverse,大致流程和原始論文的
get_req
相同。可以依循以下規則:把樹上最理想的 se 和 cuur 做比較後,可以抉擇出最後要選哪個 se。這裡留下一個保險起見的 fallback 是不知道該選誰時就退回類似 CFS 的作法選 leftmost,但這應該是不預期的錯誤狀況。
在
curr
被挑選出來進行一定 time slice 之後,要將 deadline 進行下一輪的更新。新的 deadline \(vd_i\) 會是 \(ve_i + r_i / w_i\),其中 \(r_i / w_i\) 可透過calc_delta_fair
對 slice 求得。另外要注意的地方是
if (cfs_rq->nr_running > 1)
成立的狀況,這代表的是 queue 中還有其他等待資源的排程單元。則因為update_deadline
被觸發是當前任務用掉一定 time slice 的情況,需由resched_curr
允許後續的搶佔。此外,如果 se 在 sched buddy 中的仍有可能再被挑到,因此也需要由clear_buddies
去移除。理想上 request size 必須是根據需求讓每個 se 有差異的,在目前的 review 中這還未被完成,是 TODO 項目
update_deadline
是update_curr
時的一個步驟。引入 deadline 的概念之後,reweight 的時候我們也要更新 deadline,
\[ v_{d-old} = v_e + r / w_{old} \\ \]
\[ v_{d-new} = v_e + r / w_{new} \\ \]
\[ v_{d-old} * w_{old} = v_e * w_{old} + r \\ \]
\[ (v_{d-old} - v_e) * w_{old} = r \\ \]
\[ v_e + \frac{(v_{d-old} - v_e) * w_{old}}{w_{new}} = v_e + r / w_{new} = v_{d-new} \]
基於之前對
place_entity
的改動,這裡有稍有不同,嚴格來說算是錯誤的修正。即 load 部分都要透過scale_load_down
來校正精度。在
place_entity
處也需要用之前說過的方法更新 virtual deadline,但特例是當PLACE_DEADLINE_INITIAL
這個 feature 被設置。該情況下,若是初次初始化的任務 deadline 會被刻意提早。check_preempt_tick
是 CFS 用來在每個 tick 時決定是否進行搶佔相關, 原本在entity_tick
中使用,以在每次的 timer tick 週期中檢查當前的 se 是否應被其他任務搶佔。由於 EEVDF 調整 slice 的使用方式因此這裡做出對應調整。需留意到在
update_curr
->update_deadline
中事實上已經引入resched_curr
邏輯。簡而言之,在entity_tick
最開始的update_curr
時,EEVDF 就已經對搶佔進行了檢查,後者相對 CFS 複雜的check_preempt_tick
更直接且有理可循。因此check_preempt_tick
僅僅是目前為 CFS 所保留的函式,在後續 patch 我們可以看到其被移除。於是乎當前版本
pick_next_entity
根據是使用 CFS 還是 EEVDF 決定 pick next 的手段為何。如前所述,
check_preempt_tick
只有在 CFS 中需要。同前,EEVDF 不再依賴於
sched_slice
。check_preempt_wakeup
的目的簡單說是確認喚醒的 task 是否可以搶占當前任務,進行對應設置。EEVDF 在這裡 pick 的考量是?
yield_task_fair
是對 FAIR class 做 yield 對應的操作。主要是 yield 時調整 deadline 延長一個 slice。slice 使用方式調整如前。
剩下就是一些 feature 和新增 prototype 的添加了。
[PATCH 06/15] sched: Commit to lag based placement
這個 patch 的主要改動是移除一些可以不需要從 CFS 延續到 EEVDF 的 feature(FAIR_SLEEPERS + GENTLE_FAIR_SLEEPERS)。
FAIR_SLEEPERS 算是過去 CFS 因為缺少 lag 基礎而做的補充,目的是用來給 sleeper 提供補償。但由於 FAIR_SLEEPERS 偏向是估計式的做法,可能會造成些許的 unfair。在 EEVDF 引入的狀況下,我們大可將其移除。則與之相關的 GENTLE_FAIR_SLEEPERS 也隨之刪除。
詳細可參照 mail 的敘述。這裡就不再列出詳細改動。
[PATCH 07/15] sched/smp: Use lag to simplify cross-runqueue placement
回顧在另一份筆記對
enqueue_entity
的說明。過去在做 migration 的時候,是透過標準化方式來達到公平的目的。但隨著 lag 的引入,migration 透過繼承 lag 的方式來允許 task 以更直接的方式在 queue 之間移動。關於
enqueue_entity
的更動,其一是如前面所說明,不再需要透過 min_vruntime 來做標準化,而是改由 lag 方式這件事。其二是可以看到
place_entity
的用法和 CFS 有所不同。與過去用來在任務被喚醒或是剛被創建時對目標的 se 校正不同,現在place_entity
的目的是將 \(ve\) 和 \(vd\) 根據 enqueue 狀況做更新。因此只要是做 enqueue 動作,都必須得做place_entity
。不過還是要區分是否為curr
在不同時機進行。一番改動下來,我認為在 EEVDF 和 CFS 上的
place_entity
已經可以視為是兩個不同的函式。從程式碼觀察,其內容也明顯不同。故兩者應該分開理解。不確定不需要標準化的情況下,將
curr
視為特殊狀況並預先進行place_entity
是否有意義? 在 [PATCH] sched/fair: Always update_curr() before placing at enqueue 中也提到此問題。不過在回覆中 Peter Zijlstra 倒是有提到目前的考量點。
註解中提到 PELT 計算的問題: 在
update_load_avg
算完一輪 load 之後,隨後update_cfs_group
由於 enqueue 造成的 reweight 又做了另一次更新,是否可以改善?dequeue 部份,如前所述移除過去 CFS 中才需要的標準化,不再需要另外扣除 min_vruntime。
也因應 enqueue 時不再只有
(flags & ENQUEUE_WAKEUP)
成立才進行place_entity
,因此 dequeue 時對應的總是要做update_entity_lag
。migrate_task_rq_fair
是在 fair class 中用來將 task 搬動至另一個 CPU 上,原本在 wakeup 狀況下也需要做標準化,和前面相同在 EEVDF 不再需要相關計算。task_fork_fair
顧名思義與新任務的建立有關。主要調整為:place_entity
可以直接計算出該 se 應對應的 vruntime,不需要事先設置該 se 的 vruntime(se->vruntime = curr->vruntime
)sysctl_sched_child_runs_first
是特別針對有 child 需要先於 parent 取得 CPU 的要求,但據信件討論似乎該功能已經有些問題,因此一併刪除掉了?sysctl_sched_child_runs_first
也就自此不再必要了,不過看上去此 patch 已經有相應改動,來不及賺一個 patch XDvruntime_normalized
判斷一個 task 需不需要進行標準化。主要用上的地方是在attach_task_cfs_rq
和detach_task_cfs_rq
這兩個函式。而這兩個函式主要用在兩種情境,一種是 schduler class 的轉移,從 CFS 到其他(switched_from_fair
)或是其他到 CFS(switched_to_fair
) 時,一種是 se 進行 task_group (task_move_group_fair
) 的轉移。這些情境下需要事先判斷是否有標準化的需求,並做對應的處理。則正如一再重複的,標準化相關的程式碼都可以捨棄的狀況下,此函式自然也就不再被需要了。
在 [PATCH] sched/fair: Preserve PLACE_DEADLINE_INITIAL deadline 提到目前改動中似乎存在的問題。
檢視程式碼的話,可以歸納出 fork 出一個新的 task 的過程如下。
這表示
task_fork_fair
中現存的place_entity
會對 vruntime 進行帶 flagENQUEUE_INITIAL
的更新。但隨後的enqueue_task_fair
->enqueue_entity
又會再做一次place_entity
,相當於覆蓋掉了第一次的 place。因此該 patch 中嘗試避免第一次的 place,並在第二次的 place 才引入 flagENQUEUE_INITIAL
。[PATCH 08/15] sched: Commit to EEVDF
這個 patch 主要用於將 CFS 的相關部份移除。
首當其衝的是一些只有在 CFS 下會需要的參數。
一個關鍵的改動是 last buddy 和 skip buddy 的移除。在 CFS 的時代,由於機制上的限制,需要特別標註一些特殊的
se
:__pick_next_entity
攸關 skip buddy,可予以移除。捨棄了 CFS 之後,
pick_cfs
我們當然就不需要了。同前,刪除 CFS 下所需的參數。
之前有說過 EEVDF 不再依賴
sched_slice
,後者建構於複雜的 heuristic 這點,相關的函式皆可移除。update_deadline
可以直接展開與 EEVDF 有關的部份就好。隨
sysctl_sched_latency
的移除,相關的 debug 函式(check_spread
)隨之刪去。nr_spread_over
可移除?[PATCH] sched: Remove nr_spread_over for sched debug
因應之前說明,這裡將 skip 和 last buddy 相關的程式碼移除。
check_preempt_tick
之前說過只在 CFS 中用上,因此現在可以予以移除。pick_next_entity
移除 CFS 相關程式碼,展開 EEVDF 部份。刪除
check_spread
。刪除 CFS 相關部份。
捨棄 CFS 中的 heuristic(
sched_nr_latency
)後,我們大可直接觸發 hrtick。隨 EEVDF 的更新,名為
BASE_SLICE
的 scheduler feature 也被移除。後者是在 CFS 中的sched_slice
計算應得的 slice 時,考量特殊情境所制定的對應方案。由於 EEVDF 的 slice 不再依賴於各種 heuristic,相關機制都被移除,也就包含BASE_SLICE
。而sched_idle_cfs_rq
僅在BASE_SLICE
下有相關需求,故移除之。這樣
idle_nr_running
還有其存在的必要嗎?CFS 中才需要的函式皆刪除。
sched_nr_latency
相關邏輯的移除。next_buddy_marked
這 local 變數可移除移除相關的原因都說明完畢了。剩下就對應之前的改動,大抵就是展開 EEVDF 相關程式碼,並移除隨 CFS 的部份而已。
最後可以來瀏覽一下從 CFS 變成 EEVDF 所移除的參數和一些 feature。
[PATCH 09/15] sched/debug: Rename min_granularity to base_slice
變數名稱調整至更能反應其所代表的意義,這裡就不再特別探討。
[PATCH 10/15] sched/fair: Propagate enqueue flags into place_entity()
此筆僅是新增了 flag
ENQUEUE_INITIAL
並調整了 flag 的傳遞方式。[PATCH 11/15] sched/eevdf: Better handle mixed slice length
接下來是比較新的改動,尚未被納入到 6.6 版本。
首先是引入
avg_slice
的概念。基本上和avg_vruntime
概念類似,都是時間的加權總和,只是一個追蹤 runtime,另一個是追蹤 slice/request size。entity_has_slept
首先判斷此次的place_entity
的目標是否是從 sleep 狀態被 wakeup 的情況。這類型的任務必然帶ENQUEUE_WAKEUP
。其中若是 migration 的任務(ENQUEUE_MIGRATED
)則必定符合,否則就判斷距離上次得到執行的時間是否已經超過 slice 來進一步確認。並在判斷成立後將 lag 進行補償(見下一段說明)。這裡的實作疑似有錯誤之處: 首先應該是
now - se->exec_start
而非se->exec_start - now
,後者恆為負值。其次是時間上的比較應該藉由 virtual time 才精準,詳見郵件此段則是本 patch 主要修改的關鍵: 引入 heuristic
PLACE_FUDGE
。avg_load
即 \(W = w_0 + w_1 + ... + w_n\)。則開啟PLACE_FUDGE
的狀況下,對於 slice 為 \(Si\) 的 entity \(i\),如果avg_slice
\(w_0 * S0 + w_1 * S1 + ... + w_n * Sn\) 大於 \(W * Si\),也就是其 slice 小於加權平均。此種 slice 相對小的 entity 若是透過 migration 或者是在 sleep 超出一個 slice 的時間後重新加入,則 lag (實際得到的時間 - 預期得到的時間)不超過一個 vslice 的狀況下,改為用策略 2(不納入離開後的 lag) 來處理重新回到競爭的情形。背後的考量是: EEVDF 在採用策略 1 的一大缺陷就是帶著負值的 lag 退出時,重回競爭就必須償還之。然而一個任務若進入 sleep 其實也隱含著讓出資源的意圖,假如 sleep 很長一段時間,重回競爭後卻因為負值的 lag 必須延遲進行,那豈不乾脆保持 wake 狀態來得理想多了?
PLACE_FUDGE
即為了保證合理進入 sleep 的任務得以在 wakeup 時能夠即時運行,因此做出此調整。原郵件的敘述為
但我還是不確定怎麼解釋只有 slice 短的 se 需要賦予前述的調整 :(
不過這 heuristic 的想法實則存在一些瑕疵,詳見此則回覆。
Bonus Patch: Optimize reweight and pick
來自 bytedance 的強者 Abel Wu 對 EEVDF 相關的改動 [PATCH 0/4] sched/eevdf: Optimize reweight and pick 很值得仔細深入,該改動引入 fast path 以優化 EEVDF 在挑選下個任務方式,並經測試後確實帶來正向的效益。嘗試了解他人是如何切入重要議題並做出貢獻,以及如何進行對應的測試,或許是提升自己貢獻 kernel 能力的好方法。
[PATCH 1/4] sched/eevdf: Fix vruntime adjustment on reweight
這個 patch 主要的目標在於修改 reweight 時對 vruntime/deadline 的重新計算方式。
作者提出的推論其一: 當對一個 lag 不為 0 的 entity 進行 reweight 的時候,其 vruntime 也應該做相應的調整(原本的做法認為 vruntime 是維持不變的)。
透過反證法來證明上述敘述成立,我們可以先假設進行 reweight 一個 lag != 0 的 entity 時不必調整 vruntime。且假設一個 se 原本的 weight 為 \(w\)、vruntime 為 \(v\),而整個 cfs_rq 的平均 vruntime 則為 \(V\); reweight 後,新的 weight 為 \(w'\)、新的 vruntime \(v'\),新的 cfs_rq 平均 vruntime 是 \(V'\)。
由於 lag 不應受 reweight 影響而不同,即
\[ lag = (V - v)*w = (V'- v')*w' \]
而基於先假設 \(v == v'\),上述式子可歸納為
\[ V' = (V - v)*w/w' + v -- (1) \]
另一方面,平均 vruntime 的算法應該根據如下方式
\[ V' = \frac{(WV - wv + w'v')}{(W - w + w')} = \frac{(WV - wv + w'v)}{(W - w + w')}-- (2) \]
則結合 (1) 和 (2),可推導出:
\[ \frac{(WV + w'v - wv)}{(W + w' - w)} = \frac{(V - v)*w}{w'} + v \\ \]
\[ => \frac{(WV+(-Wv+Wv)+w'v-wv)}{(W+w'-w)} = \frac{(V - v)*w}{w'} + v \\ \]
\[ => \frac{(WV - Wv + v * (W+w'-w)}{(W+w'-w)} = \frac{(V - v)*w}{w'} + v \\ \]
\[ => \frac{(WV - Wv)}{(W + w' - w)} + v = \frac{(V - v)*w}{w'} + v \\ \]
\[ => \frac{(V - v)*W}{(W + w' - w)} = \frac{(V - v)*w}{w'} -- (3) \]
對於 lag 非零的狀況,式子 (3) 可再化簡:
\[ => \frac{W}{(W + w' - w)} = \frac{w}{w'} \\ \]
\[ => Ww' = Ww + ww' - ww \\ \]
\[ => W(w' - w) = w(w' - w) \]
結果得出結論?
\[ => W = w \]
意味著 cfs_rq 只有一個 entity (總 weight \(W\) 等於原本的 weight \(w\))(?) 若是如此則 \(v\) 必然等於 \(V\)。進一步又可以得出 \(lag = V - v' = 0\),出現與假設牴觸的矛盾,故反證推論一。
推論其二: 當對一個 entity 進行 reweight 的時候,平均的 vruntime 則維持不變。
首先我們透過 lag 不變這一事實知道以下推導
\[ lag = (V - v)*w = (V' - v')*w' \\ \]
\[ => v' = V' - \frac{(V - v)*w}{w'} -- (4) \]
而根據平均 vruntime 的算法,代入 (4) 可求:
\[ V' = (WV - wv + w'v') / (W - w + w') \\ \]
\[ = (WV - wv + w'(V' - \frac{(V - v)*w}{w'})) / (W - w + w') \\ \]
\[ = (WV - wv + w'V' - Vw + wv) / (W - w + w') \\ \]
\[ = (WV + w'V' - Vw) / (W - w + w') \\ \]
\[ ==> V'*(W - w + w') = WV + w'V' - Vw \\ \]
\[ ==> V' * (W - w) = (W - w) * V -- (5) \]
如果 reweight 的 entity 是 cfs_rq 中的唯一一個,則 \(W == w\),則 reweight 時不會改變 vruntime(\(V == V'\));若否,\(W \ne w\),從式子 (5) 可得 \(V == V'\)。在此兩種情況下都滿足 \(V == V'\),故得證。
結合前兩個理論,可以知道 reweight 對 v' 的影響為
\[ v' = (V' - (V - v)*w)/w' -- (4) \]
\[ = (V - (V - v)*w)/w' -- (6) \]
結合上述理論,可以透過 (6) 來更新 vruntime。
deadline 的部份,之前我們就推導過,但新理論告訴我們 \(v \ne v'\),讓我們重新推導
\[ v_d = v + r/w \\ \]
\[ v_d' = v' + r/w' \\ \]
\[ w'v_d' = w'v' + r \\ \]
\[ w'v_d' = w'v' + wv_d - wv \\ \]
\[ v_d' = v' + w(v_d - v)/w' \\ \]
\[ v_d' = V' - (V - v)*w/w' + (d - v)*w/w' \\ \]
\[ v_d' = V - (V - v)*w/w' + (d - v)*w/w' \\ \]
\[ v_d' = (w'V - wV + wv + wd - wv)/w' \\ \]
\[ v_d' = (w'V - wV + wd)/w' \\ \]
\[ v_d' = V + (d - V)w/w' \\ \]
根據推導程式碼也修正了新 deadline 的更新方式。
則 reweight_entity 根據此改動調整作法:
curr
除外,因為其為特例不會在 rbtree 中)reweight_eevdf
根據前面介紹的調整 deadline 和 vruntimecurr
除外)這裡額外做了
update_min_vruntime
,但有幾個問題待解答:update_min_vruntime
?update_curr
中也會update_min_vruntime
,是否min_vruntime
沒有立即更新的必要性?update_min_vruntime
了? 因為目前的流程上似乎update_curr
必然發生在需要最新的 min_vruntime 時。這樣以來可以省下一些不必要的計算這邊可以參考我寄信請教原作者所得到的答案和討論細節:
https://lore.kernel.org/lkml/20231107090510.71322-1-wuyun.abel@bytedance.com/T/#t
需要 reweight 的條件也根據實際的影響修改。
[PATCH 2/4] sched/eevdf: Sort the rbtree by virtual deadline
在原始的設計中,紅黑樹所排序的目標是 vruntime,而 augmented 部份則是紀錄每個節點為 root 的子樹中之最小 deadline。然而由於在 EEVDF 下,任務的挑選是根據 eligible 的任務中擁有最小 "deadline" 者,因此該設計無法直接受益於 kernel 中的紅黑樹可以 cache 住最左(leftmost)/最小 key 值的節點的特性,每次挑選任務都必須從 root 開始走訪。
我們可以調整紅黑樹的用法來得到以 O(1) 複雜度選出任務的方法,具體作法是改讓紅黑樹排序 deadline,而 augmented 部份則是紀錄每個節點為 root 的子樹中之最小 vruntime。此 patch 之目的即與此相關。
因應上述說明,se 中改為記錄
min_vruntime
作為紅黑樹的 aumented 資料。由於 rb tree 的調整,
__pick_first_entity
現在找到的會是最小 deadline,debug 資訊額外引入此信息(left_deadline
)。而原本的left_vruntime
要改用新增的__pick_root_entity
去取得。entity_before
主要是用來作為 insert 紅黑樹的比較函式 callback。因此定義要改為判斷 deadline 的先後。entity_eligible
的算法仍然和之前一樣是看 vruntime 和 average vrnutime 的關係。\[ \sum_k w_k * (v_k - v0) >= (v_i - v0) * W \]
這裡只是抽象出額外一層的
vruntime_eligible
函式,對實際邏輯沒有影響。由於 rbtree 的調整,樹上最小 vruntime 的候選可以先透過
__pick_root_entity
來拿得到 root,再藉由 augmented 用法是 vruntime 的 heap 這件事去拿到整個樹的最小 vruntime,以正確進行update_min_vruntime
。紅黑樹的 augmented 使用方式不同,因此更新相關之料的 callback 需要進行對應調整。
callback 對應修改。
這裡看到之前提到新增的函式
__pick_root_entity
,實際上就是取得紅黑樹的 root 對應的 se 而已。行為都調整了,註解當然要附上對應的修改。
下面,使用紅黑樹挑選任務的方式
pick_eevdf
修改。從 diff 來解釋可能有些不好閱讀,這裡我們直接展開修改後的函式說明。首先,有幾個特殊的狀況可以允許捷徑。
RUN_TO_PARITY
的話,當下選取的任務會一直被執行到 non-eligible 或者取得一個新的 slice,而不是選擇 deadline 最優先的那個RUN_TO_PARITY
討論串用
curr->vlag == curr->deadline
判斷有沒有拿到新的 slice 有點取巧。這要對應著set_next_entity
中的 HACK 註解看。事實是如果curr->on_rq == true
,此時的curr->vlag
實際上不代表vlag
。而set_next_entity
借用se->vlag
將值設成 deadline,那麼當curr->vlag == curr->deadline
,就表示 se 在set_next_entity
之後尚未執行足一個 time slice。但
update_curr
理論上總是會更新curr->deadline
? 而做pick_eevdf
似乎前總是會做update_curr
,不確定這判斷式為 true 的可能性為何?一般狀況下,從 root 開始,走訪邏輯是:
[PATCH 3/4] sched/eevdf: O(1) fastpath for task selection
完成了紅黑樹的調整後,是時候利用其特性來加入 O(1) 的路徑了。
事先拿到擁有最小 deadline 的節點(
__pick_first_entity
)。由於紅黑樹有 leftmost-cached 的設計,這個操作是 O(1) 時間可以完成。這邊做一個小調整,因為
nr_running == 1
的情況下唯一的節點(node
) 對應的 entity 就是se
,可以直接返回之。引入 fast path: 樹上擁有最小 deadline 的 entity 如果 eligible,那麼他的優先級必然超過其他 entity,我們可以直接決定挑選之。如此一來,就不需要再去進行複雜度為 O(logn) 查詢紅黑樹操作。
後面只是對應 fast path 增加一個
found
位置可以直接跳過中間搜尋樹的過程。[PATCH 4/4] sched/stats: branch statistics for pick_eevdf
這個 patch 主要作為追蹤測試結果使用,不會合入正式 Linux,因此就不多做說明。
RFC Patches
下面,我們來關注 EEVDF 尚未被合併至主分支的進一步改動(2023/12/10)。
[RFC][PATCH 12/15] sched: Introduce latency-nice as a per-task attribute
雖然在之前的 patch 中,kernel 已經引入了 EEVDF 的演算法架構,然而 time slice/request size 對於每個任務目前都只能是固定的,因此沒辦法充分利用 EEVDF 的優勢。有鑑於此,我們需要進一步的 latency-nice 概念: 類似於影響 weight 的 nice value, latency-nice 較低的 process 表示對於延遲要求嚴格。
這個 patch 先將實作 latency-nice 所需的資料結構和框架定義好。
與之相關的
task_struct
成員latency_prio
。增加一個新的 sched flag
SCHED_FLAG_LATENCY_NICE
,這代表未來 user 端應該可以透過sched_setattr
來調整特定任務的 latency-nice。這邊針對不同版本的
sched_attr
結構大小給出定義,與 kernel 在處理相關結構時的檢查相關。這裡定義好 user 端透過
sched_setattr
攜帶的參數struct sched_attr
中的sched_latency_nice
。相關細節也可以從新增的註解中得知。task 初始的
latency_prio
定義為DEFAULT_PRIO
fork 預設的 prio 對應到 nice 0。
__sched_setscheduler
根據 user 給定的sched_attr
設定內部的 task。真正設定前要對參數內容進行基本的檢查(nice 值是否合理、有否設置SCHED_FLAG_LATENCY_NICE
表示數值有效)。反過來,
sched_getattr
的時候要將latency_prio
換算成 latency-nice 回給 user。Debug 資訊中也將
latency_prio
加入。[RFC][PATCH 13/15] sched/fair: Implement latency-nice
正式引入可變動的 request size 到 kernel 中。因為目前 prio 值的對應是直接沿用之前 weight 的同一套系統,例如假設 base slice 為 3 ms,則參考
sched_prio_to_weight
:注意這個 slice 值有可能在實際應用中沒有實質幫助,或許未來需要再調整。
特別定義出用來設置
latency_prio
的函數set_latency_prio
,後者還附帶調整 latency_prio 對 slice 的影響,後面會再進一步說明。覺得這邊的修改對 core - fair 架構的上下區分有點模糊: 即便我們不打算用 fair class scheduler,在 user 端還是可以設置 latency-nice,並造成 slice 的對應計算。
對 slice、latency_prio 的設置一律經由
set_latency_prio
。set_latency_fair
在set_latency_prio
下使用,即之前敘述到乘上 1024 再除以 weight 來計算 prio 對應之 request size 操作。安插上面的幾個
set_latency_prio
之後,我們不再需要在update_deadline
時特地把se->slice
初始化好。place_entity
的地方是不是也要調整?因為會從
core.c
呼叫,將set_latency_fair
之 prototype 加入至 .h 檔。[RFC][PATCH 14/15] sched/fair: Add sched group latency support
根據前述的改動,我們提到可以利用
sched_setattr
來調整 task 的 latency nice,可是卻沒有考慮到 task_group 該如何調整。於是在本 patch 中針對此問題實作解決方案。在 Linux 中,每個 task 可以被分群以屬於特定 "cgroup",依此來限制與隔離系統資源(CPU, memory 等)。使用者端可以透過存取相對應的虛擬檔案系統,即 cgroup-v2 提供之機制來控制 cgroup 對系統資源的權限。
本文暫不對 cgroup 做詳細介紹,有興趣的讀者可以參考以下文章:
為達前述目的,虛擬檔案系統中額外增加了
cpu.latency.nice
。使用者可以對該檔案寫入以達到類似於用sched_setattr()
設置 task_group 之 latency nice 的效果。在 kernel 端,
task_group
必然要新增額外變數latency_prio
來記錄藉 cgroup-v2 所設置的對應值,後者由新的函式介面sched_group_set_latency
來進行。對
cpu.latency.nice
讀取以cpu_latency_nice_read_s64
來處理。以PRIO_TO_NICE
將儲存的latency_prio
轉換為對應的 nice 值提供給 user 端。對
cpu.latency.nice
寫入則以cpu_latency_nice_write_s64
來處理。檢查 nice 值的合理性後,以NICE_TO_PRIO
將 nice 值轉為 prio 值並以sched_group_set_latency
設置之。在
cpu_legacy_files
和cpu_files
新增新的 cftype 結構,藉此在 cgroup-v2 中導入cpu.latency.nice
檔案。對檔案的讀寫操作即對應到cpu_latency_nice_read_s64
和cpu_latency_nice_write_s64
。當新的 task_group 被建立時,會先指派預設的 prio
DEFAULT_PRIO
。有了 task_group 的 latency prio 機制後,對應的 se 之預設
slice
就可以透過set_latency_fair
設置對應值。新增的
sched_group_set_latency
則可以對 task_group 更新 latency prio。留意到 lantency prio 一旦更新,group 下所有 se 的slice
值也需要同步處理。[RFC][PATCH 15/15] sched/eevdf: Use sched_attr::sched_runtime to set request/slice
此 patch 是提出另一個用非以 prio 來間接設置
slice
的方案,改以sched_attr
中的sched_runtime
來決定。Reference