進程的切換和系統的一般執行過程
一、進程調度的時機
-
中斷處理過程(包括時鐘中斷、I/O中斷、系統調用和異常)中,直接調用schedule(),或者返回用戶態時根據need_resched標記調用schedule();
-
內核線程可以直接調用schedule()進行進程切換,也可以在中斷處理過程中進行調度,也就是說內核線程作為一類的特殊的進程可以主動調度,也可以被動調度;
-
用戶態進程無法實現主動調度,僅能通過陷入內核態后的某個時機點進行調度,即在中斷處理過程中進行調度。
?
二、進程的切換
-
為了控制進程的執行,內核必須有能力掛起正在CPU上執行的進程,并恢復以前掛起的某個進程的執行,這叫做進程切換、任務切換、上下文切換;
-
掛起正在CPU上執行的進程,與中斷時保存現場是不同的,中斷前后是在同一個進程上下文中,只是由用戶態轉向內核態執行;
-
進程上下文包含了進程執行需要的所有信息
-
用戶地址空間:包括程序代碼,數據,用戶堆棧等
-
控制信息:進程描述符,內核堆棧等
-
硬件上下文(注意中斷也要保存硬件上下文只是保存的方法不同)
-
-
schedule()函數選擇一個新的進程來運行,并調用context_switch進行上下文的切換,這個宏調用switch_to來進行關鍵上下文切換
-
next = pick_next_task(rq, prev);//進程調度算法都封裝這個函數內部
-
context_switch(rq, prev, next);//進程上下文切換
-
switch_to利用了prev和next兩個參數:prev指向當前進程,next指向被調度的進程
-
?
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" /* 保存當前進程的標志位 */ \
43 "pushl %%ebp\n\t" /* 保存當前進程的堆棧基址EBP */ \
44 "movl %%esp,%[prev_sp]\n\t" /* 保存當前棧頂ESP */ \
45 "movl %[next_sp],%%esp\n\t" /* 把下一個進程的棧頂放到esp寄存器中,完成了內核堆棧的切換,從此往下壓棧都是在next進程的內核堆棧中。 */ \
46 "movl $1f,%[prev_ip]\n\t" /* 保存當前進程的EIP */ \
47 "pushl %[next_ip]\n\t" /* 把下一個進程的起點EIP壓入堆棧 */ \
48 __switch_canary \
49 "jmp __switch_to\n" /* 因為是函數所以是jmp,通過寄存器傳遞參數,寄存器是prev-a,next-d,當函數執行結束ret時因為沒有壓棧當前eip,所以需要使用之前壓棧的eip,就是pop出next_ip。 */
50 "1:\t" /* 認為next進程開始執行。 */
51 "popl %%ebp\n\t" /* restore EBP */ \
52 "popfl\n" /* restore flags */ \
53 /* output parameters 因為處于中斷上下文,在內核中 prev_sp是內核堆棧棧頂 prev_ip是當前進程的eip */ \
54 /* output parameters */ \
55 : [prev_sp] "=m" (prev->thread.sp), \
56 [prev_ip] "=m" (prev->thread.ip), \
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系統的一般執行過程
?
1、最一般的情況:正在運行的用戶態進程X切換到運行用戶態進程Y的過程?
-
正在運行的用戶態進程X
-
發生中斷——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).
-
SAVE_ALL //保存現場
-
中斷處理過程中或中斷返回前調用了schedule(),其中的switch_to做了關鍵的進程上下文切換
-
標號1之后開始運行用戶態進程Y(這里Y曾經通過以上步驟被切換出去過因此可以從標號1繼續執行)
-
restore_all //恢復現場
-
iret - pop cs:eip/ss:esp/eflags from kernel stack
-
繼續運行用戶態進程Y
?
2、幾種特殊情況
- 通過中斷處理過程中的調度時機,用戶態進程與內核線程之間互相切換和內核線程之間互相切換,與最一般的情況非常類似,只是內核線程運行過程中發生中斷沒有進程用戶態和內核態的轉換;
- 內核線程主動調用schedule(),只有進程上下文的切換,沒有發生中斷上下文的切換,與最一般的情況略簡略;
- 創建子進程的系統調用在子進程中的執行起點及返回用戶態,如fork;
- 加載一個新的可執行程序后返回到用戶態的情況,如execve;
四、Linux操作系統架構和系統執行過程概覽
1、操作系統基本概念
任何計算機系統都包含一個基本的程序集合,稱為操作系統。
- 內核(進程管理,進程調度,進程間通訊機制,內存管理,中斷異常處理,文件系統,I/O 系統,網絡部分)
- 其他程序(例如函數庫、shell程序、系統程序 等等)
操作系統的目的
- 與硬件交互,管理所有的硬件資源
- 為用戶程序(應用程序)提供一個良好的執行環境
2、Linux系統架構
- 硬件管理
- 內核實現
- 系統調用
- 基礎軟件(shell、lib)
- 用戶程序
3、最簡單也是最復雜的操作ls
?
4、站在CPU和內存角度看Linux系統的執行
?
5、從內存的角度
五、Linux的系統結構
六、用gdb對schedule進行
1、環境搭建:
?
2、設置斷點
3、list查看斷點所在代碼段,發現schedule()被調用
4、c之后按n單步執行,直到遇到__schedule函數
?
5、繼續執行,直到發現context_switch函數
6、繼續單步執行,直到發現context_switch函數
?
7、設置斷點后,進入其內部查看