pick_next_task():從就緒隊(duì)列中選中一個(gè)進(jìn)程 內(nèi)核在選擇進(jìn)程進(jìn)行調(diào)度的時(shí)候,會(huì)首先判斷當(dāng)前 CPU 上是否有進(jìn)程可以調(diào)度,如果沒(méi)有,執(zhí)行進(jìn)程遷移邏輯,從其他 CPU 遷移進(jìn)程,如果有,則選擇虛擬時(shí)間較小的進(jìn)程進(jìn)行調(diào)度。
內(nèi)核在選擇邏輯 CPU 進(jìn)行遷移進(jìn)程的時(shí)候,為了提升被遷移進(jìn)程的性能,即避免遷移之后 L1 L2 L3 高速緩存失效,盡可能遷移那些和當(dāng)前邏輯 CPU 共享高速緩存的目標(biāo)邏輯 CPU,離當(dāng)前邏輯 CPU 越近越好。
等當(dāng)前的進(jìn)程執(zhí)行完調(diào)度邏輯之后,開(kāi)啟搶占,也就是說(shuō),其他進(jìn)程可以剝奪當(dāng)前進(jìn)程的 CPU 了。
而如果某個(gè)進(jìn)程像個(gè)強(qiáng)盜一樣一直占著 CPU 不讓,內(nèi)核會(huì)通過(guò)搶占機(jī)制(比如上一篇文章提到的周期調(diào)度機(jī)制)進(jìn)行一次進(jìn)程調(diào)度,從而把當(dāng)前進(jìn)程從 CPU 上踢出去。
如果當(dāng)前 CPU 上已無(wú)進(jìn)程可調(diào)度,則執(zhí)行負(fù)載邏輯,從其他 CPU 上遷移進(jìn)程過(guò)來(lái);
如果當(dāng)前 CPU 上有進(jìn)程可調(diào)度,從隊(duì)列中選擇一個(gè)高優(yōu)進(jìn)程,所謂高優(yōu)進(jìn)程,即虛擬時(shí)間最小的進(jìn)程;
下面,我們分兩步拆解上述步驟。
4.1 負(fù)載均衡邏輯 內(nèi)核為了讓各 CPU 負(fù)載能夠均衡,在某些 CPU 較為空閑的時(shí)候,會(huì)從繁忙的 CPU 上遷移進(jìn)程到空閑 CPU 上運(yùn)行,當(dāng)然,如果進(jìn)程設(shè)置了 CPU 的親和性,即進(jìn)程只能在某些 CPU 上運(yùn)行,則此進(jìn)程無(wú)法遷移。
負(fù)載均衡的核心邏輯是 idle_balance 方法:
// kernel/sched/fair.c:9851 static int idle_balance(struct rq *this_rq ...) { int this_cpu = this_rq->cpu; struct sched_domain *sd; int pulled_task = 0;
內(nèi)核根據(jù)處理器與主存的距離將處理器分為兩個(gè) NUMA 節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)有兩個(gè)處理器。NUMA 指的是非一致性訪問(wèn),每個(gè) NUMA 節(jié)點(diǎn)中的處理器訪問(wèn)內(nèi)存節(jié)點(diǎn)的速度不一致,不同 NUMA 節(jié)點(diǎn)之間不共享 L1 L2 L3 Cache。
每個(gè) NUMA 節(jié)點(diǎn)下有兩個(gè)處理器,同一個(gè) NUMA 下的不同處理器共享 L3 Cache。
每個(gè)處理器下有兩個(gè) CPU 核,同個(gè)處理器下的不同核共享 L2 L3 Cache。
選中邏輯:先比較 A、B、c1+c2+c3 的虛擬時(shí)間,發(fā)現(xiàn) A 最小,由于 A 已經(jīng)是進(jìn)程,選中 A,如果 A 比當(dāng)前運(yùn)行進(jìn)程虛擬時(shí)間還小,下一個(gè)運(yùn)行的進(jìn)程就是 A,否則保持當(dāng)前進(jìn)程不變。
4.3 pick_next_task() 小結(jié) 內(nèi)核在選擇進(jìn)程進(jìn)行調(diào)度的時(shí)候,會(huì)先判斷當(dāng)前 CPU 上是否有進(jìn)程可以調(diào)度,如果沒(méi)有,執(zhí)行進(jìn)程遷移邏輯,從其他 CPU 遷移進(jìn)程,如果有,則選擇虛擬時(shí)間較小的進(jìn)程進(jìn)行調(diào)度。
內(nèi)核在選擇邏輯 CPU 進(jìn)行遷移進(jìn)程的時(shí)候,為了提升被遷移進(jìn)程的性能,即避免遷移之后 L1 L2 L3 高速緩存失效,盡可能遷移那些和當(dāng)前邏輯 CPU 共享高速緩存的目標(biāo)邏輯 CPU,離當(dāng)前邏輯 CPU 越近越好。
5.2 切換通用寄存器 虛擬內(nèi)存切換完畢之后,接下來(lái)切換進(jìn)程執(zhí)行相關(guān)的通用寄存器,對(duì)應(yīng)邏輯為 switch_to(prev, next ...); 方法,這個(gè)方法也是切換進(jìn)程的分水嶺,調(diào)用完之后的那一刻,當(dāng)前 CPU 上執(zhí)行就是 next 的代碼了。
// arch/arm64/include/asm/processor.h:113 struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; // 對(duì)應(yīng) x29 寄存器 unsigned long fp; unsigned long sp; // 對(duì)應(yīng) lr 寄存器 unsigned long pc; }; 這些值剛好與上述匯編片段的代碼一一對(duì)應(yīng)上,讀者應(yīng)該不需要太多匯編基礎(chǔ)就可以分析出來(lái)。
上述匯編中,最后一行 msr sp_el0, x1,x1 寄存器中保存了 next 的指針,這樣后續(xù)再調(diào)用 current 宏的時(shí)候,就指向了下一個(gè)指針:
// arch/arm64/include/asm/current.h:15 static struct task_struct *get_current(void) { unsigned long sp_el0; asm ("mrs %0, sp_el0" : "=r" (sp_el0)); return (struct task_struct *)sp_el0; } // current 宏,很多地方會(huì)使用到 #define current get_current() 進(jìn)程上下文切換的核心邏輯到這里就結(jié)束了,最后我們做下小結(jié)。