擊上方“一口Linux”,選擇“星標公眾號”

Linux是一個多任務的操作系統,可以支持遠大于CPU數量的任務同時運行,但是我們都知道這其實是一個錯覺,真正是系統在很短的時間內將CPU輪流分配給各個進程,給用戶造成多任務同時運行的錯覺。所以這就是有一個問題,在每次運行進程之前CPU都需要知道進程從哪里加載、從哪里運行,也就是說需要系統提前幫它設置好CPU寄存器和程序計數器
CPU上下文其實是一些環境正是有這些環境的支撐,任務得以運行,而這些環境的硬件條件便是CPU寄存器和程序計數器。CPU寄存器是CPU內置的容量非常小但是速度極快的存儲設備,程序計數器則是CPU在運行任何任務時必要的,里面記錄了當前運行任務的行數等信息,這就是CPU上下文

根據任務的不同,CPU的上下文切換就可以分為進程上下文切換、線程上下文切換、中斷上下文切換
在Linux中,Linux按照特權等級,將進程的運行空間分為內核空間和用戶空間:
內核空間具有最高權限,可以直接訪問所有資源
用戶空間只能訪問受限資源,不能直接訪問內存等硬件設備,要想訪問這些特權資源,必須通過系統調用
對于一個進程來說,一般是運行在用戶態的,但是當需要訪問內存、磁盤等硬件設備的時候需要陷入到內核態中,也就是要從用戶態到內核態的轉變,而這種轉變需要通過系統調用來實現,例如一個打開文件的操作,需要調用open()打開文件,read()讀取文件內容,write()將文件內容輸出到控制臺,最后close()關閉文件,這就是系統調用
在系統調用的過程中同樣發發生了CPU上下文切換:
CPU寄存器里面原來用戶態的指令位置,需要先保存起來,接著運行內核態代碼
CPU寄存器需要更新為內核態指令的位置,執行內核態代碼
系統調用結束后,CPU寄存器需要恢復原來保存的用戶態,然后切換為用戶空間,所以一次系統調用的過程,會發生兩次的CPU上下文切換
但是我們一般說系統調用是特權模式切換而不是上下文切換,因為這里沒有涉及到虛擬內存等這些進程用戶態的資源,也不會切換進程是屬于進程之內的上下文切換
進程是由內核來管理和調度的,進程的切換只能發生在內核態,所以進程的上下文包含了虛擬內存、棧、全局變量等用戶空間的資源,還包含了內核堆棧、寄存器等內核空間的狀態,所以進程的上下文切換要比系統調用更多一步,保存該進程的虛擬內存、棧等用戶空間的資源
進程上下文切換一般需要幾十納秒到數微秒的CPU時間,當進程上下文切換次數比較多的情況下愛,將導致CPU將大量的時間耗費在寄存器、內核棧即虛擬內存等資源的保存和恢復上,另外,Linux通過TLB快表來管理虛擬內存到物理內存的映射關系,當虛擬內存更新之后,需要刷新緩存,在這多處理系統上是很復雜的,因為多個處理器共享一個緩存
下面再來說說什么時候會進行進程的上下文切換,其實就是進程在被調度的時候需要切換上下文,可能是主動地,也有可能是被動的
系統進程正常調度算法導致進程上下文切換,例如目前使用的時間片輪轉算法,當一個進程的時間片耗盡之后,CPU會進項進程的調度切換到其他進程
進程在資源不足的時候,會被掛起例如在等待IO或者內存不足的時候,會主動掛起,并且等待系統調度其他進程
當進程通過一些睡眠函數sleep()主動掛起的時候,也會重新調度
當有高優先級的進程運行時,當前進程也會被掛起
當發生硬件中斷時,CPU上的進程會被中斷掛起
線程是調度的基本單位,而進程則是資源擁有的基本單位,也就是說對于內核中的任務調度是以線程為單位,但是進程只是給線程提供了虛擬內存、全局變量等資源,進程與線程之間的區別這里不再介紹
那么線程上下文的切換,其實分為兩種情況:
前后兩個線程屬于不同進程,因為資源不共享,所以這時候的線程上下文切換和進程上下文切換是一致的
前后兩個線程屬于同一個進程,因為虛擬內存是共享的,所以在切換的時候,虛擬內存這些資源保持不動,只有切換線程的私有數據、寄存器等不共享的資源
所以同進程內的線程切換要比多進程內的線程切換消耗更少的資源
中斷是為了快速響應硬件的事件,簡單來shu就是計算機停下當前的事情,去處理其他的事情,然后在回來繼續執行之前的任務,例如我們在調用print函數的時候,其實匯編的底層會幫我們調用一條 int 0x80的指令,便是調用0x80號中斷
當然,中斷要先將當前進程的狀態保存下來,這樣中斷結束后進程仍然可以從原來的狀態恢復運行,中斷上下文的切換并不涉及進程的用戶態,所以當中斷程序打斷了正在處于用戶態的進程,不需要保存和恢復這個進程的虛擬內存、全局變量等用戶態資源,只需要保存和恢復這個進程的內核態中的資源包括CPU寄存器、內核堆棧等
對于同一個CPU來說,中斷處理比進程擁有更高的優先級,所以中斷上下文切換并不會與進程上下文切換同時發生,一般來說中斷程序都執行比較快短小精悍,以便快速結束執行之前的任務。當中斷上下文切換次數比較多的時候,會耗費大量的CPU
上面已經介紹到CPU上下文切換分為進程上下文切換、線程上下文切換、中斷上下文切換,那么過多的上下文切換會把CPU的時間消耗在寄存器、內核棧以及虛擬內存等數據的保存和恢復上,縮短進程真正運行的時間,成為系統性能大幅下降的一個因素
所以我們可以使用vmstat這個工具來查詢系統的上下文切換情況,vmstat是一個常用的系統性能分析工具,可以用來分析CPU上下文切換和中斷的次數

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

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

