Linux操作系統(tǒng)通過實戰(zhàn)理解CPU上下文切換
1、CPU上下文
CPU上下文其實是一些環(huán)境正是有這些環(huán)境的支撐,任務得以運行,而這些環(huán)境的硬件條件便是CPU寄存器和程序計數(shù)器。CPU寄存器是CPU內(nèi)置的容量非常小但是速度極快的存儲設備,程序計數(shù)器則是CPU在運行任何任務時必要的,里面記錄了當前運行任務的行數(shù)等信息,這就是CPU上下文。

2、CPU上下文切換
根據(jù)任務的不同,CPU的上下文切換就可以分為進程上下文切換、線程上下文切換、中斷上下文切換
,進程上下文切換。
在Linux中,Linux按照特權(quán)等級,將進程的運行空間分為內(nèi)核空間和用戶空間:
內(nèi)核空間具有最高權(quán)限,可以直接訪問所有資源
用戶空間只能訪問受限資源,不能直接訪問內(nèi)存等硬件設備,要想訪問這些特權(quán)資源,必須通過系統(tǒng)調(diào)用
對于一個進程來說,一般是運行在用戶態(tài)的,但是當需要訪問內(nèi)存、磁盤等硬件設備的時候需要陷入到內(nèi)核態(tài)中,也就是要從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)變,而這種轉(zhuǎn)變需要通過系統(tǒng)調(diào)用來實現(xiàn),例如一個打開文件的操作,需要調(diào)用open()打開文件,read()讀取文件內(nèi)容,write()將文件內(nèi)容輸出到控制臺,最后close()關(guān)閉文件,這就是系統(tǒng)調(diào)用
在系統(tǒng)調(diào)用的過程中同樣發(fā)發(fā)生了CPU上下文切換:
CPU寄存器里面原來用戶態(tài)的指令位置,需要先保存起來,接著運行內(nèi)核態(tài)代碼
CPU寄存器需要更新為內(nèi)核態(tài)指令的位置,執(zhí)行內(nèi)核態(tài)代碼
系統(tǒng)調(diào)用結(jié)束后,CPU寄存器需要恢復原來保存的用戶態(tài),然后切換為用戶空間,所以一次系統(tǒng)調(diào)用的過程,會發(fā)生兩次的CPU上下文切換但是我們一般說系統(tǒng)調(diào)用是特權(quán)模式切換而不是上下文切換,因為這里沒有涉及到虛擬內(nèi)存等這些進程用戶態(tài)的資源,也不會切換進程是屬于進程之內(nèi)的上下文切換,進程是由內(nèi)核來管理和調(diào)度的,進程的切換只能發(fā)生在內(nèi)核態(tài),所以進程的上下文包含了虛擬內(nèi)存、棧、全局變量等用戶空間的資源,還包含了內(nèi)核堆棧、寄存器等內(nèi)核空間的狀態(tài),所以進程的上下文切換要比系統(tǒng)調(diào)用更多一步,保存該進程的虛擬內(nèi)存、棧等用戶空間的資源,進程上下文切換一般需要幾十納秒到數(shù)微秒的CPU時間,當進程上下文切換次數(shù)比較多的情況下愛,將導致CPU將大量的時間耗費在寄存器、內(nèi)核棧即虛擬內(nèi)存等資源的保存和恢復上,另外,Linux通過TLB快表來管理虛擬內(nèi)存到物理內(nèi)存的映射關(guān)系,當虛擬內(nèi)存更新之后,需要刷新緩存,在這多處理系統(tǒng)上是很復雜的,因為多個處理器共享一個緩存。
下面再來說說什么時候會進行進程的上下文切換,其實就是進程在被調(diào)度的時候需要切換上下文,可能是主動地,也有可能是被動的
系統(tǒng)進程正常調(diào)度算法導致進程上下文切換,例如目前使用的時間片輪轉(zhuǎn)算法,當一個進程的時間片耗盡之后,CPU會進項進程的調(diào)度切換到其他進程
進程在資源不足的時候,會被掛起例如在等待IO或者內(nèi)存不足的時候,會主動掛起,并且等待系統(tǒng)調(diào)度其他進程
當進程通過一些睡眠函數(shù)sleep()主動掛起的時候,也會重新調(diào)度
當有高優(yōu)先級的進程運行時,當前進程也會被掛起
當發(fā)生硬件中斷時,CPU上的進程會被中斷掛起
3、線程上下文切換
線程是調(diào)度的基本單位,而進程則是資源擁有的基本單位,也就是說對于內(nèi)核中的任務調(diào)度是以線程為單位,但是進程只是給線程提供了虛擬內(nèi)存、全局變量等資源,進程與線程之間的區(qū)別這里不再介紹
那么線程上下文的切換,其實分為兩種情況:
前后兩個線程屬于不同進程,因為資源不共享,所以這時候的線程上下文切換和進程上下文切換是一致的
前后兩個線程屬于同一個進程,因為虛擬內(nèi)存是共享的,所以在切換的時候,虛擬內(nèi)存這些資源保持不動,只有切換線程的私有數(shù)據(jù)、寄存器等不共享的資源
所以同進程內(nèi)的線程切換要比多進程內(nèi)的線程切換消耗更少的資源
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)?


