寫給Java程序員看的,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、線程上下文切換

線程是調度的基本單位,而進程則是資源擁有的基本單位。

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

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

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

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

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

5、總結

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

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

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

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

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

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

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

參考資料

Linux性能優化實戰-03講 CPU 上下文切換
許式偉的架構課-08講 操作系統內核與編程接口
操作系統爲什麼要分用戶態和內核態
用戶態和內核態的區別

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章