進程的切換和系統的一般執行過程
一、進程切換的關鍵代碼switch_to分析
(一)進程調度與進程調度的時機分析
1、不同類型的進程有不同的調度需求
第一種分類:
(1)I/O-bound:頻繁進行I/O,花費很多時間等待I/O操作的完成。
(2)CPU-bound:計算密集型,需要大量CPU時間進行計算。
第二種分類:
(1)批處理進程:不必交互、很快響應。
(2)實時進程:要求響應時間短。
(3)交互式進程(shell)。
2、調度策略:是一組規則,它們決定什麼時候以怎樣的方式選擇一個新進程運行。
3、linux進程調度是基於分時和優先級的。
4、Linux的進程根據優先級排隊。
5、Linux中進程的優先級是動態的。
6、內核中的調度算法相關代碼使用了類似OOD中的策略模式。
7、進程調度的時機:
(1)中斷處理過程(包括時鐘中斷、I/O中斷、系統調用和異常)中,直接調用schedule(),或者返回用戶態時根據need_resched標記調用schedule();
(2)內核線程(只有內核態沒有用戶態的特殊進程)可以直接調用schedule()進行進程切換,也可以在中斷處理過程中進行調度,也就是說內核線程作為一類的特殊的進程可以主動調度,也可以被動調度;
(3)用戶態進程無法實現主動調度,只能被動調度,僅能通過陷入內核態後的某個時機點進行調度,即在中斷處理過程中進行調度。
(二)進程上下文切換相關代碼分析
1、進程的切換
(1)為了控制進程的執行,內核必須有能力掛起正在CPU上執行的進程,並恢復以前掛起的某個進程的執行,這叫做進程切換、任務切換、上下文切換;
(2)掛起正在CPU上執行的進程,與中斷時保存現場是不同的,中斷前後是在同一個進程上下文中,只是由用戶態轉向內核態執行;
(3)進程上下文包含了進程執行需要的所有信息
1)用戶地址空間:包括程序代碼,數據,用戶堆棧等
2)控制信息:進程描述符,內核堆棧等
3)硬件上下文(注意中斷也要保存硬件上下文只是保存的方法不同)
2、schedule()函數選擇一個新的進程來運行,並調用context_switch進行上下文的切換,這個宏調用switch_to來進行關鍵上下文切換
(1)next = pick_ next_task(rq, prev);//進程調度算法都封裝這個函數內部
(2)context_switch(rq, prev, next);//進程上下文切換
(3)switch_to利用了prev和next兩個參數:prev指向當前進程,next指向被調度的進程
3、schedule()中:
4、context_switch中:
5、switch_to中:
31#define switch_to(prev, next, last)
32do {
33 /*
34 * Context-switching clobbers all registers, so we clobber
35 * them explicitly, via unused output variables.
36 * (EAX and EBP is not listed because EBP is saved/restored
37 * explicitly for wchan access and EAX is the return value of
38 * __switch_to())
39 */
40 unsigned long ebx, ecx, edx, esi, edi;
41
42 asm volatile("pushfl\n\t" /* save flags */ //保存當前進程的flags
43 "pushl %%ebp\n\t" /* save EBP */ //把當前進程的堆棧基址壓棧
44 "movl %%esp,%[prev_sp]\n\t" /* save ESP */ //把當前的棧頂保存到prev->thread.sp
45 "movl %[next_sp],%%esp\n\t" /* restore ESP */ //把下一個進程的棧頂保存到esp中,這兩句完成了內核堆棧的切換
46 "movl $1f,%[prev_ip]\n\t" /* save EIP */ //保存當前進程的EIP,可以從這恢復
47 "pushl %[next_ip]\n\t" /* restore EIP */ //把下一個進程的起點位置壓到堆棧,就是next進程的棧頂。next_ip一般是$1f,對於新創建的子進程是ret_from_fork
//一般用return直接把next_ip pop出來
48 __switch_canary
49 "jmp __switch_to\n" /* regparm call */ //jmp通過寄存器傳遞參數,即後面的a,d。 函數__switch_to也有return把next_ip pop出來
50 "1:\t" //認為從這開始執行next進程(EIP角度),第一條指令是next_ip這個起點,但前面已經完成內核堆棧的切換,早就是next進程的內核堆棧(算prev進程,比較模糊)
51 "popl %%ebp\n\t" /* restore EBP */ //next進程曾經是prev進程,壓棧過ebp
52 "popfl\n" /* restore flags */
53
54 /* output parameters */
55 : [prev_sp] "=m" (prev->thread.sp), //當前進程的,在中斷內部,在內核態,sp是內核堆棧的棧頂
56 [prev_ip] "=m" (prev->thread.ip), //當前進程的EIP
57 "=a" (last),
58
59 /* clobbered output registers: */
60 "=b" (ebx), "=c" (ecx), "=d" (edx),
61 "=S" (esi), "=D" (edi)
62
63 __switch_canary_oparam
64
65 /* input parameters: */
66 : [next_sp] "m" (next->thread.sp), //下一個進程的內核堆棧的棧頂
67 [next_ip] "m" (next->thread.ip), //下一個進程的執行起點
68
69 /* regparm parameters for __switch_to(): */
70 [prev] "a" (prev), //寄存器的傳遞
71 [next] "d" (next)
72
73 __switch_canary_iparam
74
75 : /* reloaded segment registers */
76 "memory");
77} while (0)
二、Linux系統的一般執行過程
(一)Linux系統的一般執行過程分析
1、Linux系統的一般執行過程
最一般的情況:正在運行的用戶態進程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
(二)Linux系統執行過程中的幾個特殊情況
1、幾種特殊情況
(1)通過中斷處理過程中的調度時機,用戶態進程與內核線程之間互相切換和內核線程之間互相切換,與最一般的情況非常類似,只是內核線程運行過程中發生中斷沒有進程用戶態和內核態的轉換;
(2)內核線程主動調用schedule(),只有進程上下文的切換,沒有發生中斷上下文的切換,與最一般的情況略簡略;
(3)創建子進程的系統調用在子進程中的執行起點及返回用戶態,如fork;
(4)加載一個新的可執行程序後返回到用戶態的情況,如execve;
三、Linux系統架構和執行過程概覽
(一)Linux操作系統架構概覽
(二)最簡單也是最復雜的操作--執行ls命令
(三)從CPU和內存的角度看Linux系統的執行
1、CPU執行指令的角度:
2、內存的角度:
四、實驗
1、環境搭建
cd LinuxKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu
mv test_exec.c test.c
make rootfs
2、gdb調試
Qemu –kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S
gdb
file ../linux-3.18.6/vmlinux
target remote:1234
3、設置斷點
b schedule
b pick_next_task
b context_switch
b switch_to
五、總結
1、Linux進程調度是基於分時和優先級的。
2、Linux中,內核線程是只有內核態沒有用戶態的特殊進程。
3、內核可以看作各種中斷處理過程和內核線程的集合。
4、Linux系統的一般執行過程 可以抽象成正在運行的用戶態進程X切換到運行用戶態進程Y的過程。
5、Linux中,內核線程可以主動調度,主動調度時不需要中斷上下文的切換。
6、Linux內核調用schedule()函數進行調度,並調用context_switch進行上下文的切換,這個宏調用switch_to來進行關鍵上下文切換。