4、中斷上下文切換
中斷是為了快速響應硬件的事件,簡單來shu就是計算機停下當前的事情,去處理其他的事情,然后在回來繼續(xù)執(zhí)行之前的任務,例如我們在調(diào)用print函數(shù)的時候,其實匯編的底層會幫我們調(diào)用一條 int 0x80的指令,便是調(diào)用0x80號中斷
當然,中斷要先將當前進程的狀態(tài)保存下來,這樣中斷結(jié)束后進程仍然可以從原來的狀態(tài)恢復運行,中斷上下文的切換并不涉及進程的用戶態(tài),所以當中斷程序打斷了正在處于用戶態(tài)的進程,不需要保存和恢復這個進程的虛擬內(nèi)存、全局變量等用戶態(tài)資源,只需要保存和恢復這個進程的內(nèi)核態(tài)中的資源包括CPU寄存器、內(nèi)核堆棧等
對于同一個CPU來說,中斷處理比進程擁有更高的優(yōu)先級,所以中斷上下文切換并不會與進程上下文切換同時發(fā)生,一般來說中斷程序都執(zhí)行比較快短小精悍,以便快速結(jié)束執(zhí)行之前的任務。當中斷上下文切換次數(shù)比較多的時候,會耗費大量的CPU
怎么查看系統(tǒng)上下文
上面已經(jīng)介紹到CPU上下文切換分為進程上下文切換、線程上下文切換、中斷上下文切換,那么過多的上下文切換會把CPU的時間消耗在寄存器、內(nèi)核棧以及虛擬內(nèi)存等數(shù)據(jù)的保存和恢復上,縮短進程真正運行的時間,成為系統(tǒng)性能大幅下降的一個因素
所以我們可以使用vmstat這個工具來查詢系統(tǒng)的上下文切換情況,vmstat是一個常用的系統(tǒng)性能分析工具,可以用來分析CPU上下文切換和中斷的次數(shù)

需要特別關(guān)注的是:
cs(context switch):每秒上下文切換的次數(shù)
in(interrupt):每秒中斷的次數(shù)
r(Running or Runnable):就緒隊列的長度,也就是正在運行和等待CPU的進程
b(Blocked):處于不可中斷睡眠狀態(tài)的進程數(shù)
vmstat是給出整個系統(tǒng)總體的上下文切換情況,要想查看每個進程的詳細情況就需要使用pidstat,加上-w選項就可以查看進程上下文切換的情況

