性能測試必備知識(5)- 深入理解「CPU 上下文切換」

做性能測試的必備知識系列,可以看下面鏈接的文章哦

//www.cnblogs.com/poloyy/category/1806772.html

 

前言

上一篇文章中,舉例了大量進程等待 CPU 調度的場景

 

靈魂拷問

既然進程是在等待,並沒有運行,為什麼系統的平均負載還是會升高呢

 

回答

本文的重點:CPU 上下文切換就是罪魁禍首

 

先來聊聊 Linux

提出疑問

  • 之前說最好一個 CPU 運行一個進程,這樣 CPU 利用率剛剛好
  • 但事實上我們的 Linux 會同時運行很多進程,包括系統態的和自己啟動的進程,這不就違背了我們的美好初衷嗎?

 

知識點來回答疑問

  • Linux 是一個多任務作業系統
  • 支援遠大於 CPU 數量的任務同時運行
  • 但多任務其實並不是真的在同時運行
  • 而是因為系統在很短時間內,將 CPU 輪流分配給它們,造成多任務同時運行的錯覺

 

什麼是 CPU 上下文

CPU 暫存器和程式計數器(PC)

  • 在每個任務運行前,CPU 都需要知道任務從哪裡載入、又從哪裡開始運行
  • 所以需要系統事先幫它設置好 CPU 暫存器和程式計數器

 

CPU 暫存器

CPU 內置的容量小,但速度極快的記憶體

 

程式計數器

用來存儲 CPU 正在執行的指令位置,或者即將執行的下一條指令位置

 

CPU 上下文

CPU 暫存器和程式計數器是 CPU 在運行任何任務前,必須的依賴環境,所以也被叫做 CPU 上下文

 

CPU 上下文切換

分步驟去理解

  1. 先把前一個任務的 CPU 上下文(CPU 暫存器和程式計數器)保存起來
  2. 載入新任務的上下文到這些暫存器和程式計數器
  3. 最後再跳轉到程式計數器所指的新位置,運行新任務
  4. 保存下來的上下文,會存儲到系統內核中,並在任務重新調度執行時再次載入進來,這樣能保證任務原來的狀態不受影響,讓任務看起來還是連續運行

 

靈魂拷問一

CPU 上下文切換無非就是更新了 CPU 暫存器的值嘛,但這些暫存器,本身就是為了快速運行任務而設計的,為什麼會影響系統的 CPU 性能呢?

 

靈魂拷問二

  • 上面老說到的【任務】到底是什麼呢?
  • 是進程,執行緒?是的,進程和執行緒是最常見的任務
  • 那除此之外,還有其他的任務嗎?

 

回答

硬體通過觸發訊號,會導致中斷處理程式的調用,也是一種常見的任務

所以,根據任務的不同,CPU 的上下文切換可以分為不同的場景

  • 進程上下文切換
  • 執行緒上下文切換
  • 中斷上下文切換

 

系統調用

Linux 按照特權等級劃分進程的運行空間

分為

  • 內核空間(Ring 0):具有最高許可權,可以直接訪問所有資源
  • 用戶空間(Ring 3):只能訪問受限資源,不能直接訪問記憶體等硬體設備,必須通過系統調用陷入到內核中,才能訪問這些特權資源

 

也就是說,進程既可以在用戶空間運行,稱為進程的用戶態

又可以在內核空間運行,稱為進程的內核態

 

重點:用戶態到內核態的轉變需要通過系統調用來完成

 

系統調用的栗子

比如,當我們查看文件內容時,就需要多次系統調用來完成:

  1. 首先調用 open() 打開文件
  2. 然後調用 read() 讀取文件內容
  3. 並調用 write() 將內容輸出
  4. 最後調用 close() 關閉文件

 

系統調用的過程發生 CPU 上下文的切換

  1. CPU 暫存器里原來用戶態的指令位置,需要先保存起來
  2. 為了執行內核態程式碼,CPU 暫存器需要更新為內核態指令的新位置
  3. 最後才是跳轉到內核態運行內核任務
  4. 系統調用結束後,CPU 暫存器需要恢復原來保存的用戶態
  5. 然後再切換回用戶空間,繼續運行進程

總結下

  • 一次系統調用的過程,其實發生了兩次 CPU 上下文切換(用戶態切內核態,內核態再切回用戶態)
  • 系統調用過程中,並不會涉及到虛擬記憶體等進程用戶態的資源,也不會切換進程

 

和進程上下文切換的不同

  • 進程上下文切換:從一個進程切換到另一個進程運行
  • 系統調用:一直是同一個進程在運行

 

總結

  • 系統調用過程通常稱為特權模式切換,而不是上下文切換
  • 但實際上,系統調用過程中, CPU 上下文切換是無法避免的

 

進程上下文切換

基礎知識點

  • 在 Linux 中,進程是由內核來管理和調度
  • 進程的切換只能發生在內核態
  • 所以,進程的上下文不僅包括了虛擬記憶體、棧、全局變數等用戶空間的資源,還包括了內核堆棧、暫存器等內核空間的狀態

 

進程的上下文切換就比系統調用時多了一步

  • 在保存當前進程的內核狀態和 CPU 暫存器之前,需要先把該進程的虛擬記憶體、棧等保存下來【保存上下文】
  • 而載入了下一進程的內核態後,還需要刷新進程的虛擬記憶體和用戶棧【載入上下文】

