深刻理解Linux的CPU上下文切換

如何理解Linux的上下文切換

  • Linux 是一個多任務操做系統,它支持同時運行的任務數量遠大於 CPU 個數。其實這些任務沒有真正的同時運行,是由於系統在很短的時間內,將 CPU 輪流分配給它們,形成多任務同時運行的錯覺。
  • 而在每一個任務運行前,CPU 都須要知道任務從哪裏加載、從哪裏開始運行,須要系統事先設置好 CPU 寄存器程序計數器CPU 寄存器是 CPU 內置的容量小、速度極快的內存。而程序計數器則是用來存儲 CPU 正在執行的指令位置、或即將執行的下一條指令位置。它們都是 CPU 在運行任務前必須依賴的環境,也被叫作 CPU 上下文
  • 上下文切換,就是先把前一個任務的 CPU 上下文保存起來,而後加載新任務的上下文到這些寄存器和程序計數器,最後再跳到程序計數器所指的新位置,運行新任務。而這些保存下來的上下文,會存儲在系統內核中,並在任務從新調度執行時再次加載進來。這樣就能保證任務原來的狀態不受影響,讓任務看起來仍是連續運行。

根據任務的不一樣,CPU 的上下文切換能夠分爲幾個不一樣的場景,也就是:進程上下文切換、線程上下文切換、中斷上下文切換。html

進程上下文切換

一、用戶空間與內核空間linux

Linux 按照特權等級,把進程的運行空間分爲內核空間和用戶空間,分別對應着 CPU 特權等級的 Ring 0Ring 3shell

  • 內核空間(Ring 0)具備最高權限,能夠直接訪問全部資源。
  • 用戶空間(Ring 3)只能訪問受限資源,不能直接訪問內存等硬件設備,必須經過系統調用陷入內核中才能訪問這些特權資源。
  • 進程既能夠在用戶空間運行,又能夠在內核空間運行。在用戶空間運行時被稱爲進程的用戶態,而陷入內核空間的時候,被稱爲進程的內核態。

二、系統調用數據庫

從用戶態到內核態的轉變,須要經過系統調用來完成。好比查看文件時,須要執行屢次系統調用:open、read、write、close等。系統調用的過程以下:ubuntu

  • 首先,把 CPU 寄存器裏原來用戶態的指令位置保存起來
  • 爲了執行內核代碼,CPU 寄存器須要更新爲內核態指令的新位置,最後跳轉到內核態運行內核任務。
  • 系統調用結束後,CPU 寄存器須要恢復原來保存的用戶態,而後再切換到用戶空間,繼續運行進程
  • 因此,一次系統調用的過程,實際上是發生了兩次 CPU 上下文切換。

但系統調用的過程當中並不會涉及虛擬內存等進程用戶態的資源,也不會切換進程,這和平時說的進程上下文切換是不同的:緩存

  • 進程上下文切換,是指從一個進程切換到另外一個進程運行
  • 系統調用過程當中一直是同一個進程在運行

所以,系統調用的過程一般稱爲特權模式切換,而不是上下文切換。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
複製代碼

咱們須要重點關注下列四項內容:

  • cs(context switch) 是每秒上下文切換的次數。
  • in(interrupt) 是每秒中斷的次數。
  • r(Running or Runnable) 是就緒隊列的長度,也就是正在運行和等待 CPU 的進程數。
  • b(Blocked) 是處於不可中斷睡眠狀態的進程數。

想要查看每一個進程的詳細狀況,須要使用 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,表示每秒非自願上下文切換的次數。

  • 自願上下文切換,是指進程沒法獲取所需資源,致使的上下文切換。好比,IO、內存等系統資源不足時,就會發生自願上下文切換。
  • 非資源上下文切換,則是指進程因爲時間片已到等緣由,被系統強制調度,進而發生的上下文切換。好比說,大量進程都在搶佔 CPU 時,就容易發生非自願上下文切換。

案例分析

準備環境

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 萬,觀察其餘幾個指標:

  • r 列:就緒隊列長度爲 8,遠大於 CPU 個數,因此會有大量的 CPU 競爭
  • us 和 sys 列:這兩列加一塊兒上升到 100%,sys 列高達 84%,說明 CPU 主要是被內核佔用了。
  • in 列:中斷次數爲 1 萬左右,說明中斷也是個潛在的問題。

綜合分析,因爲系統的就緒隊列過長,也就是正在運行和等待 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,以及自願上下文切換頻率最高的內核線程 kworkersshd

默認 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),他會拉大家進羣。

參考地址

簡棧文化服務訂閱號
相關文章
相關標籤/搜索