做性能測試的必備知識系列,可以看下面連結的文章哦
https://www.cnblogs.com/poloyy/category/1806772.html
上一篇文章中,舉例了大量程序等待 CPU 排程的場景
既然程序是在等待,并沒有運作,為什麼系統的平均負載還是會升高呢
本文的重點:CPU 上下文切換就是罪魁禍首
之前說最好一個 CPU 運作一個程序,這樣 CPU 使用率剛剛好
但事實上我們的 Linux 會同時運作很多程序,包括系統态的和自己啟動的程序,這不就違背了我們的美好初衷嗎?
Linux 是一個多任務作業系統
它支援遠大于 CPU 數量的任務同時運作
但多任務其實并不是真的在同時運作
而是因為系統在很短時間内,将 CPU 輪流配置設定給它們,造成多任務同時運作的錯覺
在每個任務運作前,CPU 都需要知道任務從哪裡加載、又從哪裡開始運作
是以需要系統事先幫它設定好 CPU 寄存器和程式計數器
CPU 内置的容量小,但速度極快的記憶體
用來存儲 CPU 正在執行的指令位置,或者即将執行的下一條指令位置
CPU 寄存器和程式計數器是 CPU 在運作任何任務前,必須的依賴環境,是以也被叫做 CPU 上下文
先把前一個任務的 CPU 上下文(CPU 寄存器和程式計數器)儲存起來
加載新任務的上下文到這些寄存器和程式計數器
最後再跳轉到程式計數器所指的新位置,運作新任務
儲存下來的上下文,會存儲到系統核心中,并在任務重新排程執行時再次加載進來,這樣能保證任務原來的狀态不受影響,讓任務看起來還是連續運作
CPU 上下文切換無非就是更新了 CPU 寄存器的值嘛,但這些寄存器,本身就是為了快速運作任務而設計的,為什麼會影響系統的 CPU 性能呢?
上面老說到的【任務】到底是什麼呢?
是程序,線程?是的,程序和線程是最常見的任務
那除此之外,還有其他的任務嗎?
硬體通過觸發信号,會導緻中斷處理程式的調用,也是一種常見的任務
是以,根據任務的不同,CPU 的上下文切換可以分為不同的場景
程序上下文切換
線程上下文切換
中斷上下文切換
核心空間(Ring 0):具有最高權限,可以直接通路所有資源
使用者空間(Ring 3):隻能通路受限資源,不能直接通路記憶體等硬體裝置,必須通過系統調用陷入到核心中,才能通路這些特權資源
也就是說,程序既可以在使用者空間運作,稱為程序的使用者态
又可以在核心空間運作,稱為程序的核心态
重點:使用者态到核心态的轉變需要通過系統調用來完成
比如,當我們檢視檔案内容時,就需要多次系統調用來完成:
首先調用 open() 打開檔案
然後調用 read() 讀取檔案内容
并調用 write() 将内容輸出
最後調用 close() 關閉檔案
CPU 寄存器裡原來使用者态的指令位置,需要先儲存起來
為了執行核心态代碼,CPU 寄存器需要更新為核心态指令的新位置
最後才是跳轉到核心态運作核心任務
系統調用結束後,CPU 寄存器需要恢複原來儲存的使用者态
然後再切換回使用者空間,繼續運作程序
總結下
一次系統調用的過程,其實發生了兩次 CPU 上下文切換【使用者态切核心态,核心态再切回使用者态】
系統調用過程中,并不會涉及到虛拟記憶體等程序使用者态的資源,也不會切換程序
程序上下文切換:從一個程序切換到另一個程序運作
系統調用:一直是同一個程序在運作
系統調用過程通常稱為特權模式切換,而不是上下文切換
但實際上,系統調用過程中, CPU 上下文切換是無法避免的
在 Linux 中,程序是由核心來管理和排程
程序的切換隻能發生在核心态
是以,程序的上下文不僅包括了虛拟記憶體、棧、全局變量等使用者空間的資源,還包括了核心堆棧、寄存器等核心空間的狀态
在儲存目前程序的核心狀态和 CPU 寄存器之前,需要先把該程序的虛拟記憶體、棧等儲存下來【儲存上下文】
而加載了下一程序的核心态後,還需要重新整理程序的虛拟記憶體和使用者棧【加載上下文】
儲存上下文和加載上下文的過程需要核心在 CPU 上運作才能完成
根據 Tsuna 的測試報告,每次上下文切換都需要幾十納秒到數微秒的 CPU 時間
這個時間還是略大的,特别是在程序上下文切換次數較多的情況下,很容易導緻 CPU 将大量時間耗費在寄存器、核心棧以及虛拟記憶體等資源的儲存和恢複上,進而大大縮短了真正運作程序的時間
這也正是上一篇文章中講到的,導緻平均負載升高的一個重要因素
TLB(Transaction Lookaside Buffer)來管理虛拟記憶體到實體記憶體的映射關系
當虛拟記憶體更新後,TLB 也需要重新整理,記憶體的通路也會變慢
特别是在多處理器系統上,緩存是被多個處理器共享的,重新整理緩存不僅會影響目前處理器的程序,還會影響共享緩存的其他處理器的程序
顧名思義,隻有在程序切換時才需要切換上下文
換句話說,隻有在程序排程時才需要切換上下文
Linux 為每個 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 上下文切換,是保證 Linux 系統正常工作的核心功能之一,一般情況下不需要關注【CPU 上下文切換是正常工作的核心功能】
但過多的上下文切換,會把 CPU 時間消耗在寄存器、核心棧、虛拟記憶體等資料的儲存和恢複上,進而縮短程序真正的運作時間,導緻系統的整體性能大幅下降【資料儲存和恢複時間增加,程序運作時間減少,性能下降】