日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

进程用户态 上下文切换需要保存哪些_漫话性能:CPU上下文切换

發布時間:2025/3/19 编程问答 15 豆豆
生活随笔 收集整理的這篇文章主要介紹了 进程用户态 上下文切换需要保存哪些_漫话性能:CPU上下文切换 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

序言

表弟:CPU平均負載?zhuanlan.zhihu.com

我們理解了平均負載( Load Average),并用三個案例展示了不同場景下平均負載升高的分析方法。這其中,多個進程(或線程)競爭 CPU 就是一個經常被我們忽視的問題。

進程在競爭 CPU 的時候并沒有真正運行,為什么還會導致系統的負載升高呢?CPU 上下文切換就是罪魁禍首。我們都知道,Linux 是一個多任務操作系統,它支持遠大于 CPU 數量的任務同時運行。當然,這些任務實際上并不是真的在同時運行,而是因為系統在很短的時間內,將 CPU 輪流分配給它們,造成多任務同時運行的錯覺。

而在每個任務運行前,CPU 都需要知道任務從哪里加載、又從哪里開始運行,也就是說,需要系統事先幫它設置好 CPU 寄存器和程序計數器(Program Counter,PC)。

CPU上下文

什么是CPU上下文呢?說直白點就是CPU的寄存器信息,以ARM為例,CPU寄存器信息如下所示:

CPU 寄存器,是 CPU 內置的容量小、但速度極快的內存。而程序計數器,則是用來存儲 CPU 正在執行的指令位置、或者即將執行的下一條指令位置。它們都是 CPU 在運行任何任務前,必須的依賴環境,因此也被叫做 CPU 上下文。

CPU 上下文切換,就是先把前一個任務的 CPU 上下文(也就是 CPU 寄存器和程序計數器)保存起來,然后加載新任務的上下文到這些寄存器和程序計數器,最后再跳轉到程序計數器所指的新位置,運行新任務。而這些保存下來的上下文,會存儲在系統內核中,并在任務重新調度執行時再次加載進來。這樣就能保證任務原來的狀態不受影響,讓任務看起來還是連續運行。

進程上下文切換

進程上下文切換是指從一個進程切換到另一個進程運行。而系統調用過程中一直是同一個進程在運行。所以,系統調用過程通常稱為特權模式切換,而不是上下文切換。但實際上,系統調用過程中,CPU 的上下文切換還是無法避免的。

特權模式

內核把虛擬地址空間劃分為兩個部分,因此能夠保護各個系統進程,使之彼此隔離。所有的現代CPU 都提供了幾種特權級別,進程可以駐留在某一特權級別。每個特權級別都有各種限制,例如對執行 某些匯編語言指令或訪問虛擬地址空間某一特定部分的限制。IA-32體系結構使用4種特權級別構成的系統, 各級別可以看作是環。內環能夠訪問更多的功能,外環則較少,如圖所示:

盡管英特爾處理器區分4種特權級別,但Linux只使用兩種不同的狀態:核心態和用戶狀態。 兩種狀態的關鍵差別在于對高于TASK_SIZE的內存區域的訪問。簡而言之,在用戶狀態禁止訪問內核空間。 用戶進程不能操作或讀取內核空間中的數據,也無法執行內核空間中的代碼。這是內核的專用領域。 這種機制可防止進程無意間修改彼此的數據而造成相互干擾。從用戶狀態到核心態的切換通過系統調用的特定轉換手段完成。

換個角度看,也就是說,進程既可以在用戶空間運行,又可以在內核空間中運行。進程在用戶空間運行時,被稱為進程的用戶態,而陷入內核空間的時候,被稱為進程的內核態。從用戶態到內核態的轉變,需要通過系統調用來完成。

系統調用的過程有沒有發生 CPU 上下文的切換呢?如下文章有詳細分析:

novelinux/system_calls?github.com

不過,需要注意的是,系統調用過程中,并不會涉及到虛擬內存等進程用戶態的資源,也不會切換進程。這跟我們通常所說的進程上下文切換是不一樣的。

進程上下文切換跟系統調用又有什么區別呢?

首先,進程是由內核來管理和調度的,進程的切換只能發生在內核態。所以,進程的上下文不僅包括了虛擬內存、棧、全局變量等用戶空間的資源,還包括了內核堆棧、寄存器等內核空間的狀態。因此,進程的上下文切換就比系統調用時多了一步:在保存當前進程的內核狀態和 CPU 寄存器之前,需要先把該進程的虛擬內存、棧等保存下來;而加載了下一進程的內核態后,還需要刷新進程的虛擬內存和用戶棧。如下圖所示,保存上下文和恢復上下文的過程并不是“免費”的,需要內核在 CPU 上運行才能完成。

根據https://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html 的測試報告,每次上下文切換都需要幾十納秒到數微秒的 CPU 時間。這個時間還是相當可觀的,特別是在進程上下文切換次數較多的情況下,很容易導致 CPU 將大量時間耗費在寄存器、內核棧以及虛擬內存等資源的保存和恢復上,進而大大縮短了真正運行進程的時間。這也正是導致平均負載升高的一個重要因素。

