什麼是CPU 上下文切換

首先先談一下程序計數器(Program Counter),計算機中提供要從存儲器中取出的下一個指令地址的寄存器,通常情況下,每一個指令取出後寄存器就自動增加一步就如《微機原理》當中的 PC = PC + 1 ,在 x86 體系裏是這樣。x86 系統中自增的是 IP,用 CS:IP 組合表示正在執行的指令地址,此時 PC 只是一個概念上的說法。在 ARM 體系中 R15 就是 PC,當然 ARM 和 IA-32、x64 都支持高級內存管理,所以「PC」的內容未必是當前指令在內存中的絕對位置。


 

CPU 上下文 可以理解成 CPU寄存器狀態以及程序計數器PC , 這些都是記錄CPU當前任務的狀態。CPU 上下文切換 會把當前的cpu的上下文保存下來,然後加載新任務的對應上下文,而這些保存下來的上下文,會存儲在系統內核中,並在任務重新調度執行時再次加載進來。這樣就能保證任務原來的狀態不受影響,讓任務看起來還是連續運行。

任務一般包括:

1.進程

2.線程

3.中斷上下文

CPU 上下文的切換會因特權模式切換、進程上下文切換、線程上下文切換以及中斷上下文切換 產生。

 

特權模式切換

Linux 按照特權等級,把進程的運行空間分爲內核空間和用戶空間,分別對應着下圖中, CPU 特權等級的 Ring 0 和 Ring 3。內核空間(Ring 0)具有最高權限,可以直接訪問所有資源;用戶空間(Ring 3)只能訪問受限資源,不能直接訪問內存等硬件設備,必須通過系統調用陷入到內核中,才能訪問這些特權資源。

進程可以在用戶空間運行,也可以在內核空間運行。 當調用open()、read()、write()、close()系統函數,CPU會保存原來用戶態的指令位置,然後更新CPU寄存器內核態指令的新位置。最後執行內核態函數。當系統調用結束後,CPU恢復原來的用戶態,切換回用戶空間繼續執行進程。

可以看到 用戶態->內核態, 內核態->用戶態 這兩個過程總共是產生了兩次CPU上下文切換。不過,需要注意的是,系統調用過程中,並不會涉及到虛擬內存等進程用戶態的資源

 

進程上下文切換

進程上下文不僅包括了虛擬內存、棧、全局變量等用戶空間的資源,還包括了內核堆棧、寄存器等內核空間的狀態,與系統調用相比進程間的切換還需要把內容保存下來。

進程上下文切換數量多,容易導致CPU花費更多的時間在寄存器、虛擬內存、內核棧等資源的保存和恢復上,減少了進程運行時間,致使平均負載升高。Linux 通過 TLB(Translation Lookaside Buffer)來管理虛擬內存到物理內存的映射關係。當虛擬內存更新後,TLB 也需要刷新,內存的訪問也會隨之變慢。特別是在多處理器系統上,緩存是被多個處理器共享的,刷新緩存不僅會影響當前處理器的進程,還會影響共享緩存的其他處理器的進程。

進程上下文切換髮生在進程調度的過程。

主要在以下場景觸發:

CPU時間是被劃分成各個時間片,當前進程的時間片被耗盡之後會被系統掛起,切換到其他正在等待CPU的進程來運行。
進程在系統資源不足(比如內存不足)時,要等到資源滿足後纔可以運行,這個時候進程也會被掛起,並由系統調度其他進程運行。

當進程通過睡眠函數 sleep 或者 sched_yield 這樣的方法將自己主動掛起時,自然也會重新調度。

當有優先級更高的進程運行時,爲了保證高優先級進程的運行,當前進程會被掛起,由高優先級進程來運行。

發生硬件中斷時,CPU 上的進程會被中斷掛起,轉而執行內核中的中斷服務程序。

 

線程上下文切換

線程與進程最大的區別在於,線程是調度的基本單位,而進程則是資源擁有的基本單位。linux內核中的任務調度,實際上的調度對象是線程;而進程只是給線程提供了虛擬內存、全局變量等資源。進程中只有一個線程,進程等於線程。因爲同一進程下線程是共享虛擬內存,相關間的切換隻需保存其私有數據、寄存器等,因此進程內的線程切換比進程間的切換消耗更少資源。

 

中斷上下文切換

爲了快速響應硬件的事件,中斷處理會打斷進程的正常調度和執行,轉而調用中斷處理程序,響應設備事件。而在打斷其他進程時,就需要將進程當前的狀態保存下來,這樣在中斷結束後,進程仍然可以從原來的狀態恢復運行。跟進程上下文不同,中斷上下文切換並不涉及到進程的用戶態。所以,即便中斷過程打斷了一個正處在用戶態的進程,也不需要保存和恢復這個進程的虛擬內存、全局變量等用戶態資源。中斷上下文,其實只包括內核態中斷服務程序執行所必需的狀態,包括 CPU 寄存器、內核堆棧、硬件中斷參數等。對同一個 CPU 來說,中斷處理比進程擁有更高的優先級,所以中斷上下文切換並不會與進程上下文切換同時發生。同樣道理,由於中斷會打斷正常進程的調度和執行,所以大部分中斷處理程序都短小精悍,以便儘可能快的執行結束。另外,跟進程上下文切換一樣,中斷上下文切換也需要消耗 CPU,切換次數過多也會耗費大量的 CPU,甚至嚴重降低系統的整體性能。所以,當你發現中斷次數過多時,就需要注意去排查它是否會給你的系統帶來嚴重的性能問題。

 

