1966 /* Here we just switch the register state and the stack. */1967 switch_to(prev, next, prev);1968 1969 barrier();
linux核心進程調度器基於兩個函數:周期性調度器函數和主調度器函數.
周期性調度器
所謂周期性調度器就是scheduler_tick中實現。如果系統正在活動中,核心會按照HZ自動調用這個函數。實際上在每個滴答的handler中會調用這個函數。如果在沒有進程等待調度,那麼在電腦電力供應不足的情況下,也可以關閉該調度器以減少電能消耗。該函數會啟用負責當前進程的調度類的周期性調度方法。
if (curr != rq->idle) /* FIXME: needed? */ curr->sched_class->task_tick(rq, curr);
由於調度器的模組化結構,調度器本身實現比較簡單,因為主要的工作完全可以委託給特定調度器類的方法。
task_tick的實現完全依賴於底層的調度器類。例如,CFS調度器類會在方法中檢測進程是否已經運行太長時間,以避免過長的延遲。如果需要調度,那麼會調用set_tsk_need_resched函數來設定TIF_NEED_RESCHED標誌,以表示該請求。
主調度器
在核心的許多地方,如果需要將CPU分配給與當前活動進程不同的另一個進程,都會直接調用主調度器函數schedule。在從系統調用返回後,核心會檢查當前進程是否設定了重新調度標誌TIF_NEED_RESCHED標誌(例如上面提到的 scheduler_tick就可能會設定這個標誌),如果設定了,則調用schedule函數。
我們現在看一下schedule的實現
3616 /*3617 * schedule() is the main scheduler function.3618 */3619 asmlinkage void __sched schedule(void)3620 {3621 struct task_struct *prev, *next;3622 long *switch_count;3623 struct rq *rq;3624 int cpu;3625 3626 need_resched:3627 preempt_disable();3628 cpu = smp_processor_id();3629 rq = cpu_rq(cpu);3630 rcu_qsctr_inc(cpu);3631 prev = rq->curr;3632 switch_count = &prev->nivcsw;
3630 先獲得當前正在啟動並執行進程,儲存在prev中。
3639 /*3640 * Do the rq-clock update outside the rq lock:3641 */3642 local_irq_disable();3643 __update_rq_clock(rq);3644 spin_lock(&rq->lock);3645 clear_tsk_need_resched(prev);
3643 更新rq->prev_clock_raw和rq->clock
3645 清除TIF_NEED_SCHED標誌
3647 if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {3648 if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&3649 unlikely(signal_pending(prev)))) {3650 prev->state = TASK_RUNNING;3651 } else {3652 deactivate_task(rq, prev, 1);3653 }3654 switch_count = &prev->nvcsw;3655 }
如果當前進程處在可中斷睡眠狀態,那麼必須再次提升為運行進程。否則調用deactivate_task使得進程停止活動
3660 prev->sched_class->put_prev_task(rq, prev);3661 next = pick_next_task(rq, prev);
調用調度器類的put_prev_task通知調度器類當前的進程要被另外一個進程取代。這個操作並不意味著prev從就緒隊列移除,而是提供了一個時機,執行一些記賬工作。
pick_next_task選擇下一個應該執行的進程。注意新選擇的進程有可能就是原來的進程,比如就緒隊列中僅有一個進程的情況。
3665 if (likely(prev != next)) {3666 rq->nr_switches++;3667 rq->curr = next;3668 ++*switch_count;3669 3670 context_switch(rq, prev, next); /* unlocks the rq */3671 } else3672 spin_unlock_irq(&rq->lock);
如果新進程不等於舊的進程,那麼我們調用context_switch進行進程內容相關的切換。
環境切換
1929 static inline void1930 context_switch(struct rq *rq, struct task_struct *prev,1931 struct task_struct *next)1932 {............... 1945 if (unlikely(!mm)) {1946 next->active_mm = oldmm;1947 atomic_inc(&oldmm->mm_count);1948 enter_lazy_tlb(oldmm, next);1949 } else1950 switch_mm(oldmm, mm, next);
switch_mm是一個體繫結構特定的函數,前換頁全域目錄以安裝一個新的地址空間。對於arm平台來說,就是設定CP15副處理器TTB寄存器為新的pgd;對於X86來說,則是設定CR3寄存器為新的pgd。有人會問,這裡切換了新的pgd,那麼代碼執行會不會不連續了? 沒關係,因為現在是核心空間,核心地址空間的頁面映射不會隨著pgd而改變。
1966 /* Here we just switch the register state and the stack. */1967 switch_to(prev, next, prev);1968 1969 barrier();1970 /*1971 * this_rq must be evaluated again because prev may have moved1972 * CPUs since it called schedule(), thus the 'rq' on its stack1973 * frame will be invalid.1974 */1975 finish_task_switch(this_rq(), prev);1976 }
進程切換的硬體上下文是共用的CPU 寄存器,進程在切換時,硬體上下文儲存在task_struct->thread欄位中,注意thread是體繫結構特定的,所以不是每個體繫結構都需要儲存寄存器到這個結構中的。swtich之後的代碼,只有在當前進程下一次被選擇執行時才會執行。
barrier確保switch_to和後面的finish_task_switch不會亂序執行。