Linux 上下文切換分析

一些基礎概念

我們知道,Linux 是一個多任務操作系統(tǒng),它支持遠大于 cpu 數量的任務同時運行。當然,這些任務實際上并不是真的在同時運行,而是因為系統(tǒng)在很短的時間內,將 cpu 在很短的時間內,將 cpu 輪流分配給他們,造成多任務同時運行的錯覺。
而每個任務運行前,cpu 都需要知道任務從哪里加載,又從哪里開始運行,也就是需要系統(tǒng)事先幫它設置好 cpu 寄存器和程序計數器,它們都是 cpu 在運行任務前,必須依賴的環(huán)境,因此也被叫做cpu 上下文。

上下文切換的分類

進程上下文切換

進程是由內核來管理和調度的,進程的切換只能發(fā)生在內核態(tài)。所以,進程上下文不僅包括了虛擬內存,棧,全局變量等用戶空間的資源,還包括了內核堆棧,寄存器等內核空間的狀態(tài)。
進程上下文切換的場景:

  • 劃分給當前進程的 cpu 時間片耗盡
  • 進程所需系統(tǒng)資源(比如內存)不足的情況下掛起,等待系統(tǒng)資源就緒。
  • 進程主動掛起,比如 sleep 函數。
  • 更高優(yōu)先級的任務搶占。
  • 發(fā)生硬件中斷時,CPU 上的進程會被中斷掛起,轉而執(zhí)行內核中的中斷服務程序。
線程上下文切換

線程和進程最大的區(qū)別在于,線程是調度的基本單位,而進程則是資源擁有的基本單位,所以:

  • 當進程只有一個線程時,可以認為進程就等于線程。
  • 當進程擁有多個線程時,這些線程會共享相同的虛擬內存和全局變量等資源。這些資源在上下文切換時是不需要修改的。
  • 另外,線程也有自己的私有數據,比如棧和寄存器等,這些在上下文切換時也是需要保存的。
    這樣一來,線程上下文切換可以分為以下兩種情況:
  • 前后兩個線程屬于不通進程。此時,資源不共享,所以切換過程等同于進程切換
  • 前后兩個線程屬于同一個進程。此時,因為虛擬內存是共享的,所以切換的時候,只需要切換線程的私有數據、寄存器等不共享的數據。
    由此可以發(fā)現,同為上下文切換,但是進城內的上下文切換,性能要更好一些,這也就是多線程代替多進程的一個優(yōu)勢。
中斷上下文切換

為了快速響應硬件的事件,中斷處理會打斷進程的正常調度和執(zhí)行,轉而調用中斷處理程序,響應設備事件。而在打斷其他進程時,就需要將進程當前的狀態(tài)保存下來,這樣在中斷結束后,進程仍然可以從原來的狀態(tài)恢復運行。

怎么分析上下文切換的問題

常用工具

vmstat是一個常用的系統(tǒng)性能分析工具,主要用來分析系統(tǒng)的內存使用情況,也常用來分析 cpu 上下文切換和中斷的次數。
比如,下面的輸出就一個vmstat的使用示例:

vmstat 5
# 每隔 5 秒輸出一組數據
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 1731492 562572 19648488    0    0     1    24    0    0  2  1 97  0  0
 0  0      0 1734320 562572 19648612    0    0     0   246 16499 23450  2  1 97  0  0
 0  0      0 1737196 562572 19648664    0    0     0   239 17506 24438  2  1 97  0  0
 1  1      0 1736132 562572 19648636    0    0     0   199 17109 24104  2  1 97  0  0

這里,需要重點關注下一下四列內容:

  • cs(context switch)是每秒上下文切換的次數。
  • in(interrupt)是每秒中斷的次數。
  • r(Running or Runnable)是就緒隊列的長度,就是正在運行和等待 cpu 的進程數,如果遠大于 cpu 核心數,則需要重點關注。
  • b(Blocked)則是處于不可中斷睡眠狀態(tài)的進程數。

vmstat只給出了系統(tǒng)總體的上下文切換情況,想要查看每個進程的詳細情況,就需要使用pidstat工具了,給它加上-w選項,就可以查看每個進程上下文切換的情況了。
比如:

