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輸出的上下文切換次數,加起來也就幾百,比vmstat的130W+明顯小了太多。這是怎麼回事呢?難道是工具本身出了錯嗎?
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只是一個進程的性能分析工具,並不提供任何關於中斷的詳細信息,怎樣才能知道中斷髮生的類型呢?