前言
我們知道現在操作系統,都是多進程和多線程,那麼會有一個操作系統幫助我們去切換進程和線程,這個是要消耗cpu資源的,那麼就來了解一下cpu資源消耗情況。
正文
一般是下面幾個場景切換:
-
進程上下文切換
-
線程上下文切換
-
中斷上下文切換
在瞭解進程切換的時候,需要了解另外一個東西,進程的運行環境,進程的運行環境分爲內核空間和用戶空間。
linux 按照特權等級,把進程的運行空間分爲內核空間和用戶空間,分佈對應着上圖中的,cpu 特權等級ring0 和 ring3。
進程既可以在用戶態空間運行,又可以在內核空間中運行,進程在用戶空間運行時,被稱爲進程的用戶態,而陷入內核空間的時候,被稱爲進程的內核態。
舉個例子,我們需要進行讀取文件。
-
首先需要調用open 打開文件
-
然後調用read 讀取文件內容
-
並調用write 將內容寫到標誌輸出
-
然後close。
那麼這個時候cpu的上下文切換是怎麼樣的呢?
cpu 寄存器裏原來用戶態的指令位置(寄存器),先保存起來。爲了執行內核態代碼,cpu 寄存器需要更新爲內核態指令的新位置。然後就跑到了內核態執行內核任務了。
而系統調用後,cpu 寄存器需要恢復到原來保存的用戶態,然後再切換到用戶空間,繼續運行進程。
一次系統調用,那麼需要兩次cpu切換。
系統調用過程中,並不會涉及到虛擬內存等進程用戶態的資源,也不會切換進程,這和進程切換不一致。
也就是說系統調用是同一個進程,發生了cpu 上下文切換(也叫特權模式切換),而進程切換,是進程進行了切換。
那麼進程的上下文切換是怎麼樣的呢?
首先進程是由內核來管理和調度的,進程的切換隻能發生在內核態。所以,進程的上下文切換不僅包含了虛擬內存、棧、全局變量等用戶空間資源,還包含了內核堆棧、寄存器等內核空間的狀態。
因此進程的上下文就比系統調用多了一步,在保存當前進程的內核狀態和cpu寄存器之前,需要把該進程的虛擬內存,棧等保留下來;而加載下一進程的內核態後,還需要刷新進程的虛擬內存和用戶棧。
這些現在不需要過於瞭解,在我後面介紹操作系統系列的時候,會詳細描述。
只需要知道其複雜高於系統調用即可,其步驟多了保存用戶空間資源的描述等。
那麼講完了進程的上下文切換,那麼講一下線程的上下文切換。
我們知道線程纔是cpu 運行的最小單位,其實進程切換就是兩個線程來自不同的進程,那麼兩個線程來自同一進程呢?這時候的cpu 操作是怎麼樣的呢?
有一個很關鍵的知識需要了解: 線程是調度的基本單位,而進程則是資源擁有的基本單位。說白了,內核的任務調度的對象是線程;而進程提供給線程虛擬內存,全局變量等資源。
這個時候就明白了,如果線程來自同一個進程,那麼就不需要切換虛擬內存,只需要切換線程的私有數據、寄存器等不共享的數據。
那麼還有一類場景是中斷上下文切換。
爲了快速響應硬件的事件,中斷處理會打斷進程的正常調度和執行,轉而調用中斷處理程序,響應設備時間。
而在打斷其他進程時,就需要將進程狀態保存下來,這樣在中斷結束後,進程依然可以從原來的狀態恢復。
跟進程上下文不同,中斷上下文切換並不涉及到進程的用戶態。所以即便中斷過程打斷一個正處於用戶態的進程,也不需要保留和恢復進程的虛擬內存、全局變量等用戶態資源。
中斷上下文,其實只包含內核態中斷服務執行必須的狀體,包括cpu 寄存器、內核堆棧、硬件中斷參數等。
對同一個cpu來說,中斷處理比進程擁有更高的優先級,所以中斷上下文切換並不會與進程上下文切換同時發生。
同樣道理,由於中斷會打斷正常進程的調度和執行,所以大部分中斷處理程序短小精悍,以便可能快的執行結束。
另外,跟進程上下文切換一樣,中斷上下文切換也需要消耗cpu,切換次數過多也會導致消耗大量的cpu,甚至英雄降低系統的整體性能。
所以,你發現中斷次數過多時,就需要注意去排查它是否會給你的系統帶來嚴重的性能影響。
實驗
我們知道除了線程需要調用cpu外,上下文還可能導致cpu高,那麼怎麼確定是不是上下文切換導致cpu高呢?
那麼我們怎麼來定位呢?
-
cs (context switch) 每秒上下文切換的次數
-
in (interrupt) 則是每秒中斷的次數
-
r (running or runnable) 是就緒隊列的長度,也就是正在運行和等待cpu的進程數。
-
b (blocked) 則是處於不可中斷睡眠的進程數。
上圖中可以看到cs 上下文切換了大概1.4k左右,in 中斷大概接近 1k,r 是0, b 是0.
那麼如何我們想知道到底是哪個進程中斷次數比較多呢?
cswch 表示每秒資源上下文切換的次數 (voluntary context switchs)
另外一個是nvcswch (none voluntary context switchs) 的次數
那麼什麼是自願,什麼是非自願呢?
自願救贖指系統無法獲取所需資源,導致上下文切換。比如i/o、內存等系統資源不足時,就會發生資源上下文切換。
而非自願切換,則是進程由於時間片等原因,被系統強制調度進而發生的上下文切換。比如大量進程都在爭搶cpu時,就容易發生非資源上下文切換。
那麼上下文切換多次次數算是正常呢?
使用sysbench 模擬線程直接的切換:
sysbench --thread=10 --max-time=300 threads run
然後vmstat 進行切換。
可以看到 cs 非常高,in 相對少,說明線程或者進程直接的切換,而不是系統中斷髮生的。
r 列非常高,遠超cpu 內核2個。
然後us 和 sy,分別是33 和67,說明內核佔用比較大。
雖然in 比較少,但是上升也很快,說明系統中斷也是有一部分。
那麼到底是哪個進程導致的呢?
通過:
pidstat -w -u 1
可以看到sysbench cpu 190%。
那麼是sysbench 的問題。
那麼問題就來了,這裏不管自願還是非自願的上下文切換次數,遠遠達不到我們看到的十幾萬次數。
這是爲什麼呢? 因爲這是進程的切換次數,如果這個進程一直捕獲的cpu,那麼哪有什麼機會去獲得進程切換的機會?
那麼需要這樣查看:
pidstat -wt 1
那麼現在上下文切換的次數找到了,那麼看下系統中斷的原因。
查看cpu 中斷的原因:
watch -d cat /proc/interrupts
可以看到res 變化很大,res 是重調度中斷,這個中斷類型表示,喚醒空閒狀態cpu來調度新的任務運行。
調度器用來分散任務到不同cpu的機制,通常也被稱爲處理器間中斷。
所以中斷升高還是因爲任務的調度問題,跟前面一致。
那麼問題來了,多少cpu 上下文切換算是正常呢? 一般是1w以內。
-
自願切換變多了,說明進程在等待資源,那麼是io問題
-
非自願切換變多了,那麼就是進程被強制調度,也就是在爭搶cpu,說明cpu到達瓶頸了。
-
中斷次數變多了,說明cpu被中斷處理程序佔用,還需要通過查看/proc/interrupts 文件查看。
結
後面兩節實戰一些例子。