一、主動調度
調用schedule函數進行主動調度,其具體流程比較簡單,需要掌握調度類,調度隊列,調度實體以及他們和CPU之間的關係,這些知識在上一篇博文《函數堆棧與進程調度基礎》中進行了一定簡單的介紹。
簡言之,當調用schedule函數進行主動調度時,首先會調用通過調度類找到下一個要被調度的進程,然後將當前進程切換狀態放入對應調度類的調度隊列裏面,等待再次被喚醒。而對於被調度的這個隊列我們就要對其進行上下文切換。
二、進程上下文切換
上下文切換主要幹下面兩件事,是通過context_switch
函數實現的:
- 切換進程空間,即虛擬內存
- 切換寄存器和CPU上下文,即保存寄存器等當前進程相關變量
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{//linux-4.13.16\kernel\sched\core.c
struct mm_struct *mm, *oldmm;
...
mm = next->mm;
oldmm = prev->active_mm;
...
switch_mm_irqs_off(oldmm, mm, next);//熟悉吧,對於進程空間的切換
...
/* Here we just switch the register state and the stack. */
switch_to(prev, next, prev);//寄存器和棧空間的切換,即上面說的切換寄存器和保護上下文
barrier();//編譯器優化指令,保證執行順序的不變性
return finish_task_switch(prev);
}
關於進程切換的小總結:
- 在上面
switch_mm_irqs_off
進行內存切換時,通過將要切換進程的頂級頁表項放在CPU的Cr3寄存器中,實現了用戶空間的切換。 - 內核棧的切換其實還是需要上一講的知識作爲鋪墊,因爲task_struct裏面有一個成員變量就指向進程的內核棧,需要注意的是內核棧是位於內核態的直接映射區的。
- 至於內核棧的棧頂指針,它會在進程切換時將其值加載到cpu對應的TSS(任務狀態段)中,CPU有一個寄存器TR(task register)就是指向這個TSS段的,使用時直接通過這個指針就可以得到了。
- 而用戶態的棧頂指針等一些寄存器變量他會保存在我們上一講剛剛講過的內核棧中的pt_regs結構體中,這樣當執行用戶態時就會恢復進程的上下文情況。。。
到目前爲止,若我們瞭解計算機組成原理的話就會發現似乎還沒有提及指令指針寄存器(IP)的變化,那它是怎樣變化的哩? - 巧妙的IP寄存器的值變遷,太妙了,這方面極客時間劉超老師的《Linux操作系統調度篇》講的是真的清楚,他總結的進程調度第一原理——“進程切換時都會調用__schedule的機理”,是順利理解IP寄存器變遷的良藥,超級推薦。
注意:可以使用ps aux
命令查看進程運行時間等基本信息。
三、搶佔式調度
主動調度指是某進程主動調用了schedule函數,那麼下面是一些發生搶佔式調度的時機:
- 每當系統觸發一個時鐘中斷時就會調用中斷處理函數,裏面會嘗試進行搶佔式調度
- 當一個由於IO而被掛起的進程由於受到IO來了的信號而被喚醒時就會比較與當前正在運行進程的優先級,若被喚醒的IO進程優先級更高就會觸發搶佔式中斷。
注意:以上兩種所謂的搶佔式調度只是將當前進程標記爲了應該被搶佔,但還未真正的被搶佔,因爲最終只有調用了__shcedule纔可以被搶佔
那麼什麼時候纔會調用__shcedule進行真正的搶佔式調度哩,具體會分有用戶態搶佔和內核態搶佔時機?
1.用戶態搶佔時機
- 進程從系統調用返回用戶態時侯是觸發調用schedule的一個時機
- 對用戶態進程而言,從中斷返回的那個時刻,也是一個被搶佔的時機
2.內核態搶佔時機
- 內核定義有一個宏
preempt_enable
,每當在內核進程進行某些操作之前會先調用該宏判斷是否有需要發生搶佔的進程(上面對應的發生情況(tick,io線程)會用一個變量做標記),若有的話就會調用shcedule進行調度。 - 同樣和用戶態一樣,當發生中斷返回時也會嘗試進行schedule,完成搶佔式調度的最後一步。
四、總結
一張非常好看的圖片,源:趣談Linux操作系統:搶佔式調度是如何發生的?