透過現象看本質 CPU上下文切換

CPU上下文切換是保證 Linux系統正常工作的一個核心功能,按照不同場景,可以分爲進程上下文切換、線程上下文切換和中斷上下文切換。究竟怎麼分析CPU上下文切換的問題。

過多的上下文切換,會把CPU時間消耗在寄存器、內核棧以及虛擬內存等數據的保存和恢復上,縮短進程真正運行的時間,成了系統性能大幅下降的一個元兇。

既然上下文切換對系統性能影響那麼大,到底要怎麼査看上下文切換呢?可以使用vmstat這個工具,來查詢系統的上下文切換情況。

vmstat常用的系統性能分析工具,主要用來分析系統的內存使用情況,也常用來分析 CPU上下文切換和中斷的次數。

[root@www ~]# vmstat 2
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 391648   2076 286040    0    0    69    52   70   66  1  1 99  0  0
 0  0      0 391656   2076 286040    0    0     0     0   49   42  0  0 100  0  0
 0  0      0 391656   2076 286040    0    0     0     0   44   38  0  0 100  0  0
^C
  • procs(進程)
    • r當前運行隊列中線程的數目,代表線程處於可運行狀態,但CPU還未能執行,這個值可以作爲判斷CPU是否繁忙的一個指標;當這個值超過了CPU數目,就會出現CPU瓶頸了;這個我們可以結合top命令的負載值同步評估系統性能(等待運行的進程數((Running or Runnable)是就緒隊列的長度,也就是正在運行和等待CPU的進程數))
    • b:處在非中斷睡眠狀態的進程數
  • system(系統)這2個值越大,會看到由內核消耗的CPU時間會越大
    • in(interrupt)則是每秒中斷的次數,包括時鐘中斷
    • cs: (context switch)是每秒上下文切換的次數
  • cpu(以百分比表示)
    • us用戶進程執行時間(user time);
    • sy系統進程執行時間(system time);
    • id:空閒時間(包括IO等待時間);
    • wa:等待IO時間;wa的值高時,說明IO等待比較嚴重,這可能由於磁盤大量作隨機訪問造成,也有可能磁盤出現瓶頸。

r: 表示運行隊列(就是說多少個進程真的分配到CPU),我測試的服務器目前CPU比較空閒,沒什麼程序在跑,當這個值超過了CPU數目,就會出現CPU瓶頸了。這個也和top的負載有關係,一般負載超過了3就比較高,超過了5就高,超過了10就不正常了,服務器的狀態很危險。top的負載類似每秒的運行隊列。如果運行隊列過大,表示你的CPU很繁忙,一般會造成CPU使用率很高。

cs:每秒上下文切換次數,例如我們調用系統函數,就要進行上下文切換,線程的切換,也要進程上下文切換,這個值要越小越好,太大了,要考慮調低線程或者進程的數目,例如在apache和nginx這種web服務器中,我們一般做性能測試時會進行幾千併發甚至幾萬併發的測試,選擇web服務器的進程可以由進程或者線程的峯值一直下調,壓測,直到cs到一個比較小的值,這個進程和線程數就是比較合適的值了。系統調用也是,每次調用系統函數,我們的代碼就會進入內核空間,導致上下文切換,這個是很耗資源,也要儘量避免頻繁調用系統函數。上下文切換次數過多表示你的CPU大部分浪費在上下文切換,導致CPU幹正經事的時間少了,CPU沒有充分利用,是不可取的。

 vmstat只給出了系統總體的上下文切換情況,要想查看每個進程的詳細情況,就需要使用pidstat 了。給它加上-w選項,你就可以查看每個進程上下文切換的情況了。

[root@www ~]# pidstat -w 5
Linux 3.10.0-693.el7.x86_64 (www.lutixia.com) 	07/02/2020 	_x86_64_	(2 CPU)

08:17:53 PM   UID       PID   cswch/s nvcswch/s  Command
08:17:58 PM     0         3      0.40      0.00  ksoftirqd/0
08:17:58 PM     0         7      0.40      0.00  migration/0
08:17:58 PM     0         9      1.20      0.00  rcu_sched
08:17:58 PM     0        10      0.20      0.00  watchdog/0
08:17:58 PM     0        11      0.20      0.00  watchdog/1
08:17:58 PM     0        12      0.20      0.00  migration/1

這個結果中有兩列內容是我們的重點關注對象。

一個是cswch,表示每秒自願上下文切換 (voluntary context switches)的次數

另一個則是nvcswch ,表示每秒非自願上下文切換 (non voluntary context switches)的次數

・所謂自願上下文切換,是指進程無法獲取所需資源,導致的上下文切換。比如說,I/O、內存等系統資源不足時,就會發生自願上下文切換。

非自願上下文切換,則是指進程由於時間片巳到等原因,被系統強制調度,進而發生的上下文切換。比如說,大量進程都在爭搶CPU時,就容易發生非自願上下文切換。

 