# 每隔 5 秒輸出一組數據
pidstat -w 5
08:12:03 AM   UID       PID   cswch/s nvcswch/s  Command
08:12:08 AM     0         1      2.59      0.00  systemd
08:12:08 AM     0         8      0.40      0.00  kworker/0:1H-events_highpri
08:12:08 AM     0        10      3.59      0.00  ksoftirqd/0
08:12:08 AM     0        11    201.80      0.00  rcu_sched

這里需要重點關注以下兩個對象:

  • cswch,表示每秒自愿上下文切換(voluntary context switches)的次數。
  • nvswch,表示每秒非自愿(non voluntary context switches)上下文切換的次數。
    兩個概念:
  • 所謂自愿上下文切換,是指進程無法獲取所需資源,導致的上下文切換。比如說, I/O、內存等系統(tǒng)資源不足時,就會發(fā)生自愿上下文切換。
  • 而非自愿上下文切換,則是指進程由于時間片已到等原因,被系統(tǒng)強制調度,進而發(fā)生的上下文切換。比如說,大量進程都在爭搶 CPU 時,就容易發(fā)生非自愿上下文切換。

案例分析

這里有一臺 2c4g 的機器,安裝了sysbench,sysstat工具。我們按照以下步驟來實際模擬下多線程調度切換的情況。
先看下空閑系統(tǒng)上下文切換情況:

# 間隔 1s 后輸出一組數據
 vmstat 1 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 2590888  40356 864544    0    0   406  1166  340  464  9  2 88  0  0

可以看到,現在的上下文切換次數數是464 次每秒,中斷次數是 340 次每秒,r和 b 都是 0。
接下來,我們模擬系統(tǒng)多線程調度的瓶頸。

# 以 10 個線程運行 5 分鐘的基準測試,模擬多線程切換的問題
sysbench --threads=10 --max-time=300 threads run
WARNING: --max-time is deprecated, use --time instead
sysbench 1.0.20 (using system LuaJIT 2.1.0-beta3)
Running the test with following options:
Number of threads: 10
Initializing random number generator from current time
Initializing worker threads...
Threads started!

使用 vmstat 觀察上下文切換情況

# 每隔 1s輸出一組數據
vmstat  1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 8  0      0 2352000  47592 940192    0    0   232   642 1417 6273  8  7 85  0  0
 8  0      0 2351992  47624 940192    0    0     0   328 24708 989038 34 66  1  0  0
 8  0      0 2351960  47624 940192    0    0     0     0 28126 1007646 36 64  1  0  0
 7  0      0 2351960  47624 940192    0    0     0     0 25801 1010638 33 67  1  0  0
10  0      0 2351960  47624 940192    0    0     0     0 19873 1037725 34 65  1  0  0
 8  0      0 2351960  47624 940192    0    0     0     0 21823 1039407 35 65  1  0  0
 6  0      0 2351960  47632 940196    0    0     0    64 24975 1004337 35 65  1  0  0
 7  0      0 2351960  47632 940196    0    0     0     0 24334 1020564 36 63  1  0  0
 7  0      0 2351992  47632 940196    0    0     0     0 27219 981063 34 65  1  0  0

由以上數據可以看到,cs 列的上下文切換此時從空閑的 464 上升到 100w 次左右,同時:

  • r列:就緒隊列長度在 8-10 之間,遠超過系統(tǒng) 2 個 cpu 的個數,肯定會有大量的 cpu 競爭。
  • us(user)和 sy(system)列:這兩列的 CPU 使用率加起來上升到了 100%,其中系統(tǒng) CPU 使用率,也就是 sy 列高達 65%,說明 CPU 大部分是被內核占用了。
  • in 列:中斷次數也上升到了 2 萬左右,說明中斷處理也是個潛在的問題。

綜合這幾個指標我們可以知道,系統(tǒng)的就緒對列過長,也就是正在運行和等待運行的進程數過多,導致了大量的上下文切換,而上下文切換又導致了系統(tǒng) cpu 使用率升高。

