內核態和用戶態的切換:
用戶態到內核態的轉換:1、進行系統調用,2、異步中斷,3、外部硬件中斷
檢查特權級別的變化:當異常發生在用戶態,而異常處理函數則必須運行在內核態,則此時必須調用內核態的堆棧(系統調用必然是發生特權級的變化),步驟是,將進程的TSS段中的esp0和ss0賦值給esp,ss寄存器
於是乎,當進程由用戶態進入內核態時,必發生中斷,因為內核態的CPL優先級高,所以要進行棧的切換。那么就會讀tr寄存器以訪問該進程(現在還是用戶態)的TSS段。隨后用TSS中內核態堆棧段ss0和棧指針esp0裝載SS和esp寄存器,這樣就實現了用戶棧到內核棧的切換了。同時,內核用一組mov指令保存所有寄存器到內核態堆棧上,這也包括用戶態中ss和esp這對寄存器的內容。
最后將先前由中斷向量檢索得到的中斷處理程序的cs,eip信息裝入相應的寄存器,開始執行中斷處理程序,這時就轉到了內核態的程序執行了。
進程切換:
這里再次強調一下,每個進程都有一個thread_struct結構的字段thread,用於保留進程的一部分硬件上下文:
這里再次強調一下,每個進程都有一個thread_struct結構的字段thread,用於保留進程的一部分硬件上下文: struct thread_struct { struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES]; unsigned long esp0; unsigned long sysenter_cs; unsigned long eip; unsigned long esp; unsigned long fs; unsigned long gs; unsigned long debugreg[8]; unsigned long cr2, trap_no, error_code; union i387_union i387; struct vm86_struct __user * vm86_info; unsigned long screen_bitmap; unsigned long v86flags, v86mask, saved_esp0; unsigned int saved_fs, saved_gs; unsigned long *io_bitmap_ptr; unsigned long io_bitmap_max; };
待會我們就將看到,其實里邊最有用的就是eip和esp兩個字段,分別表示保存的指令和堆棧棧頂的偏移地址。但這里不包括ss寄存器的值,因為所有的切換都在內核態,那么只用為內核態的堆棧基址ss0保存一次就行了,你們這些進程切來切去,我都不便應萬變,把它保存在每個CPU所對應的那個TSS段中。這里也得出了一個很重要的結論,為了提高效率,所有內核態的進程堆棧段基地址都是相同的。
thread字段存放的都是內核棧的esp和eip,然后_switch_to()宏,此時已經進入要切換進來進程的內核棧中,把已經切換進來的進程的thread.esp0裝入本地cpu的esp0字段
切換前:被切換進程的內核棧,壓入ebp,eflag,然后把被切換進程內核棧的esp賦值給本進程的thread.esp。
切換進來的進程,把本進程的thread.ESP賦值給esp,此時便切換到本進程中,但是局部變量沒有完成切換,(向prev->thread.eip存入標記為1的地址。當被替換的進程重新恢復執行時,進程執行我們下面標記為1的那條指令:)這里的prev變量依舊屬於被切換的進程。
5. 宏把next->thread.eip的值(絕大多數情況下是上面所述標記為1的地址)壓入next的內核棧:
pushl next->thread.eip
注意體會,當next執行完了以后的函數后,會回到這個棧的位置,執行eip對應的那條指令。
6. 跳到__switch_to()函數:
jmp __switch_to
7. 如干程序執行后,當A將再次獲得CPU時,它執行一些保存eflags和ebp寄存器內容內容的指令,這兩條指令的第一條指令被標記為1:
1:
popl %ebp
popfl