這兩列如果數值比較大意味着不同的性能問題:

  • 自願上下文切換時說明進程在等待資源,有可能發生了I/O等問題
  • 非自願上下文切換,說明進程在被強制調度,也就是在爭搶CPU
  • 中斷次數多了,說明CPU在被中斷處理程序佔用。可以通過/proc/interrupts 查看



透過現象看本質


環境準備

使用sysbench來模擬系統多線程調度切換的情況。

sysbench是一個多線程的基準測試工具,一般用來評估不同系統參數下的MySQL數據庫庫負載情況。這次案例中,我們只把它當成異常進程來看,作用是模擬上下文切換過多的問題。

下面的案例基於Centos 7.X,當然,其他的Linux系統同樣適用。環境如下所示:

•機器配置:2 CPU, 1GB內存

預先安裝 sysbench sysstat包,如 yum install epel* sysbench sysstat -y

操作開始前,你需要打開三個終端,登錄到同一臺Linux,並安裝好上面兩個軟件包。安裝完成後,你可以先用vmstat看一下空閒系統的上下文切次數:

[root@www ~]# vmstat 2 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 384484   2076 292804    0    0    31    25   46   41  0  0 99  0  0

可以看到上下文切換次數是cs 46,中斷次數是25,r和b都是0因爲系統比較空閒,沒有運行比較繁忙的任務。

首先,在第一個終端裏運行sysbench ,模擬系統多線程調度的瓶頸:

[root@www ~]# sysbench --threads=10 --max-time=300 threads run
WARNING: --max-time is deprecated, use --time instead
sysbench 1.0.17 (using system LuaJIT 2.0.4)

Running the test with following options:
Number of threads: 10
Initializing random number generator from current time
Initializing worker threads...

Threads started!


#PID爲2的父親進程
[root@www ~]# ps -ef 
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 20:02 ?        00:00:01 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
root          2      0  0 20:02 ?        00:00:00 [kthreadd]

#可以看出PID爲2的父親進程生成了10個子線程
[root@www ~]# ps -ef | grep kwor*
root          5      2  0 20:02 ?        00:00:00 [kworker/0:0H]
root          6      2  0 20:02 ?        00:00:00 [kworker/u256:0]
root         15      2  0 20:02 ?        00:00:00 [kworker/1:0H]
root         25      2  0 20:02 ?        00:00:04 [kworker/0:1]
root        252      2  0 20:02 ?        00:00:00 [kworker/u256:2]
root        507      2  0 20:02 ?        00:00:00 [kworker/u257:0]
root        512      2  0 20:02 ?        00:00:00 [kworker/u257:1]
root        778      2  0 20:02 ?        00:00:00 [kworker/1:1H]
root       1068      2  0 20:02 ?        00:00:00 [kworker/0:1H]
root       1213      2  0 20:37 ?        00:00:00 [kworker/1:2]
root       1250      2  0 20:47 ?        00:00:00 [kworker/0:0]
root       1252      2  0 20:48 ?        00:00:00 [kworker/1:1]
root       1258      2  0 20:52 ?        00:00:00 [kworker/0:2]
root       1273      2  0 20:55 ?        00:00:00 [kworker/1:0]

vmstat系統層面定位問題

接着,在第二個終端運行vmstat,觀察上下文切換情況:

[root@www ~]# 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
 9  0      0 381900   2076 293176    0    0    29    23  240 3698  0  2 98  0  0
 7  0      0 381892   2076 293176    0    0     0     0 23350 1354895  7 91  3  0  0
 8  0      0 381892   2076 293176    0    0     0     0 25311 1337914  7 91  2  0  0
 6  0      0 381892   2076 293176    0    0     0     0 25632 1440565  8 91  2  0  0
^C

你應該可以發現,CS列的上下文切換次數從之前的46驟然上升到了 130W+。同時,注意觀察 其他幾個指標:

• r列:就緒隊列的長度已經到了 6+,遠遠超過了系統CPU的個數 2,所以肯定會有大量的 CPU競爭

• us (user)和sy (system)列:這兩列的CPU使用率加起來上升到了100%(91+7 91+8 98+2),其中系統 CPU使用率,也就是sy列高達91%,說明CPU主要是被內核佔用了

  • in列中斷次數也上升到了 2W+左右,說明中斷處理也是個潛在的問題

 

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

 

pidstat具體定位vmstat產生的問題,縮小範圍定位問題 

那麼到底是什麼進程導致了這些問題呢?第三個終端再用pidstat來看一下,CPU和進程上下文切換的情況

[root@www ~]# uptime 
 22:08:21 up  2:05,  3 users,  load average: 5.97, 2.90, 2.00

#-w表示進程切換指標,-u表示CPU使用率
[root@www ~]# pidstat -w -u 1
Linux 3.10.0-693.el7.x86_64 (www.lutixia.com)     07/02/2020     _x86_64_    (2 CPU)