那么,到底是哪個進程導致的呢?我們繼續(xù)通過pidstat工具來排查下

pidstat -w  -u 1
Linux 5.4.119-19.0009.28 (VM-13-158-tencentos)  12/31/2023  _x86_64_    (2 CPU)

08:53:00 AM   UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
08:53:01 AM     0         1    0.00    0.95    0.00    0.00    0.95     0  systemd
08:53:01 AM     0     40473   60.95  118.10    0.00    0.00  179.05     1  sysbench

08:53:00 AM   UID       PID   cswch/s nvcswch/s  Command
08:53:01 AM     0         1      0.95      0.00  systemd
08:53:01 AM     0         9      0.95      0.00  ksoftirqd/0
08:53:01 AM     0        10    148.57      0.00  rcu_sched
08:53:01 AM     0     37780      2.86      0.00  kworker/1:0-memcg_kmem_cache
08:53:01 AM     0     40525      0.95     10.48  pidstat

從 pidstat 輸出可以看到,cpu 使用率升高果然是 sysbench 導致的,它的 cpu 使用率已經達到了 179%。

不過,上面顯示的是進程上下文切換的次數,但是我們模擬的是線程切換次數,并且,這里顯示的數據也和 vmstat 的輸出相差甚遠,查詢 pidstat 手冊之后發(fā)現,加上-t之后,才會輸出線程相關的數據。我們加上-t選項看下:

# 每隔1秒輸出一組數據(需要 Ctrl+C 才結束)
# -wt 參數表示輸出線程的上下文切換指標
10:01:54 AM     0         -     67134  15639.62  82800.00  |__sysbench
10:01:54 AM     0         -     67135  17024.53  82316.98  |__sysbench
10:01:54 AM     0         -     67136  22240.57  77202.83  |__sysbench
10:01:54 AM     0         -     67137  15895.28  82843.40  |__sysbench
10:01:54 AM     0         -     67138  22740.57  79833.02  |__sysbench
10:01:54 AM     0         -     67139  14195.28  91814.15  |__sysbench
10:01:54 AM     0         -     67140  19863.21  84233.96  |__sysbench
10:01:54 AM     0         -     67141  17538.68  81353.77  |__sysbench
10:01:54 AM     0         -     67142  14560.38  80265.09  |__sysbench
10:01:54 AM     0         -     67143  19767.92  72928.30  |__sysbench

從上面的數據來看,雖然 sysbench進程的上下文切換次數不多,但是,它的子線程切換卻有很多,所以,上下文切換的罪魁禍首,就是 sysbench 線程了。

還有一個問題也別忘記,我們用 vmstat 看的時候,發(fā)現中斷次數也上升到了 2w 多,那中斷的信息要怎么查看呢,答案是從/proc/interrupts文件中讀取:

# -d 參數表示高亮顯示變化的區(qū)域
watch -d cat /proc/interrupts
           CPU0       CPU1
...
LOC:    8239839    9176016   Local timer interrupts
...
RES:   17266140   17259862   Rescheduling interrupts
...

觀察一段時間,你可以發(fā)現,變化速度最快的是重調度中斷(RES),這個中斷類型表示,喚醒空閑狀態(tài)的 CPU 來調度新的任務運行。這是多處理器系統(tǒng)(SMP)中,調度器用來分散任務到不同 CPU 的機制,通常也被稱為處理器間中斷(Inter-Processor Interrupts,IPI)。其次為 時間中斷(LOC),也是因為進程時間片用完,被系統(tǒng)強制中斷。

綜合以上工具,我們可以系統(tǒng)的分析上下文切換和 cpu 負載的關系,比如:

  • 自愿上下文切換變多了,說明進程都在等待資源,有可能發(fā)生了 I/O 等其他問題;
  • 非自愿上下文切換變多了,說明進程都在被強制調度,也就是都在爭搶 CPU,說明 CPU 的確成了瓶頸;
  • 中斷次數變多了,說明 CPU 被中斷處理程序占用,還需要通過查看 /proc/interrupts 文件來分析具體的中斷類型。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容