1.1硬中斷和軟中斷
中斷是指在計算機執行期間,系統內發生任何非尋常的或非預期的急需處理事件,使得CPU暫時中斷當前正在執行的程序而轉去執行相應的時間處理程序。待處理完畢後又返回原來被中斷處繼續執行或調度新的進程執行的過程。
引發中斷的事件稱爲中斷源。中斷源向CPU提出處理的請求稱爲中斷請求。發生中斷時被打斷程序的暫停點稱爲斷點。CPU暫停現行程序而轉爲響應中斷請求的過程稱爲中斷響應。處理中斷源的程序稱爲中斷處理程序。CPU執行有關的中斷處理程序稱爲中斷處理。而返回斷點的過程稱爲中斷返回。中斷的實現由軟件和硬件綜合完成,硬件部分叫作硬件裝置,軟件部分稱爲軟件處理程序。linux
上下文
通常來講,CPU在任什麼時候刻都處於如下3種狀況之一。算法
中斷處理程序主動調用schedule函數讓出CPU,涵蓋第一和第二種狀況。架構
調度算法就是從就緒隊列中選一個進程。調度策略是尋找知足需求的方法,而調度算法是如何實現這個調度策略。
2.1 進程的分類
按CPU佔用率分類:ide
實時進程。實時進程對調度延遲的要求最高,這些進程執行很是重要的操做,要求當即執行響應並執行。
當前Linux系統的解決方案是,對於實時進程,linux採用FIFO(先進先出)或者Round Robin(時間片輪轉)的調度策略。對其它進程,則採用CFS調度器,核心是「徹底公平」。函數
2.2 調度策略
Linux系統中經常使用的幾種調度策略爲SCHED_NORMAL、SCHED_FIFO、SCHED_RR。
其中SCHED_NORMAL是用於普通進程的調度類,而SCHED_FIFO和SCHED_RR是用於實時進程的調度類,優先級高於SCHED_NORMAL。內核根據進程的優先級來區分普通進程與實時進程,Linux內核進程優先級爲0~139,數值越高,優先級越低,0爲最高優先級。實時進程的優先級取值爲0~99,普通進程只具備nice值,nice值映射到優先級爲100~139。學習
2.3 CFS調度算法
CFS即爲徹底公平調度算法,其基本原理是基於權重的動態優先級調度算法。每一個進程使用CPU的順序進程由已使用的CPU虛擬時間(vruntime)決定,已使用的虛擬時間越少,進程排序就越靠前,進程再次被調度執行的機率也就越高。每一個進程每次佔用CPU後可以執行的時間(ideal_runtime)由進程的權重決定,而且保證在某個時間週期(_sched_period)內運行隊列的因此進程都可以至少被調度執行一次。atom
3.1 進程執行環境的切換
爲了控制進程的執行,內核必須有能力掛起正在CPU中運行的進程,並恢復執行之前掛起的某個進程。這種行爲被稱爲進程切換,任務切換或進程上下文切換。進程上下文包含了進程執行須要的全部信息。idea
硬件上下文,相關寄存器的值。
在實際的代碼中,每一個進程切換基本由兩個步驟組成:操作系統
切換內核態堆棧和硬件上下文,由於硬件上下文提供了內核執行新進程所須要的全部信息。
線程
1.從新克隆一個menu,而後從新編譯內核。
2.打開調試模式,另打開一個窗口進行gdb遠程調試,配置gdb遠程調試並設置斷點。
context_switch關鍵代碼部分
static inline void context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next) { ... arch_start_context_switch(prev); if (unlikely(!mm)) { //若是被切換進來的進程的mm爲空切換,內核線程mm爲空 next->active_mm = oldmm; //將共享切換出去的進程的active_mm atomic_inc(&oldmm->mm_count); //有一個進程共享,全部引用計數加一 enter_lazy_tlb(oldmm, next); //將per cpu變量cpu_tlbstate狀態設爲LAZY } else //普通mm不爲空,則調用switch_mm切換地址空間 switch_mm(oldmm, mm, next); ... //這裏切換寄存器狀態和棧 switch_to(prev, next, prev);
switch_to關鍵代碼部分
#define switch_to(prev, next, last) do { /* * Context-switching clobbers all registers, so we clobber * them explicitly, via unused output variables. * (EAX and EBP is not listed because EBP is saved/restored * explicitly for wchan access and EAX is the return value of * __switch_to()) */ unsigned long ebx, ecx, edx, esi, edi; asm volatile( "pushfl\n\t" //保存當前進程flags "pushl %%ebp\n\t" //當前進程堆棧基址壓棧 "movl %%esp,%[prev_sp]\n\t" //保存ESP,將當前堆棧棧頂保存起來 "movl %[next_sp],%%esp\n\t" //更新ESP,將下一棧頂保存到ESP中 // 完成內核堆棧的切換 "movl $1f,%[prev_ip]\n\t" //保存當前進程的EIP "pushl %[next_ip]\n\t" //將next進程起點壓入堆棧,即next進程的棧頂爲起點 __switch_canary //next_ip通常爲$1f,對於新建立的子進程是ret_from_fork "jmp __switch_to\n" //prve進程中,設置next進程堆棧,jmp與call不一樣,是經過寄存器傳遞參數(call經過堆棧),因此ret時彈出的是以前壓入棧頂的next進程起點 //完成EIP的切換 "1:\t" //next進程開始執行 "popl %%ebp\n\t" //restore EBP "popfl\n" //restore flags //輸出量 : [prev_sp] "=m" (prev->thread.sp), //保存當前進程的esp [prev_ip] "=m" (prev->thread.ip), //保存當前進倉的eip "=a" (last), //要破壞的寄存器 "=b" (ebx), "=c" (ecx), "=d" (edx), "=S" (esi), "=D" (edi) __switch_canary_oparam //輸入量 : [next_sp] "m" (next->thread.sp), //next進程的內核堆棧棧頂地址,即esp [next_ip] "m" (next->thread.ip), //next進程的eip // regparm parameters for __switch_to(): [prev] "a" (prev), [next] "d" (next) __switch_canary_iparam : //從新加載段寄存器 "memory"); } while (0)
經過本次實驗,我學習到了進程調度的時機和進程切換進行,分析進程的調度時機,調度策略和算法,並跟蹤schedule,pick_next_task和context_switch等函數。 我瞭解到進程調度是爲了合理分配計算機資源,並讓每一個進程都得到適當的執行機會。 而在中斷處理過程(包括時鐘中斷、I/O中斷、系統調用和異常)中,系統直接調用schedule(),或者返回用戶態時根據need_resched標記調用schedule()。 用戶態進程沒法實現主動調度,僅能經過陷入內核態後的某個時機點進行調度,即在中斷處理過程當中進行調度。