什么是CPU上下文
Linux是一個多任務操作系統,它支持遠大於CPU核心數的任務同時進行。當然,這些任務並不是真的同時在運行,而是因為系統在很短的時間內,將CPU輪流分配給它們,造成多任務同時運行的錯覺。每個任務在運行前,CPU都需要知道任務從哪來加載,又從哪里開始運行,也就是說,需要事先幫它們設置好CPU寄存器和程序計數器( Program Counter,PC )。
CPU寄存器:是CPU內置的容量小、但速度快的內存,用來臨時存放指令執行運行過程中的操作數和中間(最終)的操作結果。
程序計數器:是用來存儲CPU正在運行的指令位置、或者即將執行的下一條指令位置。
CPU寄存器和程序計數器是CPU運行任何任務前,必須依賴的環境,也被稱作CPU上下文。
什么是CPU上下文切換
把前一個任務的CPU上下文(CPU寄存器和程序計數器)保存起來,然后加載新任務的上下文到這些寄存器和程序計數器,最后再跳轉到程序計數器所指的新位置,運行新的任務。而這些保存下來的上下文,會存儲在系統內核中,並在任務重新調度執行時再次加載進來。這樣就能保證任務原來的狀態不受影響,讓任務看起來還是連續運行。
CPU的上下文切換可以分為幾個不同的場景:進程上下文切換、線程上線文切換、中斷上下文切換;
進程上下文切換
系統調用時的切換
Linux按照特權等級,把進程的運行空間分為內核空間和用戶空間。一些特殊的操作如調用open()打開文件等 都需要切換到內核空間運行,用戶空間是沒有權限調用這些的。也就是說,進程既可以在用戶空間運行,又可以在內核空間運行。在用戶空間運行即為用戶態,而陷入內核空間的時候,即為內核態。這種從用戶態切換到內核態時,必須經過系統調用來完成。
系統調用需要上下文切換。切換時,先保存CPU寄存器里原來用戶態的指令位置。接着,為了執行內核態代碼,CPU寄存器需要更新為內核態執行的新位置。最后才是跳轉到內核態運行內核任務。系統調用結束后,CPU寄存器需要恢復原來保存的用戶態,然后再切換到用戶空間,繼續運行進程。所以一次系統調用的過程,其實是發生了兩次CPU上下文切換。
需要注意的是,系統調用過程中,並不會涉及到虛擬內存等進程用戶態的資源,也不會進行切換進程。這跟我們通常說的進程上下文切換是不一樣的。
所以,系統調用過程通常稱為特權模式切換,而不是上下文切換。但實際上,系統調用過程中,CPU的上下文切換還是無法避免的。
進程的切換
進程是由內核來管理和調度的,進程的切換只能發生在內核態。所以,進程的上下文不僅包括虛擬內存、棧、全局變量等用戶空間的資源,還包括內核堆棧、寄存器等內核空間的狀態。因此,進程的上下文切換就比系統調用時多了一步:在保存當前進程的內核狀態和CPU寄存器之前,需要先把該進程的虛擬內存、棧等保存下來;而加載了下一進程的內核態后,還需要刷新進程的虛擬內存和用戶棧。
發生進程上下文切換的場景:
- 為了保證所有進程可以得到公平調度,CPU 時間被划分為一段段的時間片,這些時間片再被輪流分配給各個進程。這樣,當某個進程的時間片耗盡了,就會被系統掛起,切換到其它正在等待 CPU 的進程運行。
- 進程在系統資源不足(比如內存不足)時,要等到資源滿足后才可以運行,這個時候進程也會被掛起,並由系統調度其他進程運行。
- 當進程通過睡眠函數 sleep 這樣的方法將自己主動掛起時,自然也會重新調度。
- 當有優先級更高的進程運行時,為了保證高優先級進程的運行,當前進程會被掛起,由高優先級進程來運行
- 發生硬件中斷時,CPU 上的進程會被中斷掛起,轉而執行內核中的中斷服務程序。
進程切換的視圖:
關鍵點:
- 發生中斷時的保存現場,將發生中斷時的所有通用寄存器保存到進程的內核棧,使用struct pt_regs結構。
- 地址空間切換將進程自己的頁全局目錄的基地址pgd保存在ttbr0_le1中,用於mmu的頁表遍歷的起始點。
- 硬件上下文切換的時候,將此時的調用保存寄存器和pc, sp保存到struct cpu_context結構中。做好了這幾個保存工作,當進程再次被調度回來的時候,通過cpu_context中保存的pc回到了cpu_switch_to的下一條指令繼續執行,而由於cpu_context中保存的sp導致當前進程回到自己的內核棧,經過一系列的內核棧的出棧處理,最后將原來保存在pt_regs中的通用寄存器的值恢復到了通用寄存器,這樣進程回到用戶空間就可以繼續沿着被中斷打斷的下一條指令開始執行,用戶棧也回到了被打斷之前的位置,而進程訪問的指令數據做地址轉化(VA到PA)也都是從自己的pgd開始進行,一切對用戶來說就好像沒有發生一樣。
總結;
進程切換有兩大步驟:地址空間切換和處理器狀態切換(硬件上下文切換)。前者保證了進程回到用戶空間之后能夠訪問到自己的指令和數據(其中包括減小tlb清空的ASID機制),后者保證了進程內核棧和執行流的切換,會將當前進程的硬件上下文保存在進程所管理的一塊內存,然后將即將執行的進程的硬件上下文從內存中恢復到寄存器,有了這兩步的切換過程保證了進程運行的有條不紊,當然切換的過程是在內核空間完成,這對於進程來說是透明的。