椪髯郑空間
-
用戶態(tài)函數(shù)棧
主要用于用戶態(tài)的函數(shù)調(diào)用
image.png -
內(nèi)核棧
Linux 給每個 task 都分配了內(nèi)核棧,主要用于內(nèi)核態(tài)的函數(shù)調(diào)用
除了內(nèi)核棧赌结,還有內(nèi)核寄存器吃环,pt_regs主要應(yīng)用于態(tài)轉(zhuǎn)換內(nèi)核態(tài)時,保護(hù)應(yīng)用態(tài)上下文的(保存寄存器)。
image.png -
總結(jié)
image.png
上下文切換含義
什么是上下文
在每個任務(wù)運行前碳却,CPU 都需要知道任務(wù)從哪里加載队秩、又從哪里開始運行,也就是說昼浦,需要系統(tǒng)事先幫它設(shè)置好 CPU 寄存器和程序計數(shù)器(Program Counter馍资,PC)。
CPU 寄存器关噪,是 CPU 內(nèi)置的容量小鸟蟹、但速度極快的內(nèi)存。而程序計數(shù)器使兔,則是用來存儲 CPU 正在執(zhí)行的指令位置建钥、或者即將執(zhí)行的下一條指令位置。它們都是 CPU 在運行任何任務(wù)前虐沥,必須的依賴環(huán)境锦针,因此也被叫做 CPU 上下文。
下文是指某一時間點CPU寄存器(CPU register)和程序計數(shù)器(PC)的內(nèi)容, 廣義上還包括內(nèi)存中進(jìn)程的虛擬地址映射信息.
什么是上下文切換
CPU 上下文切換置蜀,就是先把前一個任務(wù)的 CPU 上下文(也就是 CPU 寄存器和程序計數(shù)器)保存起來奈搜,然后加載新任務(wù)的上下文到這些寄存器和程序計數(shù)器,最后再跳轉(zhuǎn)到程序計數(shù)器所指的新位置盯荤,運行新任務(wù)馋吗。
而這些保存下來的上下文宅此,會存儲在系統(tǒng)內(nèi)核中中姜,并在任務(wù)重新調(diào)度執(zhí)行時再次加載進(jìn)來。這樣就能保證任務(wù)原來的狀態(tài)不受影響骂因,讓任務(wù)看起來還是連續(xù)運行灼卢。
上下文切換場景
- 系統(tǒng)調(diào)用
Linux 按照特權(quán)等級绍哎,把進(jìn)程的運行空間分為內(nèi)核空間和用戶空間,分別對應(yīng)著下圖中鞋真, CPU 特權(quán)等級的 Ring 0 和 Ring 3崇堰。
- 內(nèi)核空間(Ring 0)具有最高權(quán)限,可以直接訪問所有資源涩咖;
-
用戶空間(Ring 3)只能訪問受限資源海诲,不能直接訪問內(nèi)存等硬件設(shè)備,必須通過系統(tǒng)調(diào)用陷入到內(nèi)核中檩互,才能訪問這些特權(quán)資源特幔。
image.png
換個角度看,也就是說闸昨,進(jìn)程既可以在用戶空間運行蚯斯,又可以在內(nèi)核空間中運行薄风。進(jìn)程在用戶空間運行時,被稱為進(jìn)程的用戶態(tài)拍嵌,而陷入內(nèi)核空間的時候村刨,被稱為進(jìn)程的內(nèi)核態(tài)。從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)變撰茎,需要通過系統(tǒng)調(diào)用來完成。內(nèi)核空間態(tài)資源包括內(nèi)核的堆棧打洼、寄存器等龄糊;用戶空間態(tài)資源包括虛擬內(nèi)存、棧募疮、變量炫惩、正文、數(shù)據(jù)等阿浓。
image.png
CPU 寄存器里原來用戶態(tài)的指令位置他嚷,需要先保存起來。接著芭毙,為了執(zhí)行內(nèi)核態(tài)代碼筋蓖,CPU 寄存器需要更新為內(nèi)核態(tài)指令的新位置。最后才是跳轉(zhuǎn)到內(nèi)核態(tài)運行內(nèi)核任務(wù)退敦。
而系統(tǒng)調(diào)用結(jié)束后粘咖,CPU 寄存器需要恢復(fù)原來保存的用戶態(tài),然后再切換到用戶空間侈百,繼續(xù)運行進(jìn)程瓮下。所以,一次系統(tǒng)調(diào)用的過程钝域,其實是發(fā)生了兩次 CPU 上下文切換讽坏。
image.png
-
進(jìn)程切換
進(jìn)程是由內(nèi)核來管理和調(diào)度的,進(jìn)程的切換只能發(fā)生在內(nèi)核態(tài)例证。所以路呜,進(jìn)程的上下文不僅包括了虛擬內(nèi)存、棧织咧、全局變量等用戶空間的資源拣宰,還包括了內(nèi)核堆棧、寄存器等內(nèi)核空間的狀態(tài)烦感。因此巡社,進(jìn)程的上下文切換就比系統(tǒng)調(diào)用時多了一步:在保存當(dāng)前進(jìn)程的內(nèi)核狀態(tài)和 CPU 寄存器之前,需要先把該進(jìn)程的虛擬內(nèi)存手趣、棧等保存下來晌该;而加載了下一進(jìn)程的內(nèi)核態(tài)后肥荔,還需要刷新進(jìn)程的虛擬內(nèi)存和用戶棧。即除了cpu上下文切換朝群,還有進(jìn)程空間虛擬內(nèi)存切換燕耿。
如下圖所示,保存上下文和恢復(fù)上下文的過程并不是“免費”的姜胖,需要內(nèi)核在 CPU 上運行才能完成誉帅。
image.png
每次上下文切換都需要幾十納秒到數(shù)微秒的 CPU 時間。這個時間還是相當(dāng)可觀的右莱,特別是在進(jìn)程上下文切換次數(shù)較多的情況下蚜锨,很容易導(dǎo)致 CPU 將大量時間耗費在寄存器、內(nèi)核棧以及虛擬內(nèi)存等資源的保存和恢復(fù)上慢蜓,進(jìn)而大大縮短了真正運行進(jìn)程的時間亚再。
另外,Linux 通過 TLB(Translation Lookaside Buffer)來管理虛擬內(nèi)存到物理內(nèi)存的映射關(guān)系晨抡。當(dāng)虛擬內(nèi)存更新后氛悬,TLB 也需要刷新,內(nèi)存的訪問也會隨之變慢耘柱。特別是在多處理器系統(tǒng)上如捅,緩存是被多個處理器共享的,刷新緩存不僅會影響當(dāng)前處理器的進(jìn)程调煎,還會影響共享緩存的其他處理器的進(jìn)程伪朽。 同進(jìn)程不同線程間切換
在切換時,虛擬內(nèi)存這些資源就保持不動汛蝙,只需要切換線程的私有數(shù)據(jù)烈涮、寄存器等不共享的數(shù)據(jù)中斷上下文切換
為了快速響應(yīng)硬件的事件,中斷處理會打斷進(jìn)程的正常調(diào)度和執(zhí)行窖剑,轉(zhuǎn)而調(diào)用中斷處理程序坚洽,響應(yīng)設(shè)備事件。而在打斷其他進(jìn)程時西土,就需要將進(jìn)程當(dāng)前的狀態(tài)保存下來讶舰,這樣在中斷結(jié)束后,進(jìn)程仍然可以從原來的狀態(tài)恢復(fù)運行需了。
跟進(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ù)等涮俄。
對同一個 CPU 來說,中斷處理比進(jìn)程擁有更高的優(yōu)先級尸闸,所以中斷上下文切換并不會與進(jìn)程上下文切換同時發(fā)生彻亲。同樣道理,由于中斷會打斷正常進(jìn)程的調(diào)度和執(zhí)行吮廉,所以大部分中斷處理程序都短小精悍苞尝,以便盡可能快的執(zhí)行結(jié)束。總結(jié)
正常情況下茧痕,損耗對比關(guān)系如下:
進(jìn)程上下文切換>同進(jìn)程線程上下文切換>中斷上下文切換>內(nèi)核模式切換>協(xié)程上下文切換>用戶態(tài)函數(shù)調(diào)用上下文切換
上下文切換定量查看
- 系統(tǒng)層面
vmstat 是一個常用的系統(tǒng)性能分析工具,主要用來分析系統(tǒng)的內(nèi)存使用情況恼除,也常用來分析 CPU 上下文切換和中斷的次數(shù)踪旷。
# 每隔5秒輸出1組數(shù)據(jù)
$ vmstat 5
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 7005360 91564 818900 0 0 0 0 25 33 0 0 100 0 0
- cs(context switch)是每秒上下文切換的次數(shù)。
- in(interrupt)則是每秒中斷的次數(shù)豁辉。
- r(Running or Runnable)是就緒隊列的長度令野,也就是正在運行和等待 CPU 的進(jìn)程數(shù)。
- b(Blocked)則是處于不可中斷睡眠狀態(tài)的進(jìn)程數(shù)徽级。
上下文切換過多气破,體現(xiàn)在CPU使用率上是sys%升高。
- 進(jìn)程線程層面
vmstat 只給出了系統(tǒng)總體的上下文切換情況餐抢,要想查看每個進(jìn)程的詳細(xì)情況现使,就需要使用我們前面提到過的 pidstat 了。給它加上 -w 選項旷痕,你就可以查看每個進(jìn)程上下文切換的情況了碳锈。加上-t選項,可以查看線程上下文切換情況欺抗。
# 每隔5秒輸出1組數(shù)據(jù)
$ pidstat -w 5
Linux 4.15.0 (ubuntu) 09/23/18 _x86_64_ (2 CPU)
08:18:26 UID PID cswch/s nvcswch/s Command
08:18:31 0 1 0.20 0.00 systemd
08:18:31 0 8 5.40 0.00 rcu_sched
...
cswch 售碳,表示每秒自愿上下文切換(voluntary context switches)的次數(shù),自愿上下文切換绞呈,是指進(jìn)程無法獲取所需資源贸人,導(dǎo)致的上下文切換。比如說佃声, I/O艺智、內(nèi)存等系統(tǒng)資源不足時,就會發(fā)生自愿上下文切換圾亏。
另一個則是 nvcswch 力惯,表示每秒非自愿上下文切換(non voluntary context switches)的次數(shù)碗誉。非自愿上下文切換,則是指進(jìn)程由于時間片已到等原因父晶,被系統(tǒng)強制調(diào)度哮缺,進(jìn)而發(fā)生的上下文切換。比如說甲喝,大量進(jìn)程都在爭搶 CPU 時尝苇,就容易發(fā)生非自愿上下文切換。
示例
構(gòu)造多線程應(yīng)用并啟動
- 查看系統(tǒng)cpu使用情況及上下文切換情況
# 每隔1秒輸出1組數(shù)據(jù)(需要Ctrl+C才結(jié)束)
$ 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
6 0 0 6487428 118240 1292772 0 0 0 0 9019 1398830 16 84 0 0 0
8 0 0 6487428 118240 1292772 0 0 0 0 10191 1392312 16 84 0 0 0
- 查看CPU 和進(jìn)程上下文切換的情況
# 每隔1秒輸出1組數(shù)據(jù)(需要 Ctrl+C 才結(jié)束)
# -w參數(shù)表示輸出進(jìn)程切換指標(biāo)埠胖,而-u參數(shù)則表示輸出CPU使用指標(biāo)
$ pidstat -w -u 1
08:06:33 UID PID %usr %system %guest %wait %CPU CPU Command
08:06:34 0 10488 30.00 100.00 0.00 0.00 100.00 0 sysbench
08:06:34 0 26326 0.00 1.00 0.00 0.00 1.00 0 kworker/u4:2
08:06:33 UID PID cswch/s nvcswch/s Command
08:06:34 0 8 11.00 0.00 rcu_sched
08:06:34 0 16 1.00 0.00 ksoftirqd/1
08:06:34 0 471 1.00 0.00 hv_balloon
08:06:34 0 1230 1.00 0.00 iscsid
08:06:34 0 4089 1.00 0.00 kworker/1:5
08:06:34 0 4333 1.00 0.00 kworker/0:3
08:06:34 0 10499 1.00 224.00 pidstat
08:06:34 0 26326 236.00 0.00 kworker/u4:2
08:06:34 1000 26784 223.00 0.00 sshd
- 查看線程上下文切換狀況
# 每隔1秒輸出一組數(shù)據(jù)(需要 Ctrl+C 才結(jié)束)
# -wt 參數(shù)表示輸出線程的上下文切換指標(biāo)
$ pidstat -wt 1
08:14:05 UID TGID TID cswch/s nvcswch/s Command
...
08:14:05 0 10551 - 6.00 0.00 sysbench
08:14:05 0 - 10551 6.00 0.00 |__sysbench
08:14:05 0 - 10552 18911.00 103740.00 |__sysbench
08:14:05 0 - 10553 18915.00 100955.00 |__sysbench
08:14:05 0 - 10554 18827.00 103954.00 |__sysbench
...
- 查看中斷狀況
# -d 參數(shù)表示高亮顯示變化的區(qū)域
$ watch -d cat /proc/interrupts
CPU0 CPU1
...
RES: 2450431 5279697 Rescheduling interrupts
...
變化速度最快的是重調(diào)度中斷(RES)糠溜,這個中斷類型表示,喚醒空閑狀態(tài)的 CPU 來調(diào)度新的任務(wù)運行直撤。這是多處理器系統(tǒng)(SMP)中非竿,調(diào)度器用來分散任務(wù)到不同 CPU 的機制,通常也被稱為處理器間中斷(Inter-Processor Interrupts谋竖,IPI)红柱。所以,這里的中斷升高還是因為過多任務(wù)的調(diào)度問題蓖乘。
- 總結(jié)
每秒上下文切換多少次才算正常呢锤悄?這個數(shù)值其實取決于系統(tǒng)本身的 CPU 性能。如果系統(tǒng)的上下文切換次數(shù)比較穩(wěn)定嘉抒,那么從數(shù)百到一萬以內(nèi)零聚,都應(yīng)該算是正常的。但當(dāng)上下文切換次數(shù)超過一萬次些侍,或者切換次數(shù)出現(xiàn)數(shù)量級的增長時隶症,就很可能已經(jīng)出現(xiàn)了性能問題。
這時岗宣,還需要根據(jù)上下文切換的類型沿腰,再做具體分析。比方說:
- 自愿上下文切換變多了狈定,說明進(jìn)程都在等待資源颂龙,有可能發(fā)生了 I/O 等其他問題;
- 非自愿上下文切換變多了纽什,說明進(jìn)程都在被強制調(diào)度措嵌,也就是都在爭搶 CPU,說明 CPU 的確成了瓶頸芦缰;
- 中斷次數(shù)變多了企巢,說明 CPU 被中斷處理程序占用,還需要通過查看 /proc/interrupts 文件來分析具體的中斷類型让蕾。