我們知道, Linux 通過 TLB(Translation Lookaside Buffer)來管理虛擬內存到物理內存的映射關系。當虛擬內存更新后,TLB 也需要刷新,內存的訪問也會隨之變慢。特別是在多處理器系統上,緩存是被多個處理器共享的,刷新緩存不僅會影響當前處理器的進程,還會影響共享緩存的其他處理器的進程。知道了進程上下文切換潛在的性能問題后,我們再來看,究竟什么時候會切換進程上下文。顯然,進程切換時才需要切換上下文,換句話說,只有在進程調度的時候,才需要切換上下文。

Linux 為每個 CPU 都維護了一個就緒隊列,將活躍進程(即正在運行和正在等待 CPU 的進程)按照優先級和等待 CPU 的時間排序,然后選擇最需要 CPU 的進程,也就是優先級最高和等待 CPU 時間最長的進程來運行。那么,進程在什么時候才會被調度到 CPU 上運行呢?最容易想到的一個時機,就是進程執行完終止了,它之前使用的 CPU 會釋放出來,這個時候再從就緒隊列里,拿一個新的進程過來運行。其實還有很多其他場景,也會觸發進程調度:

  • 為了保證所有進程可以得到公平調度,CPU 時間被劃分為一段段的時間片,這些時間片再被輪流分配給各個進程。這樣,當某個進程的時間片耗盡了,就會被系統掛起,切換到其它正在等待 CPU 的進程運行。
  • 進程在系統資源不足(比如內存不足)時,要等到資源滿足后才可以運行,這個時候進程也會被掛起,并由系統調度其他進程運行。
  • 當進程通過睡眠函數 sleep 這樣的方法將自己主動掛起時,自然也會重新調度。
  • 當有優先級更高的進程運行時,為了保證高優先級進程的運行,當前進程會被掛起,由高優先級進程來運行。
  • 發生硬件中斷時,CPU 上的進程會被中斷掛起,轉而執行內核中的中斷服務程序。

線程上下文切換

線程與進程最大的區別在于,線程是調度的基本單位,而進程則是資源擁有的基本單位。說白了,所謂內核中的任務調度,實際上的調度對象是線程;而進程只是給線程提供了虛擬內存、全局變量等資源。

所以,對于線程和進程,我們可以這么理解:當進程只有一個線程時,可以認為進程就等于線程。當進程擁有多個線程時,這些線程會共享相同的虛擬內存和全局變量等資源。這些資源在上下文切換時是不需要修改的。另外,線程也有自己的私有數據,比如棧和寄存器等,這些在上下文切換時也是需要保存的。

線程的上下文切換其實就可以分為兩種情況:

  • 第一種, 前后兩個線程屬于不同進程。此時,因為資源不共享,所以切換過程就跟進程上下文切換是一樣。
  • 第二種,前后兩個線程屬于同一個進程。此時,因為虛擬內存是共享的,所以在切換時,虛擬內存這些資源就保持不動,只需要切換線程的私有數據、寄存器等不共享的數據。

雖然同為上下文切換,但同進程內的線程切換,要比多進程間的切換消耗更少的資源,而這,也正是多線程代替多進程的一個優勢。

中斷上下文切換

為了快速響應硬件的事件,中斷處理會打斷進程的正常調度和執行,轉而調用中斷處理程序,響應設備事件。而在打斷其他進程時,就需要將進程當前的狀態保存下來,這樣在中斷結束后,進程仍然可以從原來的狀態恢復運行。

跟進程上下文不同,中斷上下文切換并不涉及到進程的用戶態。所以,即便中斷過程打斷了一個正處在用戶態的進程,也不需要保存和恢復這個進程的虛擬內存、全局變量等用戶態資源。中斷上下文,其實只包括內核態中斷服務程序執行所必需的狀態,包括 CPU 寄存器、內核堆棧、硬件中斷參數等。

對同一個 CPU 來說,中斷處理比進程擁有更高的優先級,所以中斷上下文切換并不會與進程上下文切換同時發生。同樣道理,由于中斷會打斷正常進程的調度和執行,所以大部分中斷處理程序都短小精悍,以便盡可能快的執行結束。

另外,跟進程上下文切換一樣,中斷上下文切換也需要消耗 CPU,切換次數過多也會耗費大量的 CPU,甚至嚴重降低系統的整體性能。所以,當你發現中斷次數過多時,就需要注意去排查它是否會給你的系統帶來嚴重的性能問題。

查看系統的上下文切換

vmstat

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

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

pidstat

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

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

  • 自愿上下文切換,是指進程無法獲取所需資源,導致的上下文切換。比如說, I/O、內存等系統資源不足時,就會發生自愿上下文切換。
  • 而非自愿上下文切換,則是指進程由于時間片已到等原因,被系統強制調度,進而發生的上下文切換。比如說,大量進程都在爭搶 CPU 時,就容易發生非自愿上下文切換。

案例分析

先用 vmstat 看一下空閑系統的上下文切換次數:

這里你可以看到,現在的上下文切換次數 cs 是3,而中斷次數 in 是 8,r 和 b 都是 0。因為這會兒并沒有運行其他任務,所以它們就是空閑系統的上下文切換次數。