保存上下文和載入上下文的過程需要內核在 CPU 上運行才能完成

 

進程上下文切換如何影響系統性能?

  • 根據 Tsuna 的測試報告,每次上下文切換都需要幾十納秒到數微秒的 CPU 時間
  • 這個時間還是略大的,特別是在進程上下文切換次數較多的情況下,很容易導致 CPU 將大量時間耗費暫存器、內核棧以及虛擬記憶體等資源的保存和恢復上,進而大大縮短了真正運行進程的時間
  • 這也正是上一篇文章中講到的,導致平均負載升高的一個重要因素

 

TLB 也會受影響?

  • TLB(Transaction Lookaside Buffer)來管理虛擬記憶體到物理記憶體的映射關係
  • 當虛擬記憶體更新後,TLB 也需要刷新,記憶體的訪問也會變慢
  • 特別是在多處理器系統上,快取是被多個處理器共享的,刷新快取不僅會影響當前處理器的進程,還會影響共享快取的其他處理器的進程

 

什麼時候會切換進程上下文

  • 顧名思義,只有在進程切換時才需要切換上下文
  • 換句話說,只有在進程調度時才需要切換上下文

 

CPU 如何挑選進程來運行?

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

 

進程什麼時候才會被調度到 CPU 上運行?

1 – 主動釋放

進程執行完終止了,會釋放 CPU,這時候從等待隊列中拿一個新的進程來運行

 

2 – 時間片輪轉

 為了保證所有進程可以得到公平調度,CPU 時間被劃分為一段段的時間片,這些時間片再被輪流分配給各個進程,當某個進程的時間片耗盡了,就會被系統掛起,切換到其它正在等待 CPU 的進程運行

 

3 – 資源不足

進程在系統資源不足(比如記憶體不足)時,要等到資源滿足後才可以運行,這個時候 進程也會被掛起,並由系統調度其他進程運行

 

4 – sleep 函數

當進程通過睡眠函數 sleep 這樣的方法將自己主動掛起時,自然也會重新調度

 

5 – 優先順序高

當有優先順序更高的進程運行時,為了保證高優先順序進程的運行,當前進程會被掛起,由高優先順序進程來運行

 

6 – 硬中斷

發生硬體中斷時,CPU 上的進程會被中斷掛起,轉而執行內核中的中斷服務程式

 

執行緒上下文切換

先來聊下執行緒和進程的關係

  • 執行緒和進程的最大區別在於:執行緒是調度的基本單位,進程是資源分配的基本單位
  • 內核中的任務調度,實際上的調度對象是執行緒
  • 而進程只是給執行緒提供了虛擬記憶體、全局變數等資源
  • 當進程只有一個執行緒時,可以任務進程=執行緒
  • 當進程有多個執行緒時,執行緒會共享進程的虛擬記憶體和全局變數等資源,在執行緒上下文切換時這些資源是不需要修改的
  • 執行緒也有獨立的數據,比如棧、暫存器等,這些在執行緒上下文切換時是需要保存的

 

執行緒上下文切換的場景一

  • 前後兩個執行緒屬於不同進程
  • 此時,因為不同進程的資源不共享,所以執行緒上下文切換 等同於 進程上下文切換

 

執行緒上下文切換的場景二

  • 前後兩個執行緒屬於同一個進程
  • 此時,因為同一進程的資源是共享的,所以在切換時,虛擬記憶體這些資源就保持不動
  • 只需要切換執行緒的私有數據、暫存器等不共享的數據

 

多執行緒的優勢

執行緒上下文切換對比進程上下文切換,很明顯切換消耗的資源會更少,所以多執行緒比多進程更有優勢

 

中斷上下文切換

中斷處理

為了快速響應硬體的事件,中斷處理會打斷進程的正常調度和執行,轉而調用中斷處理程式,響應設備事件

在打斷其他進程時,就需要將進程當前的狀態保存下來,這樣在中斷結束後,進程仍然可以從原來的狀態恢復運行

 

和進程上下文切換的不同點

  • 中斷上下文切換並不涉及到進程的用戶態
  • 即便中斷過程打斷了 一個正處在用戶態的進程,也不需要保存和恢復這個進程的虛擬記憶體、全局變數等用戶態資源
  • 中斷上下文,只包括內核態中斷服務程式執行所必需的狀態,包括 CPU 暫存器、內核堆棧、硬體中斷參數

 

中斷上下文不會和進程上下文切換同時發生

  • 對同一個 CPU 來說,中斷處理比進程擁有更高的優先順序
  • 由於中斷會打斷正常進程的調度和執行,所以大部分中斷處理程式都短小精悍,以便儘可能快的執行結束

 

耗資源程度

  • 跟進程上下文切換一樣,中斷上下文切換也需要消耗 CPU,切換次數過多也會耗費大量的 CPU,甚至嚴重降低系統的整體性能
  • 當發現中斷次數過多時,就需要注意去排查它是否會給你的系統帶來嚴重的性能問題

 

CPU 上下文切換的總結

  • CPU 上下文切換,是保證 Linux 系統正常工作的核心功能之一,一般情況下不需要關注【CPU 上下文切換是正常核心功能值】
  • 但過多的上下文切換,會把 CPU 時間消耗在暫存器、內核棧、虛擬記憶體等數據的保存和恢復上,從而縮短進程真正的運行時間,導致系統的整體性能大幅下降【數據保存和恢復時間增加,進程運行時間減少,性能下降】