漫話Linux之“躺平”: IDLE 子系統(tǒng)
CPU 就和皮鞋廠的工人一樣, 無可奈何之時(shí)也得躺平。歷代CPU的架構(gòu)師都有一顆仁慈的心——給自己的產(chǎn)品留下了躺平的功能,而且是一代更比一代強(qiáng)。相關(guān)的指令有HLT/PAUSE/MWAIT/UMWAT/TPAUSE,其中我最喜愛的是UMWAIT,它的妙處我們后面再說。
當(dāng)然經(jīng)理并不會(huì)輕易讓CPU工人去摸魚, 必須滿足一定的條件。Linux 內(nèi)核中那么嚴(yán)格說來是由Scheduler 來判斷目前的工作量是否飽滿。如果工作量實(shí)在不夠,也只好讓CPU劃劃水, 睜只眼閉只眼, 好歹省點(diǎn)電費(fèi)不是。雖然在CPU層面摸魚的手段花樣百出, 但是在OS層面只有一個(gè)抽象的概念就是IDLE。在這個(gè)時(shí)候經(jīng)理表面上放任CPU工人劃水, 其實(shí)打的是省電的小九九,這也算是Win-Win的帕累托改進(jìn)。那么問題就來了, Scheduler 到底是如何判斷當(dāng)前工作量不飽滿呢?
調(diào)度器中idle 觸發(fā)條件
Linux Scheduler 為每一個(gè)CPU工人都維護(hù)了一個(gè)RunQueue 可以認(rèn)為是一個(gè)任務(wù)列表, 當(dāng)且僅當(dāng)這個(gè)列表里所有的任務(wù)都不是runnable的狀態(tài)時(shí), Scheduler 才會(huì)切換到idle process。也就是說這個(gè)時(shí)候CPU工人完全無所事事, 必須休息節(jié)省體力以及節(jié)省電費(fèi)! 同時(shí)還要注意可以通過nohz/nohz_full啟動(dòng)參數(shù)減少tick 中斷對(duì)正在休息的CPU工人的干擾!
身世顯赫
那么首先我們要考慮得是idle進(jìn)程從何而來,在描述細(xì)節(jié)之前我先劇透一下, idle進(jìn)程雖然名字聽起來不怎么樣, 但是出身顯赫。首個(gè)idle進(jìn)程實(shí)際上轉(zhuǎn)化自0號(hào)進(jìn)程!內(nèi)核成為了生活的真相帝, 躺平并不是人人都能擁有的選項(xiàng)!
具體的過程則是說來話長(zhǎng). 在開辟鴻蒙之初, kernel 所有的進(jìn)程之始祖是一個(gè)靜態(tài)的結(jié)構(gòu)體:
- struct
- task_struct init_task
init_task 并非由任何kernel API所創(chuàng)建, 是所有進(jìn)程的祖先, 名副其實(shí)的the one。
它肩負(fù)了非常重要的職責(zé), 例如創(chuàng)建首個(gè)內(nèi)核線程kernel_init。從而達(dá)到 一生二,二生三,三生萬物 的效果。不過今天這些不是本文的重點(diǎn), 重點(diǎn)在于大功告成之際, init_task 并未事了拂衣去而是默默的轉(zhuǎn)化成了idle 進(jìn)程。 它繼續(xù)無言的守護(hù)著整個(gè)系統(tǒng), 俯首甘當(dāng)孺子牛啊。
這里參見代碼 init/main.c line 451,這里是函數(shù)rest_init 的最后階段大家可以發(fā)現(xiàn)調(diào)用了 cpu_startup_entry 這個(gè)函數(shù)。
我們可以跳轉(zhuǎn)到 kernel/sched/idle.c 來一窺細(xì)節(jié). 這里第一個(gè)arch_cpu_idle_entry 是可選接口,x86并沒有實(shí)現(xiàn),核心中的核心還是函數(shù) do_idle()-- just 躺平.
如果沒有在啟動(dòng)時(shí)強(qiáng)制idle 模式為poll, 則我們將在這里進(jìn)入真正的躺平函數(shù) cpuidle_idle_call.
在smp系統(tǒng)中 core0 以外的其它c(diǎn)ore 也會(huì)通過 start_secondary 函數(shù)最終產(chǎn)生0號(hào)進(jìn)程且調(diào)用cpu_startup_entry 來進(jìn)入idle loop之中。
CPU各種“躺平”的姿勢(shì)
各個(gè)廠子出產(chǎn)的CPU工人的idle 姿勢(shì)也是慢慢演化的, 從簡(jiǎn)入繁, 花樣百出。下面我們一起來浮光掠影的看一下這些"超能力"。
X86
HLT
這是初代的idle 指令, 于486DX時(shí)代引入. 首先只有在ring0的特權(quán)級(jí)別才能執(zhí)行HLT指令, 同時(shí)執(zhí) 行的效果是 CPU 進(jìn)入了C1/C1E state(參考ACPI標(biāo)準(zhǔn))。嚴(yán)格說起來只能算是摸魚0.1v。APIC/BUS /CACHE 都是照常運(yùn)轉(zhuǎn), 只要中斷發(fā)生, CPU工人立即就要回到產(chǎn)線繼續(xù)搬磚。C1E 稍微又優(yōu)待了CPU點(diǎn), 停止內(nèi)部時(shí)鐘又降了壓, 比較體貼。
PAUSE
這個(gè)也是非常早期的指令(Pentium 4)許可CPU 工人打個(gè)盹,大概從幾個(gè)到幾十個(gè)cycles吧(各代CPU有差異)。為什么要打盹呢?其實(shí)主要是要降低CPU工人在特定情況下(spin-lock)給內(nèi)存控制器帶來的壓力,與其讓CPU工人阻塞了內(nèi)存控器, 不如讓他打個(gè)盹吧。在最近的幾代Xeon之上還附帶了降低功耗的buff。
MWAIT/MONITOR
新一代CPU架構(gòu)師回顧了前輩的設(shè)計(jì), 覺得CPU工人的權(quán)力完全沒有得到充分的照顧, 應(yīng)該給予更進(jìn)一步的休息機(jī)會(huì)乃至真正的躺平!而且喚醒的條件又多了一個(gè), 除了中斷這種強(qiáng)喚醒模式以外, 又加了內(nèi)存的CacheLine Invalidate喚醒。你的鄰居CPU 除了敲門以外還多了拿橡皮筋彈窗戶玻璃的渠道。首先這兩條指令也只能在ring0 級(jí)別執(zhí)行, 首先是調(diào)用MONITOR 地址范圍, 其次是MWAIT 進(jìn)入休眠,一旦該地址的內(nèi)存被任何其它的主體修改, 則喚醒CPU工人起來繼續(xù)搬磚。同時(shí)這次最大的改進(jìn)是可以通過MWAIT 進(jìn)入各種不同的Cstate。其中C6 是我心目中真正的躺平 CPU 電壓可以歸0同時(shí)cache 也停, 實(shí)至名歸啊。
最常見的C State 狀態(tài)詳細(xì)描述,引自[2]
Cstate | Name | Description |
---|---|---|
C0 | Operating State | CPU fully turned on |
C1E | Enhanced Halt | Stops CPU main internal clocks via software and reduces CPU voltage; bus interface unit and APIC are kept running at full speed |
C3 | Deep Sleep | Stops all CPU internal and external clocks |
C6 | Deep Power Down | Reduces the CPU internal voltage to any value, including 0 Volts |
UMWAIT/UMONITOR
MWAIT雖好, 但是奈何必須在ring0特權(quán)級(jí)下執(zhí)行, 如果是一些特定的用戶級(jí)應(yīng)用例如DPDK, Linux的 idle driver 是很難得到執(zhí)行的機(jī)會(huì),所以CPU架構(gòu)師又生憐憫之心, 允許CPU在用戶級(jí)也能進(jìn)入躺平的模式, 不過作為妥協(xié)連C1 state都不行,只能進(jìn)入 C0.1/C0.2 等神秘模式。效果還有待觀察,不過話說回來SPR這代Xeon才開始支持....距離上市少說還得1年之久。
TPAUSE
UMWAIT 指令的升級(jí)加強(qiáng)版, 附帶了一個(gè)timer。TPAUSE 可以讓CPU工人根據(jù)規(guī)定好的時(shí)間進(jìn)行休息, 時(shí)間一到, 立刻繼續(xù)搬磚。當(dāng)然這也是一個(gè)簇新簇新的指令,大家還要等待SPR。
ARM
ARM的Idle-state 級(jí)別情況比較復(fù)雜一些, 更多的是和具體的芯片實(shí)現(xiàn)相關(guān). 但是總體上也是把握幾個(gè)大的類別:
- 只是停止CPU內(nèi)部時(shí)鐘
- CPU降頻
- 停止給Cache供電
- 停止給CPU供電
和X86 相比 Arm的喚醒機(jī)制沒有和MESI協(xié)議連接有些遺憾(也就是沒有實(shí)現(xiàn)通過MEM 地址監(jiān)控的方式達(dá)成喚醒).
YEILD
這條頗為類似 PAUSE基本功能接近,使用場(chǎng)景也接近(spin lock).
WFE/WFI
這兩條指令顧名思義 wait for event/ wait for interrupt,中斷這條大家都可以理解類似HLT,那么event這條就值得看看了。ARM架構(gòu)可以從一個(gè)CPU向所有其它CPU 發(fā)送event(sev 指令),我的理解類似IPI廣播,收到了此event的CPU如果處于idle狀態(tài), 則需要立即喚醒。(注:和宋老師討論以后發(fā)現(xiàn) event 和IPI的一個(gè)區(qū)別是不需要ISR來響應(yīng),同時(shí)event并不能喚醒由于WFI指令進(jìn)入的idle,這個(gè)有點(diǎn)囧,反過來中斷可以喚醒由于WFE進(jìn)入的idle。這兩個(gè)躺平姿勢(shì)水很深啊)
軟件實(shí)現(xiàn)
除了硬件的各種花式躺平技術(shù)之外還有兩類“偽躺平”技術(shù)。
idle polling
通過啟動(dòng)參數(shù), 我們可以指定cpu的idle 進(jìn)程并不調(diào)用硬件提供的idle功能而僅僅是polling, 這種情況主要用于需要極低的CPU從idle狀態(tài)返回時(shí)延的場(chǎng)景。那么如果壓根沒有進(jìn)入實(shí)際的idle狀態(tài),當(dāng)然時(shí)延是極低的,同時(shí)也能融入到idle整體的框架,不至于破壞規(guī)矩開特例。
halt-polling
在打開虛擬化的場(chǎng)景下, 事情就變得更加有趣了。大多數(shù)情況下, qemu 會(huì)缺省的只對(duì)guest 提供HLT指令作為idle的唯一機(jī)制,但是 HLT 指令毫無懸念的會(huì)觸發(fā)VMEXIT。雖然說大多數(shù)情況下kvm看到exit reason 是HLT 也只是執(zhí)行poll而已, 但是VMEXIT/VM_RESUME 還是如此的痛,畢竟幾千個(gè)cycles已經(jīng)無謂流逝, 追求極致的我們?cè)趺茨芊湃钨Y源浪費(fèi)。于是Redhat在Guest端引入了halt poll 機(jī)制, 也就是說如果matrix中的CPU工人首先開始假摸魚(poll), 如果假摸魚時(shí)間超過了閾值才真的去觸發(fā)HLT指令。如果很快就被從假模魚狀態(tài)拉回去搬磚, 則省去了出入matrix的費(fèi)用(經(jīng)理得意的笑了)。
相關(guān)細(xì)節(jié)參考內(nèi)核文檔Documentation/admin-guide/pm/cpuidle.rst:
以及:
Documentation/virt/guest-halt-polling.rst:
CPU idle driver/governor
最后軟件硬件各路躺平姿勢(shì)花樣繁多, 內(nèi)核無奈又祭出了抽象大法把idle的時(shí)長(zhǎng)與返回時(shí)延的選擇與具體執(zhí)行idle的機(jī)制分離開來。
- idle governor 就負(fù)責(zé)做時(shí)長(zhǎng)與時(shí)延的選擇,也可以稱為 idle -select。
- idle driver 則是負(fù)責(zé)通過我們上面描述的各種軟硬件機(jī)制來實(shí)現(xiàn)governor指定的目標(biāo)。同時(shí)向governor menu 經(jīng)理提供各種不同機(jī)制的性能參數(shù),以供menu經(jīng)理選擇, 就是所謂的idle-enter。
圖片引自[6]
idle governor 缺省的算法只有一個(gè)就是menu, 還有3個(gè)候選的ladder/TEO/haltpoll 算法但是一般需要重新編譯內(nèi)核來激活。
- ladder 算法故名意思, 是首先從能耗較高/返回時(shí)延較小的狀態(tài)開始,當(dāng)系統(tǒng)idle超過了閾值再進(jìn)入更深的節(jié)能狀態(tài),從而逐步升級(jí)節(jié)能狀態(tài)。俗稱添油戰(zhàn)術(shù)也可以美其名曰“快速迭代”。
- menu 算法單從名字看則有點(diǎn)讓人摸不到頭腦,其內(nèi)部機(jī)制也確實(shí)頗為復(fù)雜,menu算法主要是要在節(jié)能狀態(tài)的停留時(shí)間與系統(tǒng)能容忍的返回時(shí)延之間做權(quán)衡以達(dá)到最佳效果。
請(qǐng)?jiān)徫曳浅2痪_地描述一下menu。menu仿佛一個(gè)非常敬業(yè)的經(jīng)理凡事都要精算做出最優(yōu)選擇,CPU工人一旦休息再想打起精神干活這個(gè)轉(zhuǎn)換是有一個(gè)代價(jià)的, 往往需要口頭鼓勵(lì)(畫餅)+物質(zhì)鼓勵(lì)(肉夾饃)。那么經(jīng)理就要考慮如果工人休息時(shí)間太短,休息的好處遠(yuǎn)低于讓CPU工人重新振作的代價(jià),那么這個(gè)休息就是不合理的(無情啊)。而且休息也有好些種類, 從假休息到完全躺平, 到底哪一種休息狀態(tài)才是收益比最佳的? menu會(huì)無情的選擇那個(gè)休息帶來好處大于重新振作代價(jià)的方案。同時(shí)menu經(jīng)理還會(huì)受到來自客戶的壓力, 時(shí)延也是要滿足的。客戶的耐心大抵上都是不好的, menu經(jīng)理會(huì)瘋狂試探客戶的底線。它選擇的方案是滿足客戶耐心上限的情況下CPU工人消耗能耗最少的方案。同時(shí)做到以上兩點(diǎn) menu經(jīng)理大約才能有希望完成OKR/KPI。
結(jié)語
今天我們一起浮光掠影的討論了一下Linux的各種躺平姿勢(shì),從中能領(lǐng)略到一代代CPU架構(gòu)師對(duì)CPU打工人的關(guān)愛。最后我衷心的祝愿CPU打工人在層出不窮的各類躺平技術(shù)加持下,最終能同各位經(jīng)理一起實(shí)現(xiàn)碳中和的OKR/KPI。
參考文獻(xiàn)
[1]https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/idle-states.txt
[2]https://www.dell.com/support/kbdoc/en-ie/000060621/what-is-the-c-state
[3] Intel SDM latest version
[4] https://www.kernel.org/doc/html/latest/virt/guest-halt-polling.html
[5]https://www.kernel.org/doc/html/latest/admin-guide/pm/cpuidle.html
[6] https://www.programmersought.com/article/13982556297/
作者簡(jiǎn)介
作者Liam,海外老碼農(nóng),對(duì)Linux、應(yīng)用密碼學(xué)、CPU微架構(gòu)、高速網(wǎng)絡(luò)通信等領(lǐng)域都有所涉獵。
本文轉(zhuǎn)載自微信公眾號(hào)「Linux閱碼場(chǎng)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Linux閱碼場(chǎng)公眾號(hào)。