- Linux內核完全注釋(20周年版·第2版)
- 趙炯編著
- 1282字
- 2024-05-10 12:20:20
2.4.3 進程初始化
當boot/目錄中的引導程序把內核從磁盤上加載到內存中,并讓系統進入保護模式下運行后,就開始執行系統初始化程序init/main.c。該程序首先確定如何分配使用系統物理內存,然后調用內核各部分的初始化函數分別對內存管理、中斷處理、塊設備和字符設備、進程管理以及硬盤和軟盤硬件進行初始化處理。在完成了這些操作之后,系統各部分已經處于可運行狀態。此后程序把自己“手工”移動到任務0(進程0)中運行,并使用fork()調用首次創建出進程1。在進程1中程序將繼續進行應用環境的初始化并執行shell登錄程序。而原進程0則會在系統空閑時被調度執行,此時任務0僅執行pause()系統調用,并會再調用調度函數。
“移動到任務0中執行”這個過程由宏move_to_user_mode(include/asm/system.h)完成。它把main.c程序執行流從內核態(特權級0)移動到了用戶態(特權級3)的任務0中繼續運行。在移動之前,系統在對調度程序的初始化過程(sched_init())中,首先對任務0的運行環境進行了設置。這包括人工預先設置好任務0數據結構各字段的值(include/linux/sched.h)、在全局描述符表中添入任務0的任務狀態段(TSS)描述符和局部描述符表(LDT)的段描述符,并把它們分別加載到任務寄存器tr和局部描述符表寄存器ldtr中。
需要強調的是,內核初始化是一個特殊過程,內核初始化代碼也即是任務0的代碼。從任務0數據結構中設置的初始數據可知,任務0的代碼段和數據段基址是0,段限長是640KB。而內核代碼段和數據段的基址是0,段限長是16MB,因此任務0的代碼段和數據段分別包含在內核代碼段和數據段中。內核初始化程序main.c就是任務0中的代碼,只是在移動到任務0之前系統正以內核態特權級0運行著main.c程序。宏move_to_user_mode的功能就是把運行特權級從內核態的0級變換到用戶態的3級,但是仍然繼續執行原來的代碼指令流。
在移動到任務0的過程中,宏move_to_user_mode使用了中斷返回指令造成特權級改變的方法。該方法的主要思想是在堆棧中構筑中斷返回指令需要的內容,把返回地址的段選擇符設置成任務0代碼段選擇符,其特權級為3。此后執行中斷返回指令iret時將導致系統CPU從特權級0跳轉到外層的特權級3上運行。圖2-7是特權級發生變化時中斷返回堆棧結構示意圖。

圖2-7 特權級發生變化時中斷返回堆棧結構示意圖
宏move_to_user_mode首先往內核堆棧中壓入任務0數據段選擇符和內核堆棧指針。然后壓入標志寄存器內容。最后壓入任務0代碼段選擇符和執行中斷返回后需要執行的下一條指令的偏移位置。該偏移位置是iret后的一條指令處。
當執行iret指令時,CPU把返回地址送入CS:EIP中,同時彈出堆棧中標志寄存器內容。由于CPU判斷出目的代碼段的特權級是3,與當前內核態的0級不同。于是CPU會把堆棧中的堆棧段選擇符和堆棧指針彈出到SS:ESP中。由于特權級發生了變化,段寄存器DS、ES、FS和GS的值變得無效,此時CPU會把這些段寄存器清零。因此在執行了iret指令后需要重新加載這些段寄存器。此后,系統就開始以特權級3運行在任務0的代碼上。所使用的用戶態堆棧還是原來在移動之前使用的堆棧。而其內核態堆棧則被指定為其任務數據結構所在頁面的頂端開始(PAGE_SIZE+(long)&init_task)。由于以后在創建新進程時,需要復制任務0的任務數據結構,包括其用戶堆棧指針,因此要求任務0的用戶態堆棧在創建任務1(進程1)之前保持“干凈”狀態。