內核的調度操作分爲觸發和執行兩個部分,觸發時僅僅設置一下當前進程的TIF_NEED_RESCHED
標誌,執行的時候則是通過schedule()
函數來完成進程的選擇和切換。當前進程的thread_info->flags
中TIF_NEED_RESCHED
位表示需要調用schedule()
函數進行調度。內核在兩種情況下會設置該標誌,一個是在時鐘中斷進行週期性的檢查時,另一個是在被喚醒進程的優先級比正在運行的進程的優先級高時。
週期性地更新當前任務的狀態時:
定時中斷處理函數中會調用schedule_tick()
用於處理關於調度的週期性檢查和處理,其調用路徑是和時鐘處理有關的tick_periodic()->update_process_times()->scheduler_tick()
或者tick_sched_handle()->update_process_times()->scheduler_tick()
,主要用於更新就緒隊列的時鐘、CPU負載和當前任務的運行時間統計等,如下所示:
//linux-3.13/kernel/sched/core.c
void scheduler_tick(void)
{
int cpu = smp_processor_id(); //獲取當前cpu編號
struct rq *rq = cpu_rq(cpu); //取得對應cpu的rq(就緒隊列)
struct task_struct *curr = rq->curr; //獲取當前運行的任務
sched_clock_tick();
raw_spin_lock(&rq->lock);
update_rq_clock(rq); //更新隊列時鐘
curr->sched_class->task_tick(rq, curr, 0); //調用當前任務的調度類對應的函數
update_cpu_load_active(rq); //更新本處理器的負載
raw_spin_unlock(&rq->lock);
perf_event_task_tick();
#ifdef CONFIG_SMP
rq->idle_balance = idle_cpu(cpu);
trigger_load_balance(rq, cpu); //必要時進行負載均衡
#endif
rq_last_tick_reset(rq);
}
其中curr->sched_class->task_tick(rq, curr, 0);
這行代碼調用了當前任務的調度類的task_tick()
函數,這個函數根據具體情況決定是否需要對當前任務設置TIF_NEED_RESCHED
標誌,如果需要則最終調用set_tsk_need_resched()
設置該標誌。需要注意的是,此處僅僅是設置標誌而沒有執行schedule()
函數,在各種系統調用、中斷的返回代碼最後,纔會根據這個標誌來決定是否執行schedule()
函數。
睡眠的任務被喚醒時:
當睡眠任務所等待的事件到達時,內核(例如驅動程序的中斷處理函數)將會調用wake_up()
喚醒相關的任務,並最終調用try_to_wake_up()
。它完成三件事:將任務重新添加到就緒隊列,將運行標誌設置爲TASK_RUNNING
,如果被喚醒的任務可以搶佔當前運行任務則設置當前任務的TIF_NEED_RESCHED
標誌。
設置了TIF_NEED_RESCHED
標誌之後,真正調用執行schedule()
函數的時機只有兩種,第一種是系統調用或者中斷返回時,根據TIF_NEED_RESCHED
標誌決定是否調用schedule()
函數(從效率方面考慮,趁着還在內核態把該處理的事情處理完畢);第二種情況是當前任務因爲原因需要睡眠,進程睡眠後立即調用schedule()
函數,在內核中這種情況也比較多,比如磁盤、網卡等設備驅動程序中。
參考文獻:《Linux技術內幕》
PS:剛開始學習Linux內核的時候很容易被各種結構體各種概念充斥腦海,一團亂麻。這時候需要把它們各自負責的功能以及之間相互的配合理清楚,推薦這本書。看完《Linux內核設計與實現》後可以相互比照,效果不錯。