可以觀察到如下指標:
r列:就緒隊列的長度已經到了8左右,已經超過了2個cpu,所以會有大量的CPU競爭
us(user)列和sy(system)列,這兩列的CPU使用率已經到達100%,并且大量是由sy造成的,說明CPU主要是被內核占用了
in(interrupt):in列的數值也到了解決1萬,所以中斷處理也是一個問題
那我們接著使用pidstat來查看是那一個進程出現了問題,由于pidstat默認是顯示進程的指標數據,但是我們使用sysbench模擬的線程的數據,所以需要加上-t選項
gpw@gopuwe:~$ pidstat -wt
所以到這里可以分析出是sysbench的子線程的上下文切換次數有很多
還有一個問題,在使用vmstat的時候,發現in指標的數據也比較多,那么我們需要找出是什么類型的中斷導致了中斷上升,中斷肯定是發生在內核態,但是pidstat只是一個進程的性能分析工具,并不提供任何關于中斷的詳細信息
我們可以從/proc/interrupts這個只讀文件中讀取,/proc是一個虛擬文件系統,用于內核空間和用戶空間之間的通信,/proc/interrupts則提供了一個只讀的中斷使用情況,可以使用cat命令查看/proc/interrupts可以發現變化速度最快的是重調度中斷RES,這個中斷類型表示喚醒空閑狀態的CPU來調度新的任務運行,也被成為處理器中斷
那么到底上下文切換的次數為多少合適呢?
這個數值其實取決于系統本身的 CPU 性能,在我看來,如果系統的上下文切換次數比較穩
定,那么從數百到一萬以內,都應該算是正常的。但當上下文切換次數超過一萬次,或者切
換次數出現數量級的增長時,就很可能已經出現了性能問題,這個時候還要根據上下文切換的類型,做具體的分析,例如:
自愿上下文切換變多了,說明進程都在等待資源,有可能發生了 I/O 等其他問題;
非自愿上下文切換變多了,說明進程都在被強制調度,也就是都在爭搶 CPU,說明 CPU的確成了瓶頸;
中斷次數變多了,說明 CPU 被中斷處理程序占用,還需要通過查看 /proc/interrupts 文件
end
一口Linux?
關注,回復【1024】海量Linux資料贈送
精彩文章合集
文章推薦
點擊“閱讀原文”查看更多分享,歡迎點分享、收藏、點贊、在看