首先,在第一個終端里運行 sysbench ,模擬系統多線程調度的瓶頸:

sysbench --test=cpu --num-threads=10 --max-time=300 run

接著,在第二個終端運行 vmstat ,觀察上下文切換情況

vmstat 1

我們發現,cs 列的上下文切換次數從之前的 0驟然上升到了 3000多。同時,注意觀察其他幾個指標:

  • r 列:就緒隊列的長度已經到了 8,等于系統 CPU 的個數 8,所以肯定會有大量的 CPU 競爭。
  • us(user)和 sy(system)列:這兩列的 CPU 使用率加起來上升到了 100%,其中系統 CPU 使用率,也就是 sy 列高達 100%,說明 CPU 主要是被內核占用了。
  • in 列:中斷次數也上升到了 1000多,說明中斷處理也是個潛在的問題。

綜合這幾個指標,我們可以知道,系統的就緒隊列過長,也就是正在運行和等待 CPU 的進程數過多,導致了大量的上下文切換,而上下文切換又導致了系統 CPU 的占用率升高。那么到底是什么進程導致了這些問題呢?

pidstat -w -u 1

從 pidstat 的輸出你可以發現,CPU 使用率的升高果然是 sysbench 導致的,它的 CPU 使用率已經達到了 100%。但上下文切換則是來自其他進程,包括非自愿上下文切換頻率最高的 pidstat ,以及自愿上下文切換頻率最高的內核線程 kworker 和 sshd。

不過,細心的你肯定也發現了一個怪異的事兒:pidstat 輸出的上下文切換次數,加起來也就幾百,比 vmstat 的 3000明顯小了太多。這是怎么回事呢?難道是工具本身出了錯嗎?別著急,在懷疑工具之前,我們再來回想一下,前面講到的幾種上下文切換場景。其中有一點提到, Linux 調度的基本單位實際上是線程,而我們的場景 sysbench 模擬的也是線程的調度問題,那么,是不是 pidstat 忽略了線程的數據呢?通過運行 man pidstat ,你會發現,pidstat 默認顯示進程的指標數據,加上 -t 參數后,才會輸出線程的指標。所以,我們可以在第三個終端里, Ctrl+C 停止剛才的 pidstat 命令,再加上 -t 參數,重試一下看看:

pidstat -wt 1

現在你就能看到了,雖然 sysbench 進程(也就是主線程)的上下文切換次數看起來并不多,但它的子線程的上下文切換次數卻有很多。看來,上下文切換罪魁禍首,還是過多的 sysbench 線程。

我們已經找到了上下文切換次數增多的根源。不知道你還記不記得,前面在觀察系統指標時,除了上下文切換頻率驟然升高,還有一個指標也有很大的變化。是的,正是中斷次數。中斷次數也上升到了 2000多,但到底是什么類型的中斷上升了,現在還不清楚。我們接下來繼續抽絲剝繭找源頭。既然是中斷,我們都知道,它只發生在內核態,而 pidstat 只是一個進程的性能分析工具,并不提供任何關于中斷的詳細信息,怎樣才能知道中斷發生的類型呢?沒錯,那就是從 /proc/interrupts 這個只讀文件中讀取。/proc 實際上是 Linux 的一個虛擬文件系統,用于內核空間與用戶空間之間的通信。/proc/interrupts 就是這種通信機制的一部分,提供了一個只讀的中斷使用情況。

觀察一段時間,你可以發現,變化速度最快的是重調度中斷(RES),這個中斷類型表示,喚醒空閑狀態的 CPU 來調度新的任務運行。這是多處理器系統(SMP)中,調度器用來分散任務到不同 CPU 的機制,通常也被稱為處理器間中斷(Inter-Processor Interrupts,IPI)。所以,這里的中斷升高還是因為過多任務的調度問題,跟前面上下文切換次數的分析結果是一致的。

通過這個案例,我們發現了多工具、多方面指標對比觀測的好處。如果最開始時,我們只用了 pidstat 觀測,這些很嚴重的上下文切換線程,壓根兒就發現不了了。現在再回到最初的問題,每秒上下文切換多少次才算正常呢?這個數值其實取決于系統本身的 CPU 性能。

小結

如果系統的上下文切換次數比較穩定,那么從數百到一萬以內,都應該算是正常的。但當上下文切換次數超過一萬次,或者切換次數出現數量級的增長時,就很可能已經出現了性能問題。這時,還需要根據上下文切換的類型,再做具體分析。比方說:

  • 自愿上下文切換變多了,說明進程都在等待資源,有可能發生了 I/O 等其他問題;
  • 非自愿上下文切換變多了,說明進程都在被強制調度,也就是都在爭搶 CPU,說明 CPU 的確成了瓶頸;
  • 中斷次數變多了,說明 CPU 被中斷處理程序占用,還需要通過查看 /proc/interrupts 文件來分析具體的中斷類型。

總結

以上是生活随笔為你收集整理的进程用户态 上下文切换需要保存哪些_漫话性能:CPU上下文切换的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。