效能測試必備知識(5)- 深入理解“CPU 上下文切換”

小菠蘿測試筆記發表於2020-07-22

做效能測試的必備知識系列,可以看下面連結的文章哦

https://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 上的程式會被中斷掛起,轉而執行核心中的中斷服務程式

 

程式上下文切換類比場景

  1. 銀行分配各個視窗給來辦理業務的人
  2. 如果只有1個視窗開放,大部分都得等【系統資源不足】
  3. 如果正在辦理業務的突然說自己不辦了,那他就去旁邊休息【sleep】
  4. 如果突然來了個VIP客戶,可以強行插隊【優先順序高】
  5. 如果突然斷電了,都得等。。【中斷】

 

執行緒上下文切換

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

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

 

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

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

 

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

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

 

多執行緒的優勢

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

 

中斷上下文切換

中斷處理

  • 為了快速響應硬體的事件,中斷處理會打斷程式的正常排程和執行,轉而呼叫中斷處理程式,響應裝置事件
  • 在打斷其他程式時,就需要將程式當前的狀態儲存下來,這樣在中斷結束後,程式仍然可以從原來的狀態恢復執行

 

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

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

 

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

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

 

耗資源程度

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

 

CPU 上下文切換的總結

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

相關文章