接著上篇CFS學習總結,下面對很久前看的一些Linux核心的主要模組寫了個簡單的總結,本總結個人針對某個模組的回憶,並不針對源碼,主要目的是一方面加強自己的記憶,另一方面提煉出一些東西與大家分享(有時候代碼看多了,對某個模組反而沒有了一個整體的概念)。一、Linux 啟動過程分析
Linux的啟動過程可以分為四個階段:系統上電階段, BIOS階段,引導程式階段,Linux核心階段。
(1)系統上電階段
對於x86體繫結構來說,CPU上電後,eip = 0xffff fff0, CPU執行eip指向的指令,通常這是條跳轉指令,即跳轉到BIOS的入口。
(2)BIOS階段
BIOS主要完成兩個功能:加電自檢,即POST(Power On Self Test)和載入核心引導程式,這裡特指MBR(Master Boot Record主引導記錄區512位元組),POST過程主要完成系統硬體的檢測,比如記憶體檢測,系統匯流排檢測等。
(3)引導程式階段
這裡說的核心引導程式包括兩部分:MBR中的主引導程式和使用中的磁碟分割中的次引導程式。MBR中的主引導程式包含446位元組的程式和64位元組的磁碟分割表,主引導程式會掃描磁碟分割表,尋找活動的磁碟分割,將使用中的磁碟分割中的次引導程式載入到記憶體執行,次引導程式負責完成載入核心的任務。
(4)Linux核心階段
核心被載入到記憶體後,首先為核心的運行做前期的準備,主要包括以下幾個主要步驟:
- 設定c程式啟動並執行堆棧
- 清空BSS段
- 設定中斷描述符表(IDT)
- 設定通用描述元表(GDT)
- 置位CR0的PE位,開啟保護模式(保護模式開啟後必須緊跟一條長跳轉指令,用來清空指令預取隊列,並重新設定%CS段寄存器)
- 調用核心解壓函數 – decompress_kernel(),並跳轉到解壓核心的入口
- 設定CR0的PG位,開啟分頁機制
基本的思想就是在記憶體中找一塊安全的記憶體區,通常在核心資料區段或堆結束的地方,在這段記憶體中將物理地址0開始的一段記憶體同時映射虛擬位址空間的0xC000 0000和0x0000 0000 開始的地址處。
二、Linux系統調用執行流程
Linux系統調用的執行流程可以描述為:
(1) 應用程式調用C庫中的函數
(2) C庫函數的實現為觸發int 0x80系統調用中斷,系統調用號在%eax中
(3) 作業系統拿中斷向量號0x80查詢中斷向量表,執行對應的中斷處理函數
(4) 中斷處理函數拿系統調用號%eax查詢系統調用表,執行相應的系統調用
(5) 系統調用執行完成後返回應用程式
三、Linux中斷分類和中斷處理過程
中斷的初始化主要包括:中斷向量表的初始化,中斷數組的初始化,中斷處理函數的註冊等。
中斷產生和處理過程中硬體執行的操作:
裝置通過插斷要求線(IRQ線)向中斷控制器發送一個中斷訊號,中斷控制器接收到中斷訊號後,一方面經過解碼電路將中斷訊號轉化為中斷號,儲存在指定的IO ports中,另一方面通過與CPU的INTR管腳直接相連的INT線向CPU發送中斷訊號;
CPU收到中斷訊號後,等執行完當前指令,在執行下一條指令前,去檢查有無中斷處於Pending狀態,如果有,通過INTA向中斷控制器發送響應訊號,並將中斷號通過Data Line取回,然後讀取IDT表,獲得該中斷號對應的IDT表中的第i項,然後根據IDT[i]描述符中指定的段選擇子,去GDT表中尋找,獲得中斷處理常式的基址,然後用基址+IDT[i]中指定的中斷處理常式的位移地址,來獲得中斷處理常式的地址,當然,在讀取IDT和GDT表時還會有一些安全性檢查。
CPU去判斷有沒有發生特權級的變化(使用者態陷入核心態),如果有,通過tr寄存器獲得TSS段的基址,從中讀取核心態堆棧的ss和esp,此時就將使用者態的堆棧切換到了核心態的堆棧,接著將使用者態的ss和esp儲存在核心堆棧中,如果此中斷的類型是故障(fault),則重新修改cs和eip的值為產生中斷的這條指令的地址,以便中斷處理完成後,重新執行這條指令。然後,在核心棧中儲存eflags,cs和eip的值,如果有硬體出錯碼,也將其儲存到堆棧上,最後將cs:eip的值賦值為中斷處理函數的基址+IDT[i]的位移。
從使用者態進入中斷與從核心態進入中斷的堆棧如下。
IDT表的初始化:通過set_intr_gate()和trap_init(),後者用於初始化20個異常和一個系統調用相關的中斷0x80。
在init_IRQ()中通過迴圈調用set_intr_gate(vector,interrupt[i])來初始化vector對應的IDT表項,這裡的interrupt[256]數組,通過下面的代碼把所有中斷的處理函數都定義為統一的入口:common_interrupt。
體繫結構無關的中斷處理過程: do_IRQ()。
irq_desc資料包含了224個irq_desc_t描述符,每一個中斷號對應一個描述符,這個描述符中的action鏈表,它指向irqaction的資料結構,代表這個中斷對應的處理函數,共用同一中斷向量的所有的處理函數都掛在這個鏈表上,通過遍曆這個鏈表,並查是否要是裝置註冊的處理函數(dev_id)。
非強制中斷(softirq)由核心靜態分配,在編譯時間就確定了,個數有限,目前核心支援8/9個,具有優先順序,主要與定時器,網路包的收發,塊裝置,調度,Tasklet等有關。在特定的點檢查有無可執行檔非強制中斷:
(1) local_bh_enable重新啟用非強制中斷時
(2) do_IRQ完成中斷處理過程後
(3) ksoftirqd被喚醒時
Tasklet是I/O驅動程式中實現可延遲函數的首選,建立在非強制中斷HI_SOFTIRQ和TASKLET_SOFTIRQ之上,二者的區別是優先順序不同,這兩種Tasklet由對應的數組tasklet_vec和tasklet_hi_vec來管理,數組的每一項對應一個CPU,即tasklet是與CPU綁定的。
Workqueue由工作隊列和工作者線程兩部分構成,工作者線程會週期性掃描工作隊列有無可處理的任務,非強制中斷和tasklet運行在中斷上下文,所以不可睡眠,workquue運行在進程上下文,可睡眠。