做效能測試的必備知識系列,可以看下面連結的文章哦
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 上下文切換
分步驟去理解
- 先把前一個任務的 CPU 上下文(CPU 暫存器和程式計數器)儲存起來
- 載入新任務的上下文到這些暫存器和程式計數器
- 最後再跳轉到程式計數器所指的新位置,執行新任務
- 儲存下來的上下文,會儲存到系統核心中,並在任務重新排程執行時再次載入進來,這樣能保證任務原來的狀態不受影響,讓任務看起來還是連續執行
靈魂拷問一
CPU 上下文切換無非就是更新了 CPU 暫存器的值嘛,但這些暫存器,本身就是為了快速執行任務而設計的,為什麼會影響系統的 CPU 效能呢?
靈魂拷問二
- 上面老說到的【任務】到底是什麼呢?
- 是程式,執行緒?是的,程式和執行緒是最常見的任務
- 那除此之外,還有其他的任務嗎?
回答
硬體通過觸發訊號,會導致中斷處理程式的呼叫,也是一種常見的任務
所以,根據任務的不同,CPU 的上下文切換可以分為不同的場景
- 程式上下文切換
- 執行緒上下文切換
- 中斷上下文切換
系統呼叫
Linux 按照特權等級劃分程式的執行空間
- 核心空間(Ring 0):具有最高許可權,可以直接訪問所有資源
- 使用者空間(Ring 3):只能訪問受限資源,不能直接訪問記憶體等硬體裝置,必須通過系統呼叫陷入到核心中,才能訪問這些特權資源
也就是說,程式既可以在使用者空間執行,稱為程式的使用者態
又可以在核心空間執行,稱為程式的核心態
重點:使用者態到核心態的轉變需要通過系統呼叫來完成
系統呼叫的栗子
比如,當我們檢視檔案內容時,就需要多次系統呼叫來完成:
- 首先呼叫 open() 開啟檔案
- 然後呼叫 read() 讀取檔案內容
- 並呼叫 write() 將內容輸出
- 最後呼叫 close() 關閉檔案
系統呼叫的過程發生 CPU 上下文的切換
- CPU 暫存器裡原來使用者態的指令位置,需要先儲存起來
- 為了執行核心態程式碼,CPU 暫存器需要更新為核心態指令的新位置
- 最後才是跳轉到核心態執行核心任務
- 系統呼叫結束後,CPU 暫存器需要恢復原來儲存的使用者態
- 然後再切換回使用者空間,繼續執行程式
總結下
- 一次系統呼叫的過程,其實發生了兩次 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個視窗開放,大部分都得等【系統資源不足】
- 如果正在辦理業務的突然說自己不辦了,那他就去旁邊休息【sleep】
- 如果突然來了個VIP客戶,可以強行插隊【優先順序高】
- 如果突然斷電了,都得等。。【中斷】
執行緒上下文切換
先來聊下執行緒和程式的關係
- 執行緒和程式的最大區別在於:執行緒是排程的基本單位,程式是資源分配的基本單位
- 核心中的任務排程,實際上的排程物件是執行緒
- 而程式只是給執行緒提供了虛擬記憶體、全域性變數等資源
- 當程式只有一個執行緒時,可以任務程式=執行緒
- 當程式有多個執行緒時,執行緒會共享程式的虛擬記憶體和全域性變數等資源,線上程上下文切換時這些資源是不需要修改的
- 執行緒也有獨立的資料,比如棧、暫存器等,這些線上程上下文切換時是需要儲存的
執行緒上下文切換的場景一
- 前後兩個執行緒屬於不同程式
- 此時,因為不同程式的資源不共享,所以執行緒上下文切換 等同於 程式上下文切換
執行緒上下文切換的場景二
- 前後兩個執行緒屬於同一個程式
- 此時,因為同一程式的資源是共享的,所以在切換時,虛擬記憶體這些資源就保持不動
- 只需要切換執行緒的私有資料、暫存器等不共享的資料
多執行緒的優勢
執行緒上下文切換對比程式上下文切換,很明顯切換消耗的資源會更少,所以多執行緒比多程式更有優勢
中斷上下文切換
中斷處理
- 為了快速響應硬體的事件,中斷處理會打斷程式的正常排程和執行,轉而呼叫中斷處理程式,響應裝置事件
- 在打斷其他程式時,就需要將程式當前的狀態儲存下來,這樣在中斷結束後,程式仍然可以從原來的狀態恢復執行
和程式上下文切換的不同點
- 中斷上下文切換並不涉及到程式的使用者態
- 即便中斷過程打斷了 一個正處在使用者態的程式,也不需要儲存和恢復這個程式的虛擬記憶體、全域性變數等使用者態資源
- 中斷上下文,只包括核心態中斷服務程式執行所必需的狀態,包括 CPU 暫存器、核心堆疊、硬體中斷引數
中斷上下文不會和程式上下文切換同時發生
- 對同一個 CPU 來說,中斷處理比程式擁有更高的優先順序
- 由於中斷會打斷正常程式的排程和執行,所以大部分中斷處理程式都短小精悍,以便儘可能快的執行結束
耗資源程度
- 跟程式上下文切換一樣,中斷上下文切換也需要消耗 CPU,切換次數過多也會耗費大量的 CPU,甚至嚴重降低系統的整體效能
- 當發現中斷次數過多時,就需要注意去排查它是否會給你的系統帶來嚴重的效能問題
CPU 上下文切換的總結
- CPU 上下文切換,是保證 Linux 系統正常工作的核心功能之一,一般情況下不需要關注【CPU 上下文切換是正常核心功能值】
- 但過多的上下文切換,會把 CPU 時間消耗在暫存器、核心棧、虛擬記憶體等資料的儲存和恢復上,從而縮短程式真正的執行時間,導致系統的整體效能大幅下降【資料儲存和恢復時間增加,程式執行時間減少,效能下降】