做者:劉浩晨
【原創做品轉載請註明出處】 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000linux
(1)爲何有多種進程調度算法——每一個進程對CPU、I/O等資源需求不同。算法
第一種分類:shell
I/O密集型(I/O-bound):頻繁的進行I/O,一般會花費不少時間等待I/O操做的完成 CPU密集型(CPU-bound):計算密集型,須要大量的CPU時間進行運算
第二種分類:架構
批處理進程:沒必要與用戶交互,一般在後臺運行;沒必要很快響應典型的批處理程序:編譯程序,科學計算 實時進程:有實時需求,不該被低優先級的進程阻塞;響應時間要短、要穩定;典型的實時進程:視頻、音配、機械控制。 交互式進程:須要常常與用戶交互,所以要花不少時間等待用戶輸入操做;響應時間要快,平均延遲低於50~150ms;典型的交互式程序: shell,文本編輯程序,圖形應用程序等
(2)調度策略:一組規則,決定何時以怎樣的方式選擇一個新進程運行。函數
(3)進程調度(schedule()函數實現)的時機:學習
注意:用戶態進程只能被動調度,內核線程是隻有內核態沒有用戶態的特殊進程。操作系統
爲了控制進程的執行,內核必須有能力掛起正在CPU上執行的進程,並恢復之前掛起的某個進程的執行,這叫作進程切換、任務切換、上下文切換;線程
掛起正在CPU上執行的進程,與中斷時保存現場是不一樣的,中斷先後是在同一個進程上下文中,只是由用戶態轉向內核態執行;3d
進程上下文包含了進程執行須要的全部信息調試
用戶地址空間:包括程序代碼,數據,用戶堆棧等 控制信息:進程描述符,內核堆棧等 硬件上下文(注意中斷也要保存硬件上下文只是保存的方法不一樣)
schedule()函數選擇一個新的進程來運行,並調用context_switch進行上下文的切換,這個宏調用switch_to來進行關鍵上下文切換
next = pick_next_task(rq, prev); //進程調度算法都封裝這個函數內部 context_switch(rq, prev, next); //進程上下文切換 switch_to利用了prev和next兩個參數:prev指向當前進程,next指向被調度的進程。
switch_to彙編代碼分析:
#define switch_to(prev, next, last) do { unsigned long ebx, ecx, edx, esi, edi; asm volatile("pushfl\n\t" "pushl %%ebp\n\t" /* 把當前進程的堆棧基址壓棧 */ "movl %%esp,%[prev_sp]\n\t" /* 把當前進程的棧頂保存到thread.sp */ "movl %[next_sp],%%esp\n\t" /* 這兩行完成內核堆棧切換,把下一進程棧頂放入esp */ "movl $1f,%[prev_ip]\n\t" /* 保存當前進程的EIP,next_ip通常是$1f,對於新建立的子進程是ret_from_fork */ "pushl %[next_ip]\n\t" /* 把下一個進程的起點EIP壓入堆棧 */ __switch_canary "jmp __switch_to\n" /* 經過寄存器傳遞參數,返回1f */ "1:\t" /* 認爲next進程開始執行,執行下一進程的第一條指令 */ "popl %%ebp\n\t" /* restore EBP */ "popfl\n" /* restore flags */ /* output parameters 由於處於中斷上下文,在內核中 prev_sp是內核堆棧棧頂 */ : [prev_sp] "=m" (prev->thread.sp),//內核堆棧的棧頂 [prev_ip] "=m" (prev->thread.ip), //[prev_ip]是標號 "=a" (last), /* clobbered output registers: */ "=b" (ebx), "=c" (ecx), "=d" (edx), "=S" (esi), "=D" (edi) __switch_canary_oparam : [next_sp] "m" (next->thread.sp), //下一個進程的內核堆棧的棧頂 [next_ip] "m" (next->thread.ip), //下一個進程執行的起點,通常是$1f,對於新建立的子進程是ret_from_fork /* regparm parameters for __switch_to(): */ [prev] "a" (prev), [next] "d" (next) __switch_canary_iparam : /* reloaded segment registers */ "memory"); } while (0)
最通常的狀況:正在運行的用戶態進程X切換到運行用戶態進程Y的過程
(1)正在運行的用戶態進程X
(2)發生中斷——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a
specific ISR) and ss:esp(point to kernel stack).
(3)SAVE_ALL //保存現場
(4)中斷處理過程當中或中斷返回前調用了schedule(),其中的switch_to作了關鍵的進程上下文切換
(5)標號1以後開始運行用戶態進程Y(這裏Y曾經經過以上步驟被切換出去過所以能夠從標號1繼續執行)
(6)restore_all //恢復現場
(7)iret - pop cs:eip/ss:esp/eflags from kernel stack
(8)繼續運行用戶態進程Y
(1)操做系統——任何計算機系統都包含一個基本的程序集合。
(2)操做系統有兩個目的:
1.根據之前所學步驟,配置好試驗環境:
2.進入gdb調試,設置斷點:
3.執行c,看到此時被凍結的內核運行到schedule處中斷:
4.單步執行到__schedule處,進入函數繼續單步執行到pick_next_task():
5.繼續執行c,到context_switch斷點處。list查看詳細代碼:
6.繼續單步執行可看到prepare_task_switch(),這個函數爲進程上下文切換作準備工做:
7.繼續單步執行可看到switch_to(),完成跟蹤:
根據這周所學課本上內容,本週主要理解Linux中進程調度與進程切換過程。進程調度是按必定的策略動態地把處理機分配給處於就緒隊列中的某一個進程,以使之執行。而進程切換是從正在運行的進程中收回處理器,而後再使待運行進程來佔用處理器。實質上就是把進程存放在處理器的寄存器中的中間數據找個地方存起來,從而把處理器的寄存器騰出來讓其餘進程使用。對於具體過程實驗中也有跟蹤調試,可供參考。