詳解Linux核心程序排程函式schedule()的觸發和執行時機
核心的排程操作分為觸發和執行兩個部分,觸發時僅僅設定一下當前程序的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技術內幕》