08:54:57 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
08:54:58 PM     0       689    0.00    0.96    0.00    0.96     1  vmtoolsd
08:54:58 PM     0      1259   12.50  100.00    0.00  100.00     1  sysbench
08:54:58 PM     0      1272    0.00    0.96    0.00    0.96     0  pidstat

08:54:57 PM   UID       PID   cswch/s nvcswch/s  Command
08:54:58 PM     0         3      1.92      0.00  ksoftirqd/0
08:54:58 PM     0         9     17.31      0.00  rcu_sched
08:54:58 PM     0        25      0.96      0.00  kworker/0:1
08:54:58 PM     0       252      0.96      0.00  kworker/u256:2
08:54:58 PM     0       689     10.58      0.00  vmtoolsd
08:54:58 PM     0      1068      0.96      0.00  kworker/0:1H
08:54:58 PM     0      1213      1.92      0.00  kworker/1:2

pidstat的輸出你可以發現,CPU使用率的升高果然是sysbench導致的,它的CPU使用率已經達到了100%。但上下文切換則是來自其他進程,包括非自願上下文切換頻率最高的 pidstat,以及自願上下文切換頻率最高的內核線程kworker

pidstat輸出的上下文切換次數,加起來也就幾百,比vmstat130W+明顯小了太多。這是怎麼回事呢?難道是工具本身出了錯嗎?

Linux調度的基本單位實際上是線程,而我們的場景sysbench模擬的也是線程的調度問題,pidstat忽略了線程的數據,pidstat默認顯示進程的指標數據,加上-t參數後,纔會輸出線程的指標。

[root@www ~]# pidstat -wt 1
Linux 3.10.0-693.el7.x86_64 (www.lutixia.com) 	07/02/2020 	_x86_64_	(2 CPU)

09:18:38 PM   UID      TGID       TID   cswch/s nvcswch/s  Command
09:18:39 PM     0         9         -      3.96      0.00  rcu_sched
09:18:39 PM     0         -         9      3.96      0.00  |__rcu_sched
09:18:39 PM     0        13         -      1.98      0.00  ksoftirqd/1
09:18:39 PM     0         -        13      1.98      0.00  |__ksoftirqd/1
09:18:39 PM     0        25         -      2.97      0.00  kworker/0:1
09:18:39 PM     0         -        25      2.97      0.00  |__kworker/0:1
09:18:39 PM     0        32         -      0.99      0.00  khugepaged
09:18:39 PM     0         -        32      0.99      0.00  |__khugepaged
09:18:39 PM     0       252         -      0.99      0.00  kworker/u256:2
09:18:39 PM     0         -       252      0.99      0.00  |__kworker/u256:2
09:18:39 PM     0         -       706      0.99      0.00  |__in:imjournal
09:18:39 PM     0       689         -      9.90      0.00  vmtoolsd
09:18:39 PM     0         -       689      9.90      0.00  |__vmtoolsd
09:18:39 PM     0         -      1011      0.99      0.00  |__tuned
09:18:39 PM     0      1341         -      1.98      0.00  kworker/1:2
09:18:39 PM     0         -      1341      1.98      0.00  |__kworker/1:2
09:18:39 PM     0         -      1343  20120.79 122291.09  |__sysbench
09:18:39 PM     0         -      1344  13162.38 110521.78  |__sysbench
09:18:39 PM     0         -      1345  26090.10 119710.89  |__sysbench
09:18:39 PM     0         -      1346  23462.38  96952.48  |__sysbench
09:18:39 PM     0         -      1347  16571.29 107426.73  |__sysbench
09:18:39 PM     0         -      1348  22124.75 116013.86  |__sysbench
09:18:39 PM     0         -      1349  20953.47 114319.80  |__sysbench
09:18:39 PM     0         -      1350  21472.28 108875.25  |__sysbench
09:18:39 PM     0         -      1351  17744.55 120934.65  |__sysbench
09:18:39 PM     0         -      1352  15824.75 108979.21  |__sysbench
09:18:39 PM     0      1354         -      0.99      0.00  kworker/0:0
09:18:39 PM     0         -      1354      0.99      0.00  |__kworker/0:0
09:18:39 PM     0      1355         -      0.99      0.00  pidstat
09:18:39 PM     0         -      1355      0.99      0.00  |__pidstat

現在你就能看到了,雖然sysbench進程(也就是主線程)的上下文切換次數看起來並不多,但它的子線程的上下文切換次數卻有很多。看來上下文切換罪魁禍首,還是過多的sysbench線程。

我們已經找到了上下文切換次數增多的根源,那是不是到這兒就可以結束了呢?

當然不是。不知道你還記不記得,前面在觀察系統指標時,除了上下文切換頻率驟然升高,還有一個指標也有很大的變化。是的,正是中斷次數。中斷次數也上升到了2W+,但到底是什麼類型的中斷上升了,現在還不清楚。

既然是中斷,我們都知道,它只發生在內核態,而pidstat只是一個進程的性能分析工具,並不提供任何關於中斷的詳細信息,怎樣才能知道中斷髮生的類型呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章