2018-2019-1 20189206 《Linux核心原理與分析》第九周作業
#linux核心分析學習筆記 ——第八章 程序的切換和系統的一般執行過程
學習目標:重點關注程序切換的過程,程序排程的時機,作業系統的基本構成以及一般的執行過程。
程序排程的時機
因為程序的排程只發生在核心中,程序排程函式schedule()只能在核心中被呼叫,使用者程序無法呼叫。 因此,程序切換需要用到實現使用者態到核心態的切換。
除了主動讓出CPU外,程序的排程都需要在程序外進行。中斷可以實現切出程序指令流的作用,中斷處理程式是與程序無關的核心指令流。
硬中斷與軟中斷
Intel定義的中斷有以下幾種:
- 硬中斷
- CPU的兩根引腳(可遮蔽中斷和不可遮蔽中斷)。CPU在執行每條指令後檢測引腳的電平
- 一般外設都是以這種方式與CPU進行訊號傳遞
- 軟中斷
- 包括零錯誤、系統呼叫、除錯斷點等,在CPU執行指令過程中發生的各種特殊情況,稱為異常。
- 異常可以分為以下3種:
- 故障 出現問題,但是可以恢復到當前指令
- 退出 是不可恢復的嚴重故障,導致程式無法繼續執行
- 陷阱 程式主動產生的異常,在執行當前指令後發生。比如系統呼叫(int 0x80)等。
程序排程時機
schedule函式
linux核心通過schedule函式實現程序排程,schedule函式在執行佇列中找到一個程序。把CPU分配給他。每呼叫一次schedule函式就實現一次程序排程,呼叫schedule函式就是程序排程的時機。
呼叫schedule函式的兩種方式:
- 程序主動呼叫schedule() 程序呼叫阻塞的系統呼叫等待外設或主動睡眠,最終會在核心中呼叫到schedule函式。
- 鬆散呼叫,核心程式碼中可以隨時呼叫schedule函式使當前核心路徑讓出CPU;也會根據
need_resched
標記做程序排程,核心檢測到need_resched
決定是否呼叫schedule函式。
上下文
CPU在任何時刻都處於以下3種情況之一:
- 運行於使用者空間,執行使用者程序上下文。
- 運行於核心空間,處於程序上下文。
- 運行於核心空間,處於中斷上下文。
中斷上下文中的get_current
可以獲取一個指向當前程序的指標,指向被中斷程序或即將執行的程序。硬體上下文切換資訊也儲存於該程序的核心堆疊中。
程序排程的時機
程序排程的時機就是核心呼叫schedule函式的時機。當核心即將返回使用者空間時,核心會檢查need_resched標誌是否設定。如果設定,則呼叫schedule函式,將從中斷處理程式返回使用者空間的時間點作為一個固定的排程時機點。
- 使用者程序通過特定的系統呼叫主動讓出CPU
- 中斷處理程式在核心返回使用者態時進行排程
- 核心執行緒主動呼叫schedule函式讓出CPU
- 中斷處理程式主動呼叫schedule函式讓出CPU(包括以上兩點)
排程策略與演算法
- 排程策略 根據演算法的整體目標,是追求資源利用率最高還是追求相應及時或者其他目標,滿足需要的目標就是排程策略
- 排程演算法 如何實現排程策略的方法並滿足設定的目標
程序的分類
第一種分類方法
- I/O消耗型程序 需要大量檔案讀寫操作或者網路讀寫操作的程序。這一類程序的特點是CPU負載不高,大量時間都在等待讀寫資料
- 處理器消耗型程序 這種程序CPU的佔用率是100%,但沒有太多的硬體進行讀寫操作。
第二種分類
- 互動式程序 這類程序有大量的人機互動,程序不斷地處於睡眠狀態,等待使用者輸入
- 批處理程序 不需要人機互動,在後臺執行,但需要大量的系統資源
- 實時程序 對排程延遲要求較高,這些程序往往執行非常重要的操作,要求立即相應並執行
排程策略
程序的分類
- SCHED_FIFO 先進先出的實時程序,如果沒有其它更高優先順序的可執行實時程序,就可以一直使用cpu執行。對於這種程序,時間片長度是沒有意義的。
- SCHED_RR 時間片輪轉的實時程序,所具有相同優先順序(且都是當前情況下優先順序最高)的SCHED_RR以時間片輪轉的方式公平使用cpu。
- SCHED_NORMAL 時間片輪轉的普通程序,時間片用完之後變成過期程序,所有程序都成為過期程序之後,再統一把過期程序轉變為活動程序。
- 時間片輪轉的普通程序(SCHED_NORMAL)優先順序分為靜態優先順序和動態優先順序。核心用從100(最高優先順序)到139(最低優先順序)表示普通程序的靜態優先順序。新程序總是繼承父程序的靜態優先順序。
CFS排程演算法 完全公平演算法
- 基本原理
- 基於權重的動態優先順序排程演算法
- 排程順序由CPU的虛擬時間(vruntime)已使用的虛擬時間越少,程序排序就越靠前,程序再次被排程執行的概率更高。
- 排程週期(_sched_period)
某個時間週期內佇列的所有程序都會至少被排程一次
__sched_period = nr_running(程序數)*sysctl_sched_min_granularity(預設值)
理論執行時間
ideal_runtime = __sched_period * 程序權重/佇列執行總權重 每次可獲取CPU後最長可佔用時間為ideal_runtime
虛擬執行時間
每個程序擁有一個vruntime,每次需要排程時就執行佇列中擁有最小的vruntime的程序來執行,最長課執行時間為ideal_runtime
vruntime = 實際執行時間 * NICE_0_LOAD / 程序權重
= 實際執行時間 * 1024 / 程序權重
NICE_0_LOAD = 1024, 表示nice值為0的程序權重
可以看到, 程序權重越大, 運行同樣的實際時間, vruntime增長的越慢
vruntime = 程序在一個排程週期內的實際執行時間 * 1024 / 程序權重
= (排程週期 * 程序權重 / 所有程序總權重) * 1024 / 程序權重
= 排程週期 * 1024 / 所有程序總權重
程序上下文的切換
核心需要有能力掛起正在CPU中執行的程序,並恢復執行以前掛起的程序,這個行為稱為程序切換。程序切換中,掛起的CPU上執行的程序與中斷時儲存現場是不同的,中斷前後在同一個程序上下文中只是使用者態轉向核心態。
程序上下文包含了:
- 使用者地址空間:程序程式碼、資料和使用者堆疊等
- 控制資訊:程序描述符、核心堆疊
- 硬體上下文:儲存相關暫存器的值
實際程式碼中程序切換由兩個步驟組成:
- 切換頁全域性目錄(RC3)以安裝一個新的地址空間,這樣不同的虛擬地址可以經過不同的頁錶轉為不同的實體地址。
- 切換核心態堆疊和硬體上下文
核心程式碼
程序切換的函式schedule()
來選擇一個新的程序來執行,並呼叫context_switch
進行上下文的切換。在context_switch
中最重要的是巨集switch_to
進行硬體上下文的切換。
以下是context_switch
的關鍵程式碼
switch_mm
表示將下一程序的頁表地址裝入RC3,得到實體地址struct_mm
是記憶體描述符,其中儲存了程序地址空間中的所有資訊
以下是彙編的switch_to
程式碼
pushfl
pushl %ebp
注意,因為現在esp還在A的堆疊中,所以這兩個東西被儲存到A程序的核心堆疊中。
pushl %%ebp 當前堆疊的棧基址儲存
movl %%esp, %[prev_sp] 儲存當前堆疊的棧頂
movl %[next_sp],%%esp 將esp指向next程序的棧頂
從這個時候開始,CPU當前執行的程序已經是next程序了,因為esp已經指向next的核心堆疊。
上面三行彙編程式碼,實現的功能是完成核心堆疊的切換
movl $1f,%[prev_ip]
pushl %[next_ip]
jmp __switch_to
這三行程式碼使用到了next程序的核心堆疊,但實際上還是在prev核心堆疊上執行。
1:
popl %%ebp
popf1
這三行程式碼才是next程序開始執行的真正位置。
如果之前B也被switch_to出去過,那麼[next_ip]裡存的就是下面這個1f的標號,但如果程序B剛剛被建立,之前沒有被switch_to出去過,那麼[next_ip]裡存的將是ret_ftom_fork。
這部分程式碼是核心程式碼,它們跟使用者程式碼不在同一個程式碼段,所有程序在核心態共用這一段核心程式碼。這裡涉及到的所有堆疊都是核心堆疊,而不涉及使用者堆疊。
詳細參考部落格switch_to執行詳解
程序排程的原始碼分析
首先還是凍結MenuOS,隨後開啟除錯,分別設定斷點。
gdb
file linux-3.18.6/vmlinux
target remote:1234
b schedule
b context_switch
b switch_to
b pick_next_task
開始執行後,可以看到,程式碼停在了schedule
處
之後可以看到context_switch
和pick_next_task
linux系統架構
linux作業系統的整體架構如圖所示:
從下向上每一層分別是 底層硬體 --> 核心實現 --> 系統呼叫介面 -->基礎軟體(shell 共享庫等) --> 使用者級的應用程式
核心向上為使用者系統呼叫提供介面,向下呼叫硬體服務介面。