2018-2019-1 20189229 《Linux核心原理與分析》第九周作業
阿新 • • 發佈:2018-12-09
教材內容總結
程序排程的時機
- 中斷處理過程(包括時鐘中斷、I/O中斷、系統呼叫和異常)中,直接呼叫schedule(),或者返回使用者態時根據need_resched標記呼叫schedule();
- 核心執行緒可以直接呼叫schedule()進行程序切換,也可以在中斷處理過程中進行排程,也就是說核心執行緒作為一類的特殊的程序可以主動排程,也可以被動排程;
- 使用者態程序無法實現主動排程,僅能通過陷入核心態後的某個時機點進行排程,即在中斷處理過程中進行排程。
總結:
- 使用者態程序只能被動排程
- 核心執行緒是隻有核心態沒有使用者態的特殊程序
核心執行緒既可以主動排程也可以被動排程
程序排程策略與演算法
- 排程策略:考慮到演算法的整體目標,是追求資源利用率最高,還是追求響應最及時,或其他特定目標。
排程演算法:考慮如何實現排程策略並滿足設定的目標
程序的分類
- I/O消耗型程序:需要大量檔案讀寫操作,cpu負載不高,大量時間在等待讀寫資料。
- 處理器消耗型程序:cpu佔用率100%,但沒有太多硬體進行讀寫操作。
- 互動式程序:大量人機互動,對系統響應時間要求較高。
- 批處理程序:在後臺執行,佔用大量系統資源。
實時程序:對排程延遲要求最高。
排程策略
- SCHED_FIFO 先進先出的實時程序,如果沒有其它更高優先順序的可執行實時程序,就可以一直使用cpu執行。
- SCHED_RR 時間片輪轉的實時程序。
SCHED_NORMAL 時間片輪轉的普通程序。
程序的切換
- 為了控制程序的執行,核心必須有能力掛起正在CPU上執行的程序,並恢復以前掛起的某個程序的執行,這叫做程序切換、任務切換、上下文切換;
- 掛起正在CPU上執行的程序,與中斷時儲存現場是不同的,中斷前後是在同一個程序上下文中,只是由使用者態轉向核心態執行;
- 程序上下文包含了程序執行需要的所有資訊
- 使用者地址空間:包括程式程式碼,資料,使用者堆疊等
- 控制資訊:程序描述符,核心堆疊等
- 硬體上下文(注意中斷也要儲存硬體上下文只是儲存的方法不同)
schedule()函式選擇一個新的程序來執行,並呼叫context_switch進行上下文的切換,這個巨集呼叫switch_to來進行關鍵上下文切換
next = pick_next_task(rq, prev);//程序排程演算法都封裝這個函式內部
context_switch(rq, prev, next);//程序上下文切換
switch_to利用了prev和next兩個引數:prev指向當前程序,next指向被排程的程序
linux系統架構與執行過程
系統架構
如圖:
ls執行過程
如圖:
實驗八 理解程序排程時機跟蹤分析程序排程與程序切換的過程
配置MenuOS系統
使用gdb跟蹤分析schedule()函式,在schedule、context_switch、switch_to、pick_next_task處設定斷點,由於switch_to為巨集定義,所以實際為在函式__switch_to處設定斷點,如下圖:
執行程式,程式分別停在schedule函式、pick_next_task函式斷點、context_switch處,檢視程式碼:
context_switch中單步執行,呼叫switch_to
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" /* 儲存當前程序flags */
43 "pushl %%ebp\n\t" /* 當前程序堆疊基址壓棧*/
44 "movl %%esp,%[prev_sp]\n\t" /*儲存ESP,將當前堆疊棧頂儲存起來*/
45 "movl %[next_sp],%%esp\n\t" /*更新ESP,將下一棧頂儲存到ESP中*/
//完成核心堆疊的切換
46 "movl $1f,%[prev_ip]\n\t" /*儲存當前程序EIP*/
47 "pushl %[next_ip]\n\t" /*將next程序起點壓入堆疊,即next程序的棧頂為起點*/
48
//完成EIP的切換
__switch_canary
//next_ip一般是$1f,對於新建立的子程序時ret_from_fork
49 "jmp __switch_to\n" /*prev程序中,設定next程序堆疊*/
//jmp不同於call是通過暫存器傳遞引數
50 "1:\t" //next程序開始執行
51 "popl %%ebp\n\t"
52 "popfl\n"
53
54 /*輸出變數定義*/
55 : [prev_sp] "=m" (prev->thread.sp), //[prev_sp]定義核心堆疊棧頂
56 [prev_ip] "=m" (prev->thread.ip), //[prev_ip]當前程序EIP
57 "=a" (last),
58
59 /* 要破壞的暫存器: */
60 "=b" (ebx), "=c" (ecx), "=d" (edx),
61 "=S" (esi), "=D" (edi)
62
63 __switch_canary_oparam
64
65 /* 輸入變數: */
66 : [next_sp] "m" (next->thread.sp), //[next_sp]下一個核心堆疊棧頂
67 [next_ip] "m" (next->thread.ip),
68 //[next_ip]下一個程序執行起點,,一般是$1f,對於新建立的子程序是ret_from_fork
69 /* regparm parameters for __switch_to(): */
70 [prev] "a" (prev),
71 [next] "d" (next)
72
73 __switch_canary_iparam
74
75 : /* 重新載入段暫存器 */
76 "memory");
77} while (0)