Linux
是一個多任務操做系統,它支持同時運行的任務數量遠大於 CPU
個數。其實這些任務沒有真正的同時運行,是由於系統在很短的時間內,將 CPU
輪流分配給它們,形成多任務同時運行的錯覺。CPU
都須要知道任務從哪裏加載、從哪裏開始運行,須要系統事先設置好 CPU 寄存器
和程序計數器
。CPU
寄存器是 CPU
內置的容量小、速度極快的內存。而程序計數器則是用來存儲 CPU
正在執行的指令位置、或即將執行的下一條指令位置。它們都是 CPU
在運行任務前必須依賴的環境,也被叫作 CPU 上下文
。上下文切換
,就是先把前一個任務的 CPU
上下文保存起來,而後加載新任務的上下文到這些寄存器和程序計數器,最後再跳到程序計數器所指的新位置,運行新任務。而這些保存下來的上下文,會存儲在系統內核中,並在任務從新調度執行時再次加載進來。這樣就能保證任務原來的狀態不受影響,讓任務看起來仍是連續運行。根據任務的不一樣,CPU
的上下文切換能夠分爲幾個不一樣的場景,也就是:進程上下文切換、線程上下文切換、中斷上下文切換。html
一、用戶空間與內核空間linux
Linux
按照特權等級,把進程的運行空間分爲內核空間和用戶空間,分別對應着 CPU 特權等級的 Ring 0
和 Ring 3
。shell
Ring 0
)具備最高權限,能夠直接訪問全部資源。Ring 3
)只能訪問受限資源,不能直接訪問內存等硬件設備,必須經過系統調用陷入內核中才能訪問這些特權資源。二、系統調用數據庫
從用戶態到內核態的轉變,須要經過系統調用
來完成。好比查看文件時,須要執行屢次系統調用:open、read、write、close等。系統調用的過程以下:ubuntu
但系統調用的過程當中並不會涉及虛擬內存等進程用戶態的資源,也不會切換進程,這和平時說的進程上下文切換是不同的:緩存
所以,系統調用的過程一般稱爲特權模式切換,而不是上下文切換。bash
三、進程上下文切換微信
進程是由內核來管理和調度的,進程的切換隻能發生在內核態,所以進程的上下文不只包括了虛擬內存、棧、全局變量等用戶空間的資源,還包括了內核堆棧、寄存器等內核空間的狀態。多線程
所以進程的上下文切換就比系統調用時多了一步:在保存當前進程的內核狀態和 CPU 寄存器以前,需先把該進程的虛擬內存、棧等保存
下來;而加載了下一進程的內核態後,還須要刷新進程的虛擬內存和用戶棧。ssh
保存上下文和恢復上下文的過程並非免費
的,須要內核在 CPU 上運行才能完成。據測試,每次上下文切換都須要幾十納秒到數微妙的 CPU
時間。特別是在進程上下文切換次數較多的狀況下,很容易致使 CPU
將大量時間消耗在寄存器、內核棧、虛擬內存等資源的保存和恢復上,從而大大縮短了真正運行進程的時間。
Linux
經過 TLB
來管理虛擬內存到物理內存的映射關係。當虛擬內存更新後,TLB
也須要刷新,內存的訪問也會隨之變慢。特別是多處理器系統上,緩存是被多個處理器共享的,刷新緩存不只會影響當前處理器的進程,還會影響共享緩存的其它處理器的進程。
四、進程上下文什麼時候切換
Linux
爲每一個 CPU
維護了一個就緒隊列,將活躍進程按照優先級和等待 CPU
的時間排序,而後選擇最須要 CPU
的進程,也就是優先級最高和等待 CPU
時間最長的進程來運行。那麼,進程在何時纔會被調度到 CPU
上運行呢?
CPU
會釋放出來,這時再從就緒隊列中拿一個新的進程來運行CPU
時間被劃分爲一段段的時間片,這些時間片被輪流分配給各個進程。當某個進程時間片耗盡了就會被系統掛起,切換到其它等待 CPU
的進程運行。sleep
主動掛起時,也會從新調度。CPU
上的進程會被中斷掛起,轉而執行內核中的中斷服務程序。線程與進程最大的區別在於,線程是操做系統調度的最小單位,而進程是操做系統分配資源的最小單位。所謂內核調度,實際上的調度對象是線程,而進程只是給線程提供了虛擬內存、全局變量等資源。對於線程和進程咱們能夠這麼理解:
其實線程的上下文切換能夠分爲兩種狀況:
能夠發現同進程內的線程切換,要比多進程間的切換消耗更少的資源,這也正是多線程代替多進程的一個優點。
爲了快速響應硬件的事件,中斷處理會打斷進程的正常調度和執行,轉而調用中斷處理程序,響應設備事件。而在打斷其它進程時,就須要將進程當前的狀態保存下來,這樣在中斷結束後,進程仍然能夠從原來的狀態恢復運行。
CPU
寄存器、內核堆棧、硬件中斷參數等。CPU
來講,中斷處理比進程擁有更高的優先級,因爲中斷會打斷正常進程的調度和執行,因此大部分中斷處理程序都短小精悍,以便儘量快的執行結束。CPU
,當發現中斷次數過多時,就須要注意去排查它是否會給你的系統帶來嚴重的性能問題。總結一下,無論是哪一種場景緻使的上下文切換,你都應該知道:
CPU
上下文切換是保證 Linux
系統正常工做的核心功能之一,通常狀況下咱們無需特別關注。CPU
時間消耗在寄存器、內核棧、虛擬內存等數據的保存和恢復上,從而縮短進程真正運行的時間,致使系統的總體性能大幅降低。咱們能夠經過 vmstat
工具來查看系統的上下文切換狀況。vmstat
主要用來分析系統內存使用狀況,也經常使用來分析 CPU
上下文切換和中斷的次數。
# 每隔 5 秒輸出 1 組數據
$ 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
複製代碼
咱們須要重點關注下列四項內容:
想要查看每一個進程的詳細狀況,須要使用 pidstat,給它加上 -w
選項,就能夠查看每一個進程上下文切換的狀況。
# 每隔 5 秒輸出 1 組數據
$ 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,表示每秒自願上下文切換的次數;另外一個是 nvcswch,表示每秒非自願上下文切換的次數。
準備環境
sysbench
是一個多線程的基準測試工具,通常用來評估不一樣系統參數下的數據庫負載狀況,本次案例把它看成一個異常進程來看,做用是模擬上下文切換過多的問題。
# 預先安裝 sysbench
$ yum install sysbench -y
複製代碼
操做和分析
首先在第一個終端裏運行 sysbench
,模擬系統多線程調度的瓶頸:
# 以 10 個線程運行 5 分鐘的基準測試,模擬多線程切換的問題
$ sysbench --threads=10 --max-time=300 threads run
複製代碼
接着在第二個終端運行 vmstat
,觀察上下文切換狀況:
# 每隔 1 秒輸出 1 組數據(須要 Ctrl+C 才結束)
$ 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
複製代碼
能夠發現,cs 列的上下文切換次數從以前的 35 上升到了 139 萬,觀察其餘幾個指標:
綜合分析,因爲系統的就緒隊列過長,也就是正在運行和等待 CPU 的進程數過多,致使了大量的上下文切換,而上下文切換又致使了系統 CPU 的佔用率升高。
咱們可使用 pidstat 繼續分析究竟是哪一個進程致使了這些問題?
# 每隔 1 秒輸出 1 組數據(須要 Ctrl+C 才結束)
# -w 參數表示輸出進程切換指標,而 -u 參數則表示輸出 CPU 使用指標
$ 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
複製代碼
能夠發現,CPU
使用率的升高是 sysbench
致使的,但上下文切換則來自其餘進程,包括非自願上下文切換頻率最高的 pidstat
,以及自願上下文切換頻率最高的內核線程 kworker
和 sshd
。
默認 pidstat
顯示進程的指標數據,加上 -t
參數後,纔會輸出線程的指標
# 每隔 1 秒輸出一組數據(須要 Ctrl+C 才結束)
# -wt 參數表示輸出線程的上下文切換指標
$ 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
...
複製代碼
雖然 sysbench
進程的上下文切換次數很少,但它的子線程的上下文切換次數很是多,能夠斷定上下文切換罪魁禍首的是 sysbench
進程。還沒完,記得咱們經過 vmstat
看到的中斷次數到了 1 萬,究竟是什麼類型的中斷上升了呢?
咱們能夠經過 /proc/interrupts
來讀取中斷的使用狀況,經過運行下面的命令:
# -d 參數表示高亮顯示變化的區域
$ watch -d cat /proc/interrupts
CPU0 CPU1
...
RES: 2450431 5279697 Rescheduling interrupts
...
複製代碼
能夠發現,變化速度最快的是重調度中斷(RES)
,表示喚醒空閒狀態的 CPU
來調度新的任務運行。這是多處理器系統(SMP)中,調度器用來分散任務隊列到不一樣 CPU
的機制,一般也被稱爲處理器間中斷
。根本緣由仍是由於過多任務的調度問題,跟前邊分析結果是一致的。
這個數值其實取決於系統自己的 CPU
性能。若是系統的上下文切換次數比較穩定,從數百到一萬之內,都應該算是正常的。若是當上下文切換次數超過一萬次,或者切換次數出現數量級增加時,極可能已經出現了性能問題。
這時,你還須要根據上下文切換的類型,再作具體分析,比方說:
IO
等其餘問題CPU
,說明 CPU
的確成了瓶頸。CPU
被中斷處理程序佔用,還須要經過查看 /proc/interrupts
文件來分析具體的中斷類型。若是你們喜歡個人文章,能夠關注我的訂閱號。歡迎隨時留言、交流。若是想加入微信羣的話一塊兒討論的話,請加管理員簡棧文化-小助手(lastpass4u),他會拉大家進羣。