CPU上下文切分析

vmstat 是一個常用的系統性能分析工具,主要用來分析系統的內存使用情況,也常用來分析 CPU 上下文切換和中斷的次數。

root@ECSab169d:~# 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
 1  0  90720 3960176   1036 3014980    0    0    35    85    0    1  1  0 98  0  0
 0  0  90720 3959928   1036 3015004    0    0     0    24  240  358  0  0 99  0  0
 0  1  90720 3959356   1036 3015068    0    0     0    34  260  390  1  0 99  0  0
 0  0  90720 3959204   1036 3015088    0    0     0    23  234  340  0  0 99  0  0
 0  0  90720 3960708   1036 3015088    0    0     0   609  297  429  0  0 99  0  0
 0  0  90720 3960772   1036 3015112    0    0     0   140  295  410  0  0 99  0  0
 0  0  90720 3960688   1036 3015164    0    0     0    36  269  406  0  0 99  0  0

r 是就緒隊列的長度,也就是正在運行和等待 CPU 的進程數。

b 則是處於不可中斷睡眠狀態的進程數

swpd 交換分區大小,一般在內存不足的情況會使用swap進行擴展。

free 空閒的物理內存的大小,我的機器內存總共8G,剩餘3415M。

buff Linux/Unix系統是用來存儲,目錄裏面有什麼內容,權限等的緩存,我本機大概佔用300多M

cache cache直接用來記憶我們打開的文件,給文件做緩衝,我本機大概佔用300多M(這裏是Linux/Unix的聰明之處,把空閒的物理內存的一部分拿來做文件和目錄的緩存,是爲了提高 程序執行的性能,當程序使用內存時,buffer/cached會很快地被使用。)

si 每秒從磁盤讀入虛擬內存的大小,如果這個值大於0,表示物理內存不夠用或者內存泄露了,要查找耗內存進程解決掉。我的機器內存充裕,一切正常。

so 每秒虛擬內存寫入磁盤的大小,如果這個值大於0,同上。

bi 塊設備每秒接收的塊數量,這裏的塊設備是指系統上所有的磁盤和其他塊設備,默認塊大小是1024byte,我本機上沒什麼IO操作,所以一直是0,但是我曾在處理拷貝大量數據(2-3T)的機器上看過可以達到140000/s,磁盤寫入速度差不多140M每秒

bo 塊設備每秒發送的塊數量,例如我們讀取文件,bo就要大於0。bi和bo一般都要接近0,不然就是IO過於頻繁,需要調整。

in 每秒CPU的中斷次數,包括時間中斷

cs 每秒上下文切換次數

可以看到,這個例子中的上下文切換次數 cs 是 33 次,而系統中斷次數 in 則是 25 次,而就緒隊列長度 r 和不可中斷狀態進程數 b 都是 0。

vmstat 只給出了系統總體的上下文切換情況,要想查看每個進程的詳細情況,就需要使用我們前面提到過的 pidstat 了。給它加上 -w 選項,你就可以查看每個進程上下文切換的情況了。

root@xxxxx:~# pidstat -w 5
Linux 4.15.0-66-generic (xxxxx)     01/25/2021      _x86_64_        (4 CPU)

05:30:32 PM   UID       PID   cswch/s nvcswch/s  Command
05:30:37 PM     0         8     18.16      0.00  rcu_sched
05:30:37 PM     0        11      0.20      0.00  watchdog/0
05:30:37 PM     0       183      4.59      0.00  kworker/3:1H
05:30:37 PM     0       388      2.40      0.00  kworker/0:1H
05:30:37 PM     0       546      0.20      0.00  irqbalance
05:30:37 PM     0       621      9.98      0.00  qemu-ga
05:30:37 PM   109       663      0.40      0.00  uml_switch
05:30:37 PM     0      1275      0.40      0.00  master
05:30:37 PM   110      1277      0.20      0.00  qmgr
05:30:37 PM     0      5896      7.39      0.00  kworker/0:0
05:30:37 PM     0     13579      8.18      0.00  sshd
05:30:37 PM     0     13766      0.20      0.00  vmstat
05:30:37 PM     0     26398      5.19      0.00  kworker/u8:1
05:30:37 PM     0     28341      6.19      0.00  kworker/u8:2
05:30:37 PM     0     28898      5.99      1.40  bash

 

這個結果中有兩列內容是我們的重點關注對象。一個是 cswch ,表示每秒自願上下文切換(voluntary context switches)的次數,另一個則是 nvcswch ,表示每秒非自願上下文切換(non voluntary context switches)的次數。

謂自願上下文切換: 是指進程無法獲取所需資源,導致的上下文切換。比如說, I/O、內存等系統資源不足時,就會發生自願上下文切換。

非自願上下文切換: 則是指進程由於時間片已到等原因,被系統強制調度,進而發生的上下文切換。比如說,大量進程都在爭搶 CPU 時,就容易發生非自願上下文切換。

 

總結

自願上下文切換變多了,說明進程都在等待資源,有可能發生了 I/O 等其他問題;

非自願上下文切換變多了,說明進程都在被強制調度,也就是都在爭搶 CPU,說明 CPU 的確成了瓶頸;

中斷次數變多了,說明 CPU 被中斷處理程序佔用,還需要通過查看 /proc/interrupts 文件來分析具體的中斷類型

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章