CPU的性能瓶頸不僅僅是cpu負(fù)載枫甲。因?yàn)長inux的性能問題可能是牽一發(fā)而動全身的源武。
比如一個占用內(nèi)存較高的java程序,導(dǎo)致問題的根本原因是內(nèi)存不足想幻,但是反映最直觀的可能是cpu使用率很高粱栖。因?yàn)閖ava開啟了大量的線程進(jìn)行GC操作。進(jìn)而導(dǎo)致cpu使用率高脏毯,平均負(fù)載也隨之升高闹究。所以問題的關(guān)鍵還是追根溯源。再比如進(jìn)程在競爭CPU的時候并沒有真正的運(yùn)行食店,但是為什么還會導(dǎo)致系統(tǒng)平均負(fù)載升高渣淤,這就是上下文切換導(dǎo)致的了。
CPU上下文切換
Linux 是一個多任務(wù)的操作系統(tǒng)吉嫩,它支持遠(yuǎn)大于CPU數(shù)量的任務(wù)同時運(yùn)行价认,當(dāng)然并不是真正的同時運(yùn)行,是每個任務(wù)輪流執(zhí)行CPU分給他們的時間片自娩,讓人感覺是同時在運(yùn)行用踩。
每一個任務(wù)運(yùn)行前,CPU都需要知道任務(wù)從哪里加載,又從哪里運(yùn)行捶箱,也就是說智什,需要系統(tǒng)事先設(shè)置好CPU寄存器。
CPU寄存器包含指令寄存器(IR)和程序計數(shù)器(PC)丁屎。他們用來暫存指令荠锭,數(shù)據(jù)和地址,程序運(yùn)行的下一條指令地址晨川,這些都是任務(wù)運(yùn)行時的必要環(huán)境证九。因此也被稱作CPU上下文
上下文切換就是把前一個任務(wù)的CPU上下文保存起來,然后加載新任務(wù)的上下文到這些指令寄存器(IR)和程序寄存器(PC)等寄存器中共虑。這些被保存下來的上下文會存儲在操作系統(tǒng)的內(nèi)核中愧怜,等待任務(wù)重新調(diào)度執(zhí)行時再次加載進(jìn)來,這樣就能保證任務(wù)的原來狀態(tài)不受影響妈拌,讓任務(wù)看起來是連續(xù)運(yùn)行的拥坛。
根據(jù)場景不同,CPU的上下文切換又分為進(jìn)程上下文切換尘分,線程上下文切換以及中斷上下文切換猜惋。
進(jìn)程上下文切換
在介紹進(jìn)程上下文切換前,需要先了解進(jìn)程執(zhí)行過程中所涉及到的CPU上下文切換培愁,我們稱之為特權(quán)模式切換著摔。
Linux 按照特權(quán)等級,將進(jìn)程的運(yùn)行空間分為內(nèi)核空間和用戶空間定续。對應(yīng)的是CPU的環(huán)0(Ring 0) 和 環(huán)3(Ring3)谍咆。(環(huán)2和環(huán)1,Linux沒用到)
- 內(nèi)核空間(Ring0)具有最高權(quán)限私股,可以訪問所有資源摹察。
- 用戶空間(Ring3)只能訪問受限資源,想要訪問物理設(shè)備需要陷入內(nèi)核態(tài)中庇茫,在內(nèi)核空間(Ring3)中港粱,才可以訪問特權(quán)資源。
那么從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)變就發(fā)生一次特權(quán)模式切換旦签,如從磁盤上讀取一個文件,就發(fā)生了一次內(nèi)核調(diào)用寸宏,也就發(fā)生一次特權(quán)模式切換宁炫。CPU需要將寄存器中的用戶態(tài)的指令位置保存起來,截至執(zhí)行內(nèi)核態(tài)的代碼氮凝,CPU寄存器需要更新為內(nèi)核態(tài)的新位置羔巢,最后跳轉(zhuǎn)到內(nèi)核態(tài)執(zhí)行內(nèi)核調(diào)用。之后再恢復(fù)之前的用戶態(tài)。這樣的一次系統(tǒng)調(diào)用過程實(shí)際上發(fā)生了兩次CPU上下文切換竿秆。
那這就是進(jìn)程上下文切換嗎启摄?不是的,進(jìn)程上下文切換只是說一個進(jìn)程切換到另一個進(jìn)程上去幽钢。
那特權(quán)模式切換和進(jìn)程上下文切換有什么區(qū)別嗎歉备?
首先進(jìn)程的管理是有內(nèi)核進(jìn)行管理和調(diào)度的。進(jìn)程的切換只能發(fā)生在內(nèi)核態(tài)匪燕,所以蕾羊,進(jìn)程的上下文切換不僅僅包括了虛擬內(nèi)存,棧帽驯,全局變量等用戶空間資源龟再,還包括了內(nèi)核態(tài)堆棧,寄存器等內(nèi)核空間狀態(tài)尼变。
特別需要注意的是操作系統(tǒng)會將當(dāng)前任務(wù)的虛擬內(nèi)存一并保存利凑。而Linux中通過TLB來管理虛擬內(nèi)存到物理內(nèi)存的映射關(guān)系。TLB用于虛擬地址與實(shí)地址之間的交互嫌术,提供一個尋找實(shí)地址的緩存區(qū)截碴,能夠有效減少尋找物理地址所消耗時間。當(dāng)虛擬內(nèi)存被刷新后蛉威,TLB也會被更新日丹。如果沒有TLB,則每次取數(shù)據(jù)都需要兩次訪問內(nèi)存蚯嫌,即查頁表獲得物理地址和取數(shù)據(jù)哲虾。在多核的技術(shù)下,這會極大的降低程序的執(zhí)行效率择示。因?yàn)榫彺鍸3 Cache 是被所有核共享的束凑。當(dāng)TLB被更新后,緩存中的TLB數(shù)據(jù)會失效栅盲,每個CPU都需要從主存中重新載入汪诉,一個進(jìn)程的上下文切換,同時可能影響其他CPU核心上的進(jìn)程的執(zhí)行效率谈秫。
當(dāng)需要進(jìn)程調(diào)度的時候扒寄,會需要切換上下文,Linux為每個CPU維護(hù)一個就緒隊(duì)列拟烫,將活躍的進(jìn)程按照優(yōu)先級和等待CPU的時間排序该编,然后選擇需要CPU的進(jìn)程(優(yōu)先級高或者等待時間最長的進(jìn)程)來運(yùn)行。
什么時候會發(fā)生進(jìn)程調(diào)度硕淑?
- 進(jìn)程的CPU時間片耗盡课竣,被系統(tǒng)掛起嘉赎,切換到其他等待CPU的進(jìn)程運(yùn)行。
- 進(jìn)程所需要的系統(tǒng)資源不足于樟。要等待資源滿足后才可以運(yùn)行公条。這個時候會被系統(tǒng)掛起。
- 進(jìn)程通過sleep函數(shù)主動將自己掛起迂曲。
- 當(dāng)有優(yōu)先級更高的進(jìn)程運(yùn)行時靶橱,當(dāng)前進(jìn)程會被掛起,由高優(yōu)先級的進(jìn)程運(yùn)行奢米。
- 硬中斷發(fā)生時抓韩,CPU上的進(jìn)程會被掛起,轉(zhuǎn)而執(zhí)行內(nèi)核的中斷服務(wù)程序鬓长。
線程上下文切換
線程的上下文切換就十分的簡單了谒拴,線程是調(diào)度的基本單位,而進(jìn)程是資源擁有的基本單位涉波。
- 當(dāng)進(jìn)程只有一個線程時英上,進(jìn)程可理解就是線程。
- 當(dāng)進(jìn)程擁有多個線程時啤覆,線程會共享虛擬內(nèi)存和全局變量等資源苍日,這些資源在上下文切換中不需要修改。
- 線程的上下文切換也需要保存自己的一些數(shù)據(jù)窗声,比如棧相恃,寄存器。這些在上下文切換時是需要保存的笨觅。
也就是當(dāng)
1拦耐、兩個不同進(jìn)程的線程上下文切換時,此時的切換構(gòu)成和進(jìn)程上下文切換一樣见剩。
2杀糯、兩個線程處于同一進(jìn)程時,切換只需要切換棧苍苞,寄存器等少部分資源固翰。
中斷上下文切換
中斷時為了快速響應(yīng)硬件事件的,跟進(jìn)程上下文不同羹呵,中斷上下文不涉及進(jìn)程的用戶態(tài)骂际。即便打斷的是一個用戶態(tài)的進(jìn)程,也不需要保存和恢復(fù)這個進(jìn)程的虛擬內(nèi)存担巩,全局變量等用戶態(tài)資源方援。中斷上下文只包括內(nèi)核態(tài)中斷服務(wù)程序執(zhí)行必需的狀態(tài)。CPU寄存器涛癌,內(nèi)核堆棧犯戏,硬件中斷參數(shù)。
查看系統(tǒng)的上下文切換情況
既然過多的上下文切換會把CPU的時間消耗在上下文環(huán)境的保存上拳话,并沒有充分利用其計算功能先匪。那就需要查看當(dāng)前系統(tǒng)的上下文切換情況了。
vmstat
查看系統(tǒng)的上下文切換情況
$ 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) 每秒的上下文切換次數(shù)
- in (interrupt) 每秒的中斷次數(shù)
- r (Runing or Runnable) 就緒隊(duì)列的長度弃衍,也就是正在運(yùn)行和等待CPU的進(jìn)程數(shù)呀非。
- b (Blocked) 不可中斷睡眠狀態(tài)的進(jìn)程數(shù)
pidstat
pidstat 可以看到具體的某個應(yīng)用程序的上下文切換情況。
$ 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) 自愿上下文切換镜盯,指的是進(jìn)程無法獲得所需的資源導(dǎo)致的上下文切換岸裙。比如I/O不足,內(nèi)存不足速缆。
- nvcswch (non voluntary context switches) 非自愿上下文切換降允,指的是 進(jìn)程由于時間片已到等原因,被系統(tǒng)強(qiáng)制調(diào)度艺糜,進(jìn)而發(fā)生上下文切換剧董。比如大量進(jìn)程在爭搶CPU
模擬上下文切換的場景
這里使用的sysbench,模擬操作系統(tǒng)的多線程調(diào)度瓶頸破停。以10個線程運(yùn)行5分鐘的基準(zhǔn)測試翅楼。模擬多線程切換
$ 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 瞬間上升(第一行是開機(jī)以來的參數(shù)的平均值)。
接下來觀察pidstat 真慢,這里需要加上 -t 參數(shù)毅臊,顯示線程,要不不帶參數(shù)黑界,看不到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 發(fā)生了大量的自愿上下文切換
$ 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
這是重調(diào)度中斷(RES),在喚醒空閑狀態(tài)的CPU來重新調(diào)度新任務(wù)運(yùn)行童社。具體可看 /proc/interrupts求厕。
小結(jié)
一般上下文切換在數(shù)百到一萬之內(nèi)上下文切換超過1萬,很可能遇到性能問題扰楼。需要具體看看了呀癣。
- 資源上下文切換時說明進(jìn)程在等待資源,有可能發(fā)生了I/O等問題弦赖;
- 非自愿上下文切換项栏,說明進(jìn)程在被強(qiáng)制調(diào)度,也就是在爭搶CPU蹬竖;
- 中斷次數(shù)多了沼沈,說明CPU在被中斷處理程序占用流酬。可以通過/proc/interrupts 查看列另。