上篇文章中,就提到了小米玄戒O1的多核任務調度策略,但講得不夠詳細,尤其是對于完全公平調度器和能效感知調度,這次我們就深度剖析一下這兩種調度策略。
目錄
- 1. 完全公平調度器(CFS)
- 1.1 完全公平調度基本原理
- 1.2 完全公平調度基本流程
- 1.3 完全公平調度中的負載均衡
- 1.3.1 負載均衡原理與觸發機制
- 1.3.2 負載均衡中的任務遷移機制
- 1.3.3 負載均衡的特點與意義
- 2. 能效感知調度(EAS)
- 2.1 核心目標與技術背景
- 2.1.1 🚩核心目標
- 2.1.2 💫技術背景
- 2.2 基本原理
- 2.2.1 能量建模
- 2.2.2 任務放置決策
- 2.2.3 頻率選擇
- 2.2.4 喚醒決策
- 2.2.5 負載均衡
- 2.3 能效度量
- 2.3.1 能效計算
- 2.3.1 能效計算中的延遲懲罰
- 核心思想:時間價值量化
- 2.3.3 能效度量的實際案例
- 3. 完全公平調度 VS 能效感知調度
- 4. 后記
1. 完全公平調度器(CFS)
完全公平調度器(Completely Fair Scheduler, CFS
)是Linux內核中的一種進程調度算法,它通過紅黑樹數據結構來實現公平的CPU時間分配。
1.1 完全公平調度基本原理
CFS的核心思想是:給所有可運行進程提供公平的CPU時間份額。它不像傳統調度器那樣使用時間片的概念,而是基于以下原則:
- 虛擬運行時間(vruntime):記錄每個進程已經獲得的CPU時間,CFS總是選擇
vruntime
最小的進程運行。 - 權重(nice值):不同優先級的進程獲得不同的CPU時間權重。
- 紅黑樹:用于高效地找到
vruntime
最小的進程。
在多核系統中,每個CPU核心都有自己的運行隊列(run queue
),包含自己的紅黑樹和調度狀態。這樣可以減少核心間的競爭,提高并行效率。
為什么要使用紅黑樹?
CFS使用紅黑樹是因為:
- 高效查找:總能以O(log n)時間找到vruntime最小的進程(最左側節點)
- 平衡性好:紅黑樹是自平衡的,插入刪除操作后能保持較好的平衡
- 適合頻繁更新:進程的vruntime經常變化,需要高效地重新插入
虛擬運行時間(vruntime)的計算(注意這里是delta值,也就是每次疊加的虛擬運行時間)
delta_vruntime = (delta_exec * NICE_0_LOAD) / (task_weight * (cpu_capacity / max_capacity))
這樣一來,實時的虛擬運行時間是這樣的
vruntime += delta_vruntime
其中,NICE_0_LOAD是基準權重(默認值為 1024)。
delta_exec
:任務實際運行的時間(物理時間,單位ns)。
cpu_capacity
: 當前CPU核心的計算能力。
max_capacity
:系統中最強核心的容量(通常為 1024)。
權重task_weight是通過nice值轉化而來:
例如,nice 值為 - 10 的進程權重約為 9548,而 nice 值為 19 的進程權重僅為 15。權重越高,vruntime 增長越慢,進程獲得的 CPU 時間越多。
在實際的開發中,這種非線性運算相對來說是很消耗計算資源的,一般都會將這種映射保存到一個數組中,隨用隨取。
// include/linux/sched/prio.h
static const int prio_to_weight[40] = {/* -20 */ 88761, 71755, 56483, 46273, 36291,/* -15 */ 29154, 23254, 18705, 14949, 11916,/* -10 */ 9548, 7620, 6100, 4904, 3906,/* -5 */ 3121, 2501, 1991, 1586, 1277,/* 0 */ 1024, 820, 655, 526, 423,/* 5 */ 335, 272, 215, 172, 137,/* 10 */ 110, 87, 70, 56, 45,/* 15 */ 36, 29, 23, 18, 15,
};
CFS維護一個紅黑樹(cfs_rq),按 vruntime 升序排列所有可運行進程。每次調度時,選擇 vruntime
最小的進程運行,確保 “饑餓” 進程優先獲得資源。例如,若進程 A 的 vruntime 為 10ms,進程 B 為 20ms,則 A 會優先被調度。
那么,權重是根據nice值來的,那么nice值又是哪里來的?
nice 值的完整生命周期應該是這樣的:
-
初始賦值:
用戶顯式指定(nice/renice)或繼承父進程值。
系統根據進程類型設置默認值(如交互式進程為 - 5,批處理為 10)。 -
內核處理:
通過set_load_weight()將 nice 值轉換為權重(影響vruntime增長速度)。
將 nice 值映射為靜態優先級(static_prio = 120 + nice×2)。 -
動態調整:
用戶通過setpriority()系統調用修改 nice 值。
內核根據進程行為自動微調(通過update_curr()
接口)如 I/O 密集型進程優先級提升。 -
調度決策:
實時進程(SCHED_FIFO/SCHED_RR)優先于普通進程(SCHED_NORMAL)。
普通進程在 CFS 隊列中按vruntime排序,權重高的進程(nice 值低)優先調度。
嚴格意義上來說,內核并未修改任務的nice值,而是直接修改了任務的優先級。從這點來說,nice值是人為設定的。
1.2 完全公平調度基本流程
玄戒O1共有10個核,這樣講起來會非常復雜,為方便理解,我們簡化整個架構,基本原理是不變的。
我們模擬一個具有2個大核(高性能)和2個小核(節能)的ARM架構系統,基本配置如下:
大核:計算能力為1024,調度隊列為rq_big0和rq_big1
小核:計算能力為512,調度隊列為rq_little0和rq_little1
任務集:6個任務(T1-T6),初始vruntime=0,權重如下:T1(nice=0), T2(nice=0), T3(nice=0), T4(nice=0) T5(nice=0), T6(nice=0),
這6個任務的類型分別如下:
T1, T2:CPU密集型(持續運行)
T3, T4:I/O密集型(頻繁睡眠)
T5, T6:后臺任務(低優先級)
初始任務分配(假設沒有任務親和性限制)
各個核心維護的紅黑樹如下:
為了便于理解,我在即將處理的任務上方加上了一個解開鎖的標志:
5ns
后
大核0:選擇T1(0)運行 → vruntime += 5 → T1(5)
大核1:選擇T3(0)運行 → vruntime += 5 → T3(5)
小核0:選擇T5(0)運行 → vruntime += 10 → T5(10)
小核1:選擇T6(0)運行 → vruntime += 10 → T6(10)
此時的紅黑樹變成了這樣:
10ns
后
大核0:選擇T2(0)運行 → vruntime += 5 → T2(5)
大核1:選擇T4(0)運行 → vruntime += 5 → T4(5)
小核0:選擇T5(10)運行 → vruntime += 10 → T5(20)
小核1:選擇T6(10)運行 → vruntime += 10 → T6(20)
T3, T4是I/O密集型任務,會出現頻繁的睡眠現象,那么我們假定T3(5)此時發生了睡眠,此時將其從紅黑樹中移除,直至下次喚醒。
此時的紅黑樹變成了這樣:
15ns
后(負載均衡遷移)
此時,由于T5任務的vruntime為30,觸發了負載均衡,因此從小核0遷移到大核0中去處理。
同樣地,T6任務的vruntime為30,觸發了負載均衡,因此從小核1遷移到大核1中去處理。
20ns
后(T3任務喚醒)
假設此時任務T3被重新喚醒,以前是在大核1中處理,現在我們仍然交由它進行處理。同時新進來了需要處理的新任務T7(0),當前兩個小核是空閑的,我們將其放在小核0中進行處理。
25ns
后
繼續執行當前需要執行的任務即可
1.3 完全公平調度中的負載均衡
在 CFS 調度器中,負載均衡觸發與任務遷移的核心邏輯與vruntime
(虛擬運行時間)和處理器能力的動態關系密切相關。以下從原理、遷移機制和特點三方面詳細解釋:
1.3.1 負載均衡原理與觸發機制
vruntime
是 CFS 衡量任務 “公平性” 的關鍵指標,計算公式為:
vruntime = 實際運行時間 × (NICE值對應的權重 / 系統基準權重)
但在多核心場景下,還需考慮處理器能力的影響:
處理能力強的核心(如大核)在相同物理時間內,任務的 vruntime 增長更慢(因為等效于 “時間被壓縮”);
處理能力弱的核心(如小核)在相同物理時間內,任務的 vruntime 增長更快。
假設大核處理能力是小核的 2 倍(如題目中設定),則:
大核上運行 1ms,等效于小核上運行 2ms;
大核上任務的 vruntime 增長速度是小核的 1/2。
結構不同的核心,處理效率也會有所不同,這樣經過相同的時間,不同核心中的任務就好像被“區別對待”了,所以當差距太大的時候,就需要從小核遷移到大核,進行一定的補償。
1.3.2 負載均衡中的任務遷移機制
任務選擇
優先遷移小核紅黑樹中vruntime
最高的任務,因為它最需要更多 CPU 資源來降低 vruntime
。
權重校準
在上述的例子中,為了方便理解,在將T5遷移到大核后,并沒有進行“時間校準”,這樣會導致一個問題,就是T5可能會在較長時間內得不到處理。所以我們需要再任務遷移后進行時間校準,也就是與大核中原有的任務把時間線拉平。
權重比例校準的公式稍微復雜一些:
校準比例 = src_weight / dst_weight = 512/1024 = 0.5
T5校準后vruntime = (30 - src_min) × 0.5 + dst_min≈ 15 + dst_min (假設min_vruntime差異可忽略)
大概相當于將T5的vruntime
減少到了原來的一半,也就是15。
插入大核紅黑樹
然后根據vruntime
大小,將其插入大核的紅黑樹中即可。
1.3.3 負載均衡的特點與意義
- 校準即公平:
vruntime
轉換不是簡單的數值搬運,而是通過權重比例進行時間維度轉換 - 未運行卻接近:因校準公式主動縮放vruntime值,使遷移后任務立即適配新CPU的時間體系
- 短期犧牲換長期收益:短暫等待后,任務將在高算力CPU上更高效地追趕進度
這種設計確保了 跨異構CPU的全局公平性,即使遷移初期有調度延遲,長期看仍是最優策略。
2. 能效感知調度(EAS)
完全公平和能效感知這兩種調度器策略代表了現代操作系統(尤其是Linux內核)在處理任務時追求的不同核心目標:一個是公平性優先,另一個是能效優先。
2.1 核心目標與技術背景
2.1.1 🚩核心目標
- 在保證系統性能和響應性的前提下,最小化整個系統的能量消耗。
- 利用現代處理器的異構多核架構和動態電壓頻率調節技術來節省功耗。
2.1.2 💫技術背景
異構多核系統: 現代移動設備CPU通常包含不同類型的內核:
“大核”: 高性能核心,頻率高,單線程性能強,但功耗也高(尤其是高頻時功耗增長非線性)。
“小核”: 高能效核心,頻率較低,性能較弱,但單位性能功耗非常低,空閑功耗也極低。
動態電壓頻率調節: CPU可以根據負載動態調整工作電壓和頻率。低頻低壓時功耗顯著低于高頻高壓。
CPU 空閑狀態: CPU核心可以進入不同的休眠狀態,關閉部分或全部電路以節省功耗,但喚醒需要時間和能量。(從上篇文章我們可以知道,小米玄戒O1的CPU核心,并非每個都可以進入睡眠狀態,對于功耗小且需要處理“日常”事務的A520核心來說,就無需睡眠)。
2.2 基本原理
2.2.1 能量建模
EAS的核心是建立一個系統能量模型。這個模型描述了:
不同CPU核心在不同工作頻率下執行任務時的功耗。
不同CPU核心在不同工作頻率下執行任務的性能/能力。
核心在不同休眠狀態下的功耗和進入/退出該狀態的延遲與能耗。
以下圖為例,就是某CPU核心的頻率-功耗曲線。
這樣就同時涵蓋了CPU的暫態與穩態性能。
2.2.2 任務放置決策
當有新任務喚醒或需要遷移時,EAS 不僅僅看哪個CPU最空閑,而是預測將任務放在哪個CPU核心上運行,以及該核心運行在什么頻率下,能使完成該任務所需的“能量延遲積”最小化或滿足性能約束下的能耗最低。從這點就可以看出來,能效感知調度明顯要更加復雜,且更加適用于手機處理器,畢竟對于移動設備來說,續航能力🚗直接決定了使用感受。
??偏好小核: 對于輕量級、后臺或不緊急的任務,EAS 會優先將其調度到能效核心上運行。因為這些任務在小核上就能滿足性能要求,且功耗遠低于在大核上運行。
??適時用大核: 對于計算密集型、對延遲敏感或需要高吞吐量的任務,EAS 會將其調度到性能核心上運行,以確保性能達標。同時,它會盡量讓大核運行在能滿足任務需求的最低必要頻率上。
??集群感知: 考慮到CPU拓撲(如哪些核心共享L2緩存、電源域)。將相關聯的任務調度到共享緩存的集群內核心上運行,可以減少緩存失效,提高性能并間接節能。避免不必要的跨集群遷移。
2.2.3 頻率選擇
EAS 與 schedutil CPU頻率調控器深度集成(這部分內容也非常多,后面有時間可以單獨寫一篇💪)。
傳統的調控器可能只根據單個CPU的利用率調頻。schedutil結合EAS的能量模型信息,做出更全局、更符合能效目標的頻率決策。
例如,如果一個任務在小核上運行需要80%的利用率才能滿足要求,而在大核上只需20%的利用率,EAS 結合 schedutil 可能會選擇將其放在大核上運行在較低頻率,因為這比讓小核跑滿頻更省電(因為大核低頻的單位性能功耗可能低于小核高頻)。
2.2.4 喚醒決策
當任務從睡眠中被喚醒時,EAS 會評估:
哪個CPU核心最適合運行它(考慮能效模型、親和性、緩存熱度)。
喚醒該核心(如果目標核心在休眠)的能耗開銷是否值得。
可能選擇喚醒一個空閑的小核來運行輕量任務,而不是喚醒一個休眠的大核。
2.2.5 負載均衡
傳統的負載均衡主要追求各CPU核心的負載均勻。EAS的負載均衡更注重能效均衡。
它可能允許小核集群負載較高(因為它們在負載下的能效依然好),而讓大核集群保持較低負載甚至部分核心休眠,只要整體系統性能和響應性達標。
遷移任務時,優先考慮遷移到能效更高的核心集群,并評估遷移帶來的能耗收益是否大于遷移操作本身的能耗開銷。
2.3 能效度量
2.3.1 能效計算
能效感知調度的重要依據,就是CPU能耗,CPU能耗可根據下面的公式進行計算:
穩態運行能耗:這個開頭就提到了,直接從曲線就可以得到。
喚醒能耗:同樣可以通過曲線得到。
延遲懲罰:用于衡量因延遲滿足任務需求而導致的用戶體驗損失或系統效率下降。其本質是將“時間敏感度”轉化為可計算的能耗等價量,使調度器能在性能與能效間做出數學上的最優決策。
2.3.1 能效計算中的延遲懲罰
核心思想:時間價值量化
若為省電而延遲處理用戶交互(如點擊響應),會導致卡頓,傳統調度器無法量化“卡頓”的成本。延遲懲罰將延遲時間映射為虛擬“能耗懲罰值”,與其他能耗(如CPU功耗)統一量綱比較。
這個公式與上面的公式幾乎是一樣的,只是多了個延遲懲罰的敏感因子α。
公式我們知道了,那么延遲懲罰到底是怎么算出來的呢?
延遲懲罰總共有3種度量方法,分別是:
- 基于任務類型的分類度量
很簡單,就是根據不同的任務類型,使用不同的懲罰函數,可以看到用戶交互的懲罰函數是非線性的,超過閾值t
max后運算結果會指數上升。
- 基于硬件事件的動態度量
基于硬件事件的動態度量,指的是用性能監測單元(PMU)提供的數據計算延遲懲罰,延遲一般會有兩種來源,分別是L2緩存未命中和前端停頓,當然,這個前端指的是CPU流水線的前端,前端負責獲取和解碼將要執行的指令。基于硬件事件的動態度量公式如下:
從上面的公式也可以看出來,懲罰來自緩存未命中和分支預測失敗,畢竟分支預測失敗是導致前端停頓的一個重要原因(關于分支預測我們會在下篇文章中詳細講解)因此公式中并未將其他前端停頓的因素包含進去。
至于小米玄戒O1這款SOC到底用的哪種度量方法,從我的角度來說,至少用了其中一種。而且很明顯,這2種方法不會完全獨立開來,硬件與軟件是協同作用的。
2.3.3 能效度量的實際案例
假設有個用戶點擊圖標啟動APP這樣一個場景,很明顯是對延遲非常敏感的一個任務。基本屬性配置如下:
類型:最高延遲敏感級📈
最大容忍延遲:80ms
權重系數:Kui=12
決策選項:
1??喚醒小核(喚醒延遲 20ms,運行耗時 60ms → 總延遲 80ms)。
實際能耗:小核喚醒(5mJ) + 運行(1.2GHz×60ms=8mJ) = 13mJ
延遲懲罰:Platency=12*(80-80)^2 = 0
總成本:13mJ
2??喚醒大核(喚醒延遲 50ms,運行耗時 30ms → 總延遲 80ms)。
實際能耗:大核喚醒(20mJ) + 運行(2.0GHz×30ms=25mJ) = 45mJ
延遲懲罰:同樣是0
總成本:45mJ
3??等待小核空閑(預計延遲 150ms)。
實際能耗:小核喚醒(0mJ) + 運行(1.2GHz×60ms=8mJ) = 8mJ
延遲懲罰:Platency=12*(150-80)^2 = 58800mJ
總成本:達到了驚人的58808mJ
💥
所以最終選擇方案1進行任務調度。
3. 完全公平調度 VS 能效感知調度
經比較,這兩種調度算法有以下的一些不同。
完全公平調度 (CFS) 是調度器設計的經典范式,專注于在時間維度上公平地分配CPU資源。它在同構系統和強調公平性的場景中表現出色,但對現代異構多核處理器的能效潛力利用不足。
能效感知調度 (EAS) 代表了調度器設計的進化方向,將能量消耗提升為核心優化目標。它深度理解硬件特性(大小核、DVFS、空閑狀態),通過聯合優化任務放置和CPU頻率,在滿足性能要求的前提下,主動尋找能耗最低的執行路徑。它是解決移動設備和數據中心能效瓶頸的關鍵技術。
4. 后記
小米玄戒O1架構的具體調度策略我們不得而知,以上僅僅是依據目前公開資料做的合理推測。
接下來的一篇帖子想更新一下分支預測方面的內容,畢竟對于十幾級流水線深度的超大核來說,分支預測的準確度直接影響了處理任務的效率。敬請關注!😆
參考資料:
- 嗶哩嗶哩up主老石談芯【全網最深度解讀】小米玄戒O1:一場輸不起的芯片戰爭
- 嗶哩嗶哩up主軒轅的編程宇宙Linux如何調度進程?大學老師不講的,看完動畫秒懂!
- 嗶哩嗶哩up主5c477超頻預備知識 性能和功耗的關系 基礎頻率是怎么來的【愛玩數碼 第三期】
- 紅黑樹在線生成網站Red/Black Tree Visualization
“潮平兩岸闊,風正一帆懸”,我們正處在科技飛速發展的時代,愿我們專注技術,順勢啟航!??