CPU的性能瓶頸不僅僅是cpu負載。因爲Linux的性能問題可能是牽一髮而動全身的。
比如一個佔用內存較高的java程序,導致問題的根本原因是內存不足,但是反映最直觀的可能是cpu使用率很高。因爲java開啓了大量的線程進行GC操作。進而導致cpu使用率高,平均負載也隨之升高。所以問題的關鍵還是追根溯源。再比如進程在競爭CPU的時候並沒有真正的運行,但是爲什麼還會導致系統平均負載升高,這就是上下文切換導致的了。
CPU上下文切換
Linux 是一個多任務的操作系統,它支持遠大於CPU數量的任務同時運行,當然並不是真正的同時運行,是每個任務輪流執行CPU分給他們的時間片,讓人感覺是同時在運行。
每一個任務運行前,CPU都需要知道任務從哪裏加載,又從哪裏運行,也就是說,需要系統事先設置好CPU寄存器。
CPU寄存器包含指令寄存器(IR)和程序計數器(PC)。他們用來暫存指令,數據和地址,程序運行的下一條指令地址,這些都是任務運行時的必要環境。因此也被稱作CPU上下文
上下文切換就是把前一個任務的CPU上下文保存起來,然後加載新任務的上下文到這些指令寄存器(IR)和程序寄存器(PC)等寄存器中。這些被保存下來的上下文會存儲在操作系統的內核中,等待任務重新調度執行時再次加載進來,這樣就能保證任務的原來狀態不受影響,讓任務看起來是連續運行的。
根據場景不同,CPU的上下文切換又分爲進程上下文切換,線程上下文切換以及中斷上下文切換。
進程上下文切換
在介紹進程上下文切換前,需要先了解進程執行過程中所涉及到的CPU上下文切換,我們稱之爲特權模式切換。
Linux 按照特權等級,將進程的運行空間分爲內核空間和用戶空間。對應的是CPU的環0(Ring 0) 和 環3(Ring3)。(環2和環1,Linux沒用到)
- 內核空間(Ring0)具有最高權限,可以訪問所有資源。
- 用戶空間(Ring3)只能訪問受限資源,想要訪問物理設備需要陷入內核態中,在內核空間(Ring3)中,纔可以訪問特權資源。
那麼從用戶態到內核態的轉變就發生一次特權模式切換,如從磁盤上讀取一個文件,就發生了一次內核調用,也就發生一次特權模式切換。CPU需要將寄存器中的用戶態的指令位置保存起來,截至執行內核態的代碼,CPU寄存器需要更新爲內核態的新位置,最後跳轉到內核態執行內核調用。之後再恢復之前的用戶態。這樣的一次系統調用過程實際上發生了兩次CPU上下文切換。
那這就是進程上下文切換嗎?不是的,進程上下文切換隻是說一個進程切換到另一個進程上去。
那特權模式切換和進程上下文切換有什麼區別嗎?
首先進程的管理是有內核進行管理和調度的。進程的切換隻能發生在內核態,所以,進程的上下文切換不僅僅包括了虛擬內存,棧,全局變量等用戶空間資源,還包括了內核態堆棧,寄存器等內核空間狀態。
特別需要注意的是操作系統會將當前任務的虛擬內存一併保存。而Linux中通過TLB來管理虛擬內存到物理內存的映射關係。TLB用於虛擬地址與實地址之間的交互,提供一個尋找實地址的緩存區,能夠有效減少尋找物理地址所消耗時間。當虛擬內存被刷新後,TLB也會被更新。如果沒有TLB,則每次取數據都需要兩次訪問內存,即查頁表獲得物理地址和取數據。在多核的技術下,這會極大的降低程序的執行效率。因爲緩存L3 Cache 是被所有核共享的。當TLB被更新後,緩存中的TLB數據會失效,每個CPU都需要從主存中重新載入,一個進程的上下文切換,同時可能影響其他CPU核心上的進程的執行效率。
TLB 三級緩存.png
當需要進程調度的時候,會需要切換上下文,Linux爲每個CPU維護一個就緒隊列,將活躍的進程按照優先級和等待CPU的時間排序,然後選擇需要CPU的進程(優先級高或者等待時間最長的進程)來運行。
什麼時候會發生進程調度?
- 進程的CPU時間片耗盡,被系統掛起,切換到其他等待CPU的進程運行。
- 進程所需要的系統資源不足。要等待資源滿足後纔可以運行。這個時候會被系統掛起。
- 進程通過sleep函數主動將自己掛起。
- 當有優先級更高的進程運行時,當前進程會被掛起,由高優先級的進程運行。
- 硬中斷髮生時,CPU上的進程會被掛起,轉而執行內核的中斷服務程序。
線程上下文切換
線程的上下文切換就十分的簡單了,線程是調度的基本單位,而進程是資源擁有的基本單位。
- 當進程只有一個線程時,進程可理解就是線程。
- 當進程擁有多個線程時,線程會共享虛擬內存和全局變量等資源,這些資源在上下文切換中不需要修改。
- 線程的上下文切換也需要保存自己的一些數據,比如棧,寄存器。這些在上下文切換時是需要保存的。
也就是當
1、兩個不同進程的線程上下文切換時,此時的切換構成和進程上下文切換一樣。
2、兩個線程處於同一進程時,切換只需要切換棧,寄存器等少部分資源。
中斷上下文切換
中斷時爲了快速響應硬件事件的,跟進程上下文不同,中斷上下文不涉及進程的用戶態。即便打斷的是一個用戶態的進程,也不需要保存和恢復這個進程的虛擬內存,全局變量等用戶態資源。中斷上下文只包括內核態中斷服務程序執行必需的狀態。CPU寄存器,內核堆棧,硬件中斷參數。
查看系統的上下文切換情況
既然過多的上下文切換會把CPU的時間消耗在上下文環境的保存上,並沒有充分利用其計算功能。那就需要查看當前系統的上下文切換情況了。
vmstat
查看系統的上下文切換情況
$ 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
1 0 0 2453028 2108 1211540 0 0 3 10 27 35 0 0 99 0 0
0 0 0 2453004 2108 1211572 0 0 0 0 52 66 0 0 100 0 0
- cs (centext switch) 每秒的上下文切換次數
- in (interrupt) 每秒的中斷次數
- r (Runing or Runnable) 就緒隊列的長度,也就是正在運行和等待CPU的進程數。
- b (Blocked) 不可中斷睡眠狀態的進程數
pidstat
pidstat 可以看到具體的某個應用程序的上下文切換情況。
$ pidstat -w 1
Linux 3.10.0-957.el7.x86_64 (localhost.localdomain) 2019年03月23日 _x86_64_ (2 CPU)
13時51分31秒 UID PID cswch/s nvcswch/s Command
13時51分32秒 0 9 9.80 0.00 rcu_sched
13時51分32秒 0 11 0.98 0.00 watchdog/0
13時51分32秒 0 12 0.98 0.00 watchdog/1
13時51分32秒 0 481 0.98 0.00 kworker/1:3
13時51分32秒 0 4683 18.63 0.00 xfsaild/dm-0
13時51分32秒 0 9433 9.80 0.00 vmtoolsd
13時51分32秒 0 27261 0.98 0.00 kworker/u256:0
13時51分32秒 0 40878 2.94 0.00 kworker/0:1
13時51分32秒 0 40880 0.98 0.00 pidstat
- cswch (voluntary context switches) 自願上下文切換,指的是進程無法獲得所需的資源導致的上下文切換。比如I/O不足,內存不足。
- nvcswch (non voluntary context switches) 非自願上下文切換,指的是 進程由於時間片已到等原因,被系統強制調度,進而發生上下文切換。比如大量進程在爭搶CPU
模擬上下文切換的場景
這裏使用的sysbench,模擬操作系統的多線程調度瓶頸。以10個線程運行5分鐘的基準測試。模擬多線程切換
$ sysbench --threads=100 --max-time=300 threads run
觀察vmstat的輸出
$ 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
17 0 0 2442576 2108 1217720 0 0 3 10 27 80 0 0 99 0 0
11 0 0 2442576 2108 1217720 0 0 0 0 23882 1325686 10 89 1 0 0
10 0 0 2442576 2108 1217720 0 0 0 0 22815 1348568 11 89 0 0 0
17 0 0 2442576 2108 1217720 0 0 0 0 23978 1328481 10 89 1 0 0
11 0 0 2442576 2108 1217720 0 0 0 25 23081 1344558 11 89 0 0 0
14 0 0 2442576 2108 1217720 0 0 0 0 23804 1305193 11 89 1 0 0
19 0 0 2442576 2108 1217720 0 0 0 0 22418 1254798 11 89 0 0 0
可觀察到 cs 瞬間上升(第一行是開機以來的參數的平均值)。
接下來觀察pidstat ,這裏需要加上 -t 參數,顯示線程,要不不帶參數,看不到sysbench。
$ pidstat -u -w 1
平均時間: UID PID %usr %system %guest %CPU CPU Command
平均時間: 0 42213 0.00 0.05 0.00 0.05 - kworker/0:0
平均時間: 0 42218 19.82 100.00 0.00 100.00 - sysbench
平均時間: 0 42321 0.33 1.00 0.00 1.33 - pidstat
平均時間: UID PID cswch/s nvcswch/s Command
平均時間: 0 2 0.05 0.00 kthreadd
平均時間: 0 3 0.29 0.00 ksoftirqd/0
平均時間: 0 9 8.32 0.00 rcu_sched
平均時間: 0 11 0.24 0.00 watchdog/0
平均時間: 0 12 0.24 0.00 watchdog/1
平均時間: 0 14 0.90 0.00 ksoftirqd/1
平均時間: 0 37 0.10 0.00 khugepaged
加上-t後
可以看到 sysbench 發生了大量的自願上下文切換
$ pidstat -wut 1
平均時間: 0 27261 - 1.85 0.00 kworker/u256:0
平均時間: 0 - 27261 1.85 0.00 |__kworker/u256:0
平均時間: 997 - 27300 1.85 0.00 |__grafana-server
平均時間: 997 - 27304 0.93 0.00 |__grafana-server
平均時間: 997 - 27307 1.85 0.00 |__grafana-server
平均時間: 997 - 27341 1.85 0.00 |__grafana-server
平均時間: 0 - 42219 2879.63 11057.41 |__sysbench
平均時間: 0 - 42220 1902.78 13955.56 |__sysbench
平均時間: 0 - 42221 4470.37 11027.78 |__sysbench
平均時間: 0 - 42222 1792.59 17249.07 |__sysbench
平均時間: 0 - 42223 2542.59 7395.37 |__sysbench
平均時間: 0 - 42224 1183.33 16775.93 |__sysbench
平均時間: 0 - 42225 3678.70 12963.89 |__sysbench
平均時間: 0 - 42226 3208.33 15801.85 |__sysbench
平均時間: 0 - 42227 2602.78 13896.30 |__sysbench
通過dstat命令可以看到,除了有很多的資源上下文切換,還有很多中斷。
$ dstat
You did not select any stats, using -cdngy by default.
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read writ| recv send| in out | int csw
1 2 97 0 0 0|5683B 19k| 0 0 | 0 0 | 583 30k
8 85 7 0 0 0| 0 0 | 132B 1060B| 0 0 | 28k 977k
8 82 10 0 0 0| 0 0 | 132B 620B| 0 0 | 25k 1025k
這是重調度中斷(RES),在喚醒空閒狀態的CPU來重新調度新任務運行。具體可看 /proc/interrupts。
小結
一般上下文切換在數百到一萬之內上下文切換超過1萬,很可能遇到性能問題。需要具體看看了。
- 資源上下文切換時說明進程在等待資源,有可能發生了I/O等問題;
- 非自願上下文切換,說明進程在被強制調度,也就是在爭搶CPU;
- 中斷次數多了,說明CPU在被中斷處理程序佔用。可以通過/proc/interrupts 查看。
作者:OOM_Killer
鏈接:https://www.jianshu.com/p/1c80cd47e8cf
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。