需要特別關(guān)注的是:
cswch(voluntary context switches):表示每秒自愿上下文切換的次數(shù)
nvcswch(non voluntary context switches):表示每秒非自愿上下文切換的次數(shù)
這兩個概念的分別含義:
自愿上下文切換:進程無法獲取所需的資源,導致的上下文切換,例如IO、內(nèi)存等資源不足時,就會發(fā)生自愿上下文切換
非自愿上下文切換:進程由于時間片已到等時間,被系統(tǒng)強制調(diào)度,進而發(fā)生的上下文切換,例如大量的進程都在爭搶CPU時,就容易發(fā)生非自愿上下文切換
實戰(zhàn)分析
通過上面的工具已經(jīng)可以初步查看到系統(tǒng)上下文切換的次數(shù),但是當系統(tǒng)上下文切換的次數(shù)為多少時是不正常的呢?
案例使用sysbench工具來模擬多線程調(diào)度切換的情況,sysbench是一個多線程的基準測試工具,可以模擬上下文切換過多的問題
首先在第一個終端運行stsbench,模擬多線程切換問題
# 以 10 個線程運行 5 分鐘的基準測試,模擬多線程切換的問題 sysbench --threads=10 --max-time=300 threads run
然后在第二個終端運行vmstat,每1秒查看上下文切換的情況

可以觀察到如下指標:
r列:就緒隊列的長度已經(jīng)到了8左右,已經(jīng)超過了2個cpu,所以會有大量的CPU競爭
us(user)列和sy(system)列,這兩列的CPU使用率已經(jīng)到達100%,并且大量是由sy造成的,說明CPU主要是被內(nèi)核占用了
in(interrupt):in列的數(shù)值也到了解決1萬,所以中斷處理也是一個問題
那我們接著使用pidstat來查看是那一個進程出現(xiàn)了問題,由于pidstat默認是顯示進程的指標數(shù)據(jù),但是我們使用sysbench模擬的線程的數(shù)據(jù),所以需要加上-t選項
gpw@gopuwe:~$ pidstat -wt

所以到這里可以分析出是sysbench的子線程的上下文切換次數(shù)有很多
還有一個問題,在使用vmstat的時候,發(fā)現(xiàn)in指標的數(shù)據(jù)也比較多,那么我們需要找出是什么類型的中斷導致了中斷上升,中斷肯定是發(fā)生在內(nèi)核態(tài),但是pidstat只是一個進程的性能分析工具,并不提供任何關(guān)于中斷的詳細信息
我們可以從/proc/interrupts這個只讀文件中讀取,/proc是一個虛擬文件系統(tǒng),用于內(nèi)核空間和用戶空間之間的通信,/proc/interrupts則提供了一個只讀的中斷使用情況,可以使用cat命令查看/proc/interrupts可以發(fā)現(xiàn)變化速度最快的是重調(diào)度中斷RES,這個中斷類型表示喚醒空閑狀態(tài)的CPU來調(diào)度新的任務運行,也被成為處理器中斷
那么到底上下文切換的次數(shù)為多少合適呢?
這個數(shù)值其實取決于系統(tǒng)本身的 CPU 性能,在我看來,如果系統(tǒng)的上下文切換次數(shù)比較穩(wěn)
定,那么從數(shù)百到一萬以內(nèi),都應該算是正常的。但當上下文切換次數(shù)超過一萬次,或者切
換次數(shù)出現(xiàn)數(shù)量級的增長時,就很可能已經(jīng)出現(xiàn)了性能問題,這個時候還要根據(jù)上下文切換的類型,做具體的分析,例如:
自愿上下文切換變多了,說明進程都在等待資源,有可能發(fā)生了 I/O 等其他問題;
非自愿上下文切換變多了,說明進程都在被強制調(diào)度,也就是都在爭搶 CPU,說明 CPU的確成了瓶頸;
中斷次數(shù)變多了,說明 CPU 被中斷處理程序占用,還需要通過查看 /proc/interrupts 文件
原文作者:人人極客社區(qū)
