- 前言
在計(jì)算機(jī)系統(tǒng)中,cpu的主要任務(wù)是執(zhí)行程序,其核心步驟包括取指、譯碼和執(zhí)行。然而,若無程序需要執(zhí)行,cpu如何處理這一情況呢?有人可能會(huì)認(rèn)為直接停止運(yùn)行即可,但實(shí)際上,決定何時(shí)停止以及如何停止需要在復(fù)雜的軟硬件環(huán)境中仔細(xì)考慮。
讓我們轉(zhuǎn)向Linux內(nèi)核,Linux系統(tǒng)中的CPU被兩種程序所占用:一類是進(jìn)程(或線程),即進(jìn)程上下文;另一類是中斷和異常的處理程序,即中斷上下文。
進(jìn)程負(fù)責(zé)處理事務(wù),例如讀取用戶輸入并在屏幕上顯示。當(dāng)事務(wù)處理完畢,如用戶不再輸入且無新內(nèi)容需顯示時(shí),進(jìn)程可以釋放CPU,但隨時(shí)準(zhǔn)備重新占用(如用戶突然按鍵)。同樣,若系統(tǒng)無中斷或異常事件,CPU不會(huì)在中斷上下文中花費(fèi)時(shí)間。
在Linux內(nèi)核中,CPU這種無所事事的狀態(tài)被稱為idle狀態(tài),而cpuidle框架正是管理這種狀態(tài)的工具。
注:cpuidle框架系列文章將以ARM64為例平臺(tái)。由于ARM64發(fā)布時(shí)間較短,早期版本的內(nèi)核中沒有相關(guān)代碼,因此我們選擇了最新的3.18-rc4版本的內(nèi)核。
- 功能概述
曾經(jīng),Linux內(nèi)核的cpu idle框架非常簡單,簡單到driver工程師只需在“includeasm-armarch-xxxsystem.h”中定義一個(gè)名為arch_idle的內(nèi)聯(lián)函數(shù),并在該函數(shù)中調(diào)用內(nèi)核提供的cpu_do_idle接口即可,其余的實(shí)現(xiàn)內(nèi)核會(huì)幫我們完成,如下:
static inline void arch_idle(void) { cpu_do_idle(); }
盡管簡單,但這包含了idle處理的兩個(gè)關(guān)鍵點(diǎn):
1)idle進(jìn)程
idle進(jìn)程的存在是為了解決“何時(shí)idle”的問題。
我們知道,Linux系統(tǒng)運(yùn)行的基礎(chǔ)是進(jìn)程調(diào)度。當(dāng)所有進(jìn)程都不再運(yùn)行時(shí),即為CPU idle狀態(tài)。內(nèi)核通過一個(gè)簡單的方法來判斷這種狀態(tài):在init進(jìn)程(系統(tǒng)的第一個(gè)進(jìn)程)完成初始化任務(wù)后,將其轉(zhuǎn)變?yōu)閕dle進(jìn)程。由于idle進(jìn)程的優(yōu)先級(jí)最低,當(dāng)其被調(diào)度時(shí),說明系統(tǒng)中其他進(jìn)程已停止運(yùn)行,即CPU已idle。最終,idle進(jìn)程會(huì)調(diào)用idle指令(如WFI),使CPU進(jìn)入idle狀態(tài)。
“ARM WFI和WFE指令”中提到,WFI Wakeup events會(huì)將CPU從WFI狀態(tài)喚醒,這些事件通常是一些中斷事件。因此,CPU喚醒后會(huì)執(zhí)行中斷處理程序,處理程序中會(huì)喚醒某些進(jìn)程。當(dāng)處理程序返回時(shí),進(jìn)行調(diào)度,如果沒有其他進(jìn)程需要執(zhí)行,調(diào)度器會(huì)恢復(fù)idle進(jìn)程的運(yùn)行,當(dāng)然,idle進(jìn)程不會(huì)做任何事情,繼續(xù)進(jìn)入idle狀態(tài),等待下一次喚醒。
2)WFI
WFI用于解決“如何idle”的問題。
通常情況下,ARM CPU在idle時(shí)可以使用WFI指令,將CPU置于等待中斷狀態(tài)。在這種狀態(tài)下,至少會(huì)關(guān)閉ARM核的時(shí)鐘,以節(jié)省功耗(具體實(shí)現(xiàn)取決于ARM核的設(shè)計(jì),可參考“ARM WFI和WFE指令”)。
也許您會(huì)覺得,上述過程已經(jīng)足夠好,為什么還要開發(fā)cpuidle框架?我的理解是:
- 軟件架構(gòu)
在Linux內(nèi)核中,cpuidle框架位于“drivers/cpuidle”文件夾中,包含cpuidle核心、cpuidle調(diào)控器和cpuidle驅(qū)動(dòng)三個(gè)模塊,再結(jié)合位于內(nèi)核調(diào)度中的cpuidle入口,共同完成CPU的idle管理。軟件架構(gòu)如下圖:
1)內(nèi)核調(diào)度模塊
位于kernelschedidle.c中,負(fù)責(zé)實(shí)現(xiàn)idle線程的通用入口(cpuidle入口)邏輯,包括idle模式的選擇和idle的進(jìn)入等。
2)cpuidle核心
cpuidle核心負(fù)責(zé)實(shí)現(xiàn)cpuidle框架的整體結(jié)構(gòu),主要功能包括:
cpuidle核心的代碼主要包括:cpuidle.c、driver.c、governor.c、sysfs.c。
3)cpuidle驅(qū)動(dòng)
不同的架構(gòu)和CPU核會(huì)有不同的cpuidle驅(qū)動(dòng),平臺(tái)驅(qū)動(dòng)開發(fā)者可以在cpuidle核心提供的框架下開發(fā)自己的cpuidle驅(qū)動(dòng)。代碼主要包括:cpuidle-xxx.c。
4)cpuidle調(diào)控器
Linux內(nèi)核的框架有兩種比較固定的抽象模式:
模式2的解釋可能有些抽象,但在cpuidle的場景中容易理解:
前面提到,許多CPU提供了多種idle級(jí)別(即所謂的“方案”),這些idle級(jí)別的主要區(qū)別在于“idle時(shí)的功耗”和“退出時(shí)的延遲”。cpuidle驅(qū)動(dòng)(機(jī)制)負(fù)責(zé)定義這些idle狀態(tài)(每個(gè)狀態(tài)的功耗和延遲分別是多少),并實(shí)現(xiàn)進(jìn)入和退出的相關(guān)操作。最終,cpuidle驅(qū)動(dòng)會(huì)將這些信息傳遞給調(diào)控器,由調(diào)控器根據(jù)具體的應(yīng)用場景決定選擇哪種idle狀態(tài)(策略)。
內(nèi)核中的cpuidle調(diào)控器都位于governors/目錄下。
- 軟件流程
在閱讀本章之前,請(qǐng)先閱讀以下三篇文章:
Linux cpuidle framework(2)_cpuidle核心
Linux cpuidle framework(3)_ARM64通用CPU idle驅(qū)動(dòng)
Linux cpuidle framework(4)_menu調(diào)控器
前面提到過,內(nèi)核會(huì)在系統(tǒng)啟動(dòng)完成后,在init進(jìn)程(或線程)中處理cpuidle相關(guān)事務(wù)。大致過程如下(內(nèi)核啟動(dòng)相關(guān)的分析將在其他文章中詳細(xì)介紹):
cpu_startup_entry流程:
具體代碼比較簡單,不再分析,但有一點(diǎn)需要特別說明:
使用cpuidle框架進(jìn)入idle狀態(tài)時(shí),本地irq處于關(guān)閉狀態(tài),因此從idle返回時(shí),只能繼續(xù)執(zhí)行,直到irq被打開,才能執(zhí)行相應(yīng)的中斷處理程序,這與傳統(tǒng)的cpuidle不同。同時(shí),這也間接驗(yàn)證了“Linux cpuidle framework(4)_menu調(diào)控器”中提到的,為什么menu調(diào)控器在reflect接口中只是簡單地設(shè)置一個(gè)標(biāo)志。因?yàn)閞eflect是在關(guān)閉中斷時(shí)被調(diào)用的,需要盡快返回,以便處理中斷事件。