網易公開課《Linux核心分析》學習心得-理解程序排程時機跟蹤分析程序排程與程序切換的過程
首先在核心程式碼中搜索schedule,發現以下結果
在core.c檔案中是
實驗
設定斷點
跟蹤schedule的程序
可以看到
struct task_struct *tsk = current;
sched_submit_work(tsk);
將一個task_struct的指標賦值給了當前程序,之後呼叫了sched_submit_work。
繼續執行,進入後面的__schedule()函式
可以看到切換程序的大部分程式碼都在__schedule()函式中
執行完畢
分析
程序排程的時機
• 中斷處理過程中,直接呼叫schedule(),或者返回使用者態時根據need_resched標記呼叫schedule()。
• 核心執行緒可以直接呼叫schedule()進行程序切換,也可以在中斷處理過程中進行排程,也就是說核心執行緒作為一類的特殊的程序可以主動排程,也可以被動排程。
• 使用者態程序無法實現主動排程,僅能通過陷入核心態後的某個時機點進行排程,即在中斷處理過程中進行排程。
switch_to的分析
switch_to巨集有三個引數,它們是prev,next和last。prev表示替換程序,next表示新程序描述符放入賦值在記憶體中的位置。最後一個引數的輸入引數,它表示巨集把程序C的描述符地址寫在記憶體的位置。
在程序切換之前,巨集吧第一個輸入引數prev表示的變數的內容存入CPU的暫存器,在完成程序切換,A已經恢復執行,巨集把CPU的eax的暫存器的內容寫入由第三個輸入引數(last所示的A在記憶體中的位置)。因為CPU的eax暫存器不會在切換點發生變化,所以C得描述符地址也存在記憶體的位置。在schedule()執行過程中。引數last指向A的區域性變數prev,所以prev被C的地址覆蓋。
__schedule()的分析
static void __sched __schedule(void)
2771{
// 建立一些區域性變數
2772 struct task_struct *prev, *next;
2773 unsigned long *switch_count;
2774 struct rq *rq;
2775 int cpu;
2776
// 關閉程序搶佔
2777need_resched:
2778 preempt_disable();
// 初始化一些變數
2779 cpu = smp_processor_id();
2780 rq = cpu_rq(cpu);
2781 rcu_note_context_switch(cpu);
2782 prev = rq->curr;
// 選擇一個高優先順序的任務加入佇列
2824 next = pick_next_task(rq, prev);
//並把這個任務的需要加入的標誌need_resched去掉
2825 clear_tsk_need_resched(prev);
2826 clear_preempt_need_resched();
2827 rq->skip_clock_update = 0;
2828
//完成整個排程活動
2829 if (likely(prev != next)) {
2830 rq->nr_switches++;
2831 rq->curr = next;
2832 ++*switch_count;
2833
2834 context_switch(rq, prev, next); /* unlocks the rq */
2835 /*
2836 * The context switch have flipped the stack from under us
2837 * and restored the local variables which were saved when
2838 * this task called schedule() in the past. prev == current
2839 * is still correct, but it can be moved to another cpu/rq.
2840 */
2841 cpu = smp_processor_id();
2842 rq = cpu_rq(cpu);
2843 } else
2844 raw_spin_unlock_irq(&rq->lock);
2845
2846 post_schedule(rq);
2847
2848 sched_preempt_enable_no_resched();
2849 if (need_resched())
2850 goto need_resched;
2851}
2852
2853static inline void sched_submit_work(struct task_struct *tsk)
2854{
2855 if (!tsk->state || tsk_is_pi_blocked(tsk))
2856 return;
2857 /*
2858 * If we are going to sleep and we have plugged IO queued,
2859 * make sure to submit it to avoid deadlocks.
2860 */
2861 if (blk_needs_flush_plug(tsk))
2862 blk_schedule_flush_plug(tsk);
2863}
總結
Linux系統的一般執行過程是首先有正在執行的使用者態程序X然後在停止前發生中斷,使用SAVE_ALL儲存現場。並且中斷處理過程中或中斷返回前呼叫了schedule(),其中的switch_to做了關鍵的程序上下文切換。最後,標號1之後開始執行使用者態程序Y(這裡Y曾經通過以上步驟被切換出去過因此可以從標號1繼續執行)。
完成排程恢復現場,之後繼續執行使用者態程序Y。