CPU 上下文切換、用戶態、內核態、進程與線程上下文切換


1、概述
JDK源碼中很多Native方法,特別是多線程、NIO部分,很多功能需要操作系統功能支持,作為Java程序員,如果要理解和掌握多線程和NIO等原理,就需要對操作系統的原理有所了解。

2、CPU 上下文切換
多任務操作系統中,多於CPU個數的任務同時運行就需要進行任務調度,從而多個任務輪流使用CPU。

從用戶角度看好像所有的任務同時在運行,實際上是多個任務你運行一會,我運行一會,任務切換的速度很快,我們感覺不到而已。

而每個任務運行前,CPU需要知道從哪里加載這個任務的程序,還需要知道從程序哪行開始執行,這就要求OS事先幫任務設置好CPU的 寄存器 和程序計數器 。

CPU執行任務必須依賴的環境稱為 CPU上下文

CPU 上下文切換,就是先把前一個任務的 CPU 上下文(也就是 CPU 寄存器和程序計數器)保存起來,然后加載新任務的上下文到這些寄存器和程序計數器,最后再跳轉到程序計數器所指的新位置,運行新任務。

CPU的上下文切換分為幾種場景:進程上下文切換、線程上下文切換、中斷上下文切換

2.1、用戶態、內核態
Linux按特權等級,將進程的運行空間分為 內核空間 和 用戶空間 。

 

Intel x86架構使用了4個級別來標明不同的特權級權限。

R0實際就是內核態,擁有最高權限,可以直接訪問所有資源(包括外圍設備,例如硬盤,網卡等)。而一般應用程序處於R3狀態–用戶態。

進程在用戶空間運行時,被稱為進程的 用戶態,而陷入內核空間的時候,被稱為進程的 內核態。

R0最高可以讀取R0-3所有的內容,R1可以讀R1-3的,R2以此類推,R3只能讀自己的數據。

2.2、為什么分內核態和用戶態
假設沒有這種內核態和用戶態之分,程序隨隨便便就能訪問硬件資源,比如說分配內存,程序能隨意的讀寫所有的內存空間,如果程序員一不小心將不適當的內容寫到了不該寫的地方,就很可能導致系統崩潰。

用戶程序進行系統調用后,操作系統執行一系列的檢查驗證,確保這次調用是安全的,再進行相應的資源訪問操作。內核態能有效保護硬件資源的安全。

2.3、系統調用
從用戶態到內核態的轉變,需要通過系統調用來完成。比如,當我們查看文件內容時,就需要多次系統調用來完成:首先調用 open() 打開文件,然后調用 read() 讀取文件內容,並調用 write() 將內容寫到標准輸出,最后再調用 close() 關閉文件。

系統調用會將CPU從用戶態切換到核心態,以便 CPU 訪問受到保護的內核內存。

系統調用的過程會發生 CPU 上下文的切換,CPU 寄存器里原來用戶態的指令位置,需要先保存起來。接着,為了執行內核態代碼,CPU 寄存器需要更新為內核態指令的新位置。最后才是跳轉到內核態運行內核任務。

而系統調用結束后,CPU 寄存器需要恢復原來保存的用戶態,然后再切換到用戶空間,繼續運行進程。所以,一次系統調用的過程,其實是發生了兩次 CPU 上下文切換。

注意:系統調用過程中,並不會涉及到虛擬內存等進程用戶態的資源,也不會切換進程。

系統調用過程通常稱為特權模式切換,而不是進程上下文切換。

3、進程上下文切換
進程上下文切換跟系統調用又有什么區別呢?

首先,進程是由內核來管理和調度的,進程的切換只能發生在內核態。所以,進程的上下文不僅包括了虛擬內存、棧、全局變量等用戶空間的資源,還包括了內核堆棧、寄存器等內核空間的狀態。

因此,進程的上下文切換就比系統調用時多了一步:在保存當前進程的內核狀態和 CPU 寄存器之前,需要先把該進程的虛擬內存、用戶棧等保存下來;而加載下一個進程的內核態后,還需要加載這個進程的虛擬內存和用戶棧。

根據 Tsuna 的測試報告,每次上下文切換都需要幾十納秒到數微秒的 CPU 時間,在進程上下文切換次數較多的情況下,這個時間對於CPU來說是相當可觀的,會大大縮短CPU真正用於運行進程的時間。

3.1、什么時候會切換進程上下文?
只有在進程調度的時候,才需要切換上下文。

Linux 為每個 CPU 都維護了一個就緒隊列,將活躍進程(即正在運行和正在等待 CPU 的進程)按照優先級和等待 CPU 的時間排序,然后選擇最需要 CPU 的進程,也就是優先級最高和等待 CPU 時間最長的進程來運行。


新進程在什么時候才會被調度到 CPU 上運行呢?
1. 運行中的進程執行完終止了,CPU 會釋放出來,新的基礎進程就可以被調度到CPU上運行了。
2. 運行中的進程時間片用完,進程被掛起
3. 運行中的進程資源不足,進程被掛起
4. 運行中的進程執行Sleep方法主動掛起
5. 新進程優先級更高,運行中的進程被掛起
6. 發生硬件中斷,運行中的進程會被中斷掛起,轉而執行內核中的中斷服務程序。


4、線程上下文切換
線程是調度的基本單位,而進程則是資源擁有的基本單位。

所謂內核中的任務調度,實際上的調度對象是線程;而進程只是給線程提供了虛擬內存、全局變量等資源。

當進程只有一個線程時,可以認為進程就等於線程,當進程擁有多個線程時,這些線程會共享進程的虛擬內存和全局變量等資源。這些資源在上下文切換時是不需要修改的。

線程也有自己的私有數據,比如棧和寄存器等,這些在上下文切換時也是需要保存的。

線程的上下文切換其實就可以分為兩種情況:

兩個線程屬於不同進程,因為資源不共享,切換過程和進程上線文切換一樣
兩個線程屬於同一個進程,只需要切換線程的私有數據、寄存器等不共享的數據

5、總結
CPU上線文切換,切換寄存器、程序計數器

進程上線文切換,切換虛擬內存、用戶棧

線程上下文切換,2種情況:(1)線程私有數據(比如線程棧、程序計數器等);(2)、(1)+ 線程資源 ;

系統調用:需要進行線程上下文切換,但不是進程上下文切換

R0實際就是內核態,擁有最高權限,可以直接訪問所有資源(包括外圍設備,例如硬盤,網卡等)。

應用程序處於R3狀態–用戶態。

系統調用會進行 內核態、用戶態 轉換


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM