1. 程式人生 > 其它 >Linux CPU的上下文切換

Linux CPU的上下文切換

  我們都知道 Linux 是一個多工作業系統,它支援的任務同時執行的數量遠遠大於 CPU 的數量。當然,這些任務實際上並不是同時執行的(Single CPU),而是因為系統在短時間內將 CPU 輪流分配給任務,造成了多個任務同時執行的假象。

一、CPU 上下文(CPU Context)

  在每個任務執行之前,CPU 需要知道在哪裡載入和啟動任務。這意味著系統需要提前幫助設定 CPU 暫存器程式計數器。CPU暫存器是內置於CPU中的小型但速度極快的記憶體。程式計數器用於儲存 CPU 正在執行的或下一條要執行指令的位置。它們都是 CPU 在執行任何任務之前必須依賴的依賴環境,因此也被稱為 “CPU 上下文”。如下圖所示:

  瞭解了 CPU 上下文是什麼,再看 CPU 上下文切換就很容易了。“CPU上下文切換”指的是先儲存上一個任務的 CPU 上下文(CPU暫存器和程式計數器),

然後將新任務的上下文載入到這些暫存器和程式計數器中,最後跳轉到程式計數器。這些儲存的上下文儲存在系統核心中,並在重新安排任務執行時再次載入。

這確保了任務的原始狀態不受影響,並且任務似乎在持續執行。

二、CPU 上下文切換的型別

CPU 上下文切換無非就是更新 CPU 暫存器和程式計數器值,而這些暫存器是為了快速執行任務而設計的,那為什麼會影響 CPU 效能呢?

在回答這個問題之前,請問,你有沒有想過這些“任務”是什麼?你可能會說一個任務就是一個程序

或者一個執行緒。是的,程序和執行緒正是最常見的任務,但除此

之外,還有其他型別的任務。別忘了硬體中斷也是一個常見的任務,硬體觸發訊號,會引起中斷處理程式的呼叫。

因此,CPU 上下文切換至少有三種不同的型別:

  1. 程序上下文切換
  2. 執行緒上下文切換
  3. 中斷上下文切換

三、程序上下文切換

Linux 按照特權級別將程序的執行空間劃分為核心空間和使用者空間,分別對應下圖中 Ring 0 和 Ring 3 的 CPU 特權級別的 。

  • 核心空間Ring 0)擁有最高許可權,可以直接訪問所有資源

  • 使用者空間Ring 3)只能訪問受限資源,不能直接訪問記憶體等硬體裝置。它必須通過系統呼叫陷入(trapped)

    核心中才能訪問這些特權資源。

從另一個角度看,一個程序既可以在使用者空間也可以在核心空間執行。當一個程序在使用者空間執行時,稱為該程序的使用者態,當它落入核心空間時,稱為該程序的核心態

使用者態核心態的轉換需要通過系統呼叫來完成。例如,當我們檢視一個檔案的內容時,我們需要以下系統呼叫:

  • open():開啟檔案

  • read():讀取檔案的內容

  • write():將檔案的內容寫入到輸出檔案(包括標準輸出)

  • close():關閉檔案

那麼在上述系統呼叫過程中是否會發生 CPU 上下文切換呢?當然是的。

  1、這需要先儲存 CPU 暫存器中原來的使用者態指令的位置。

  2、接下來,為了執行核心態的程式碼,需要將 CPU 暫存器更新到核心態指令的新位置。

  3、最後,跳轉到核心態執行核心任務。

那麼系統呼叫結束後,CPU 暫存器需要恢復原來儲存的使用者狀態,然後切換到使用者空間繼續執行程序。因此,在一次系統呼叫的過程中,實際上有兩次 CPU 上下文切換

但需要指出的是,系統呼叫程序不會涉及程序切換,也不會涉及虛擬記憶體等系統資源切換。這與我們通常所說的“程序上下文切換”不同。程序上下文切換是指從一個程序

切換到另一個程序,而系統呼叫期間始終運行同一個程序系統呼叫過程通常被稱為特權模式切換,而不是上下文切換。但實際上,在系統呼叫過程中,CPU 的上下文切換

也是不可避免的。

四、程序上下文切換 vs 系統呼叫

  那麼程序上下文切換和系統呼叫有什麼區別呢?

  首先,程序是由核心管理的,程序切換隻能發生在核心態。因此,程序上下文不僅包括虛擬記憶體全域性變數等使用者空間資源,還包括核心棧暫存器等核心空間

的狀態。所以程序上下文切換系統呼叫要多出一步:在儲存當前程序的核心狀態和 CPU 暫存器之前,需要儲存程序的虛擬記憶體、棧等;並載入下一個程序的核心狀態。

  根據 Tsuna 的測試報告,每次上下文切換需要幾十納秒至微秒的 CPU 時間。這個時間是相當可觀的,尤其是在大量程序上下文切換的情況下,很容易導致 CPU 花

費大量時間來儲存和恢復暫存器、核心棧、虛擬記憶體等資源。這正是我們在上一篇文章中談到的,一個導致平均負載上升的重要因素。

那麼,該程序何時會被排程/切換到在 CPU 上執行?其實有很多場景,下面是我為大家總結的:

  • 當一個程序的 CPU 時間片用完時,它會被系統掛起,並切換到其他等待 CPU 執行的程序。

  • 當系統資源不足(如記憶體不足)時,直到資源充足之前,程序無法執行。此時程序也會被掛起,系統會排程其他程序執行。

  • 當一個程序通過 sleep 函式自動掛起自己時,自然會被重新排程。

  • 當優先順序較高的程序執行時,為了保證高優先順序程序的執行,當前程序會被高優先順序程序掛起執行

  • 當發生硬體中斷時,CPU 上的程序會被中斷掛起,轉而執行核心中的中斷服務程式。

瞭解這些場景是非常有必要的,因為一旦上下文切換出現效能問題,它們就是幕後殺手。

五、執行緒上下文切換

執行緒和程序最大的區別在於,執行緒是任務排程的基本單位,而程序是資源獲取的基本單位。說白了,核心中所謂的任務排程,實際的排程物件是執行緒;

而程序只為執行緒提供虛擬記憶體和全域性變數等資源。所以,對於執行緒和程序,我們可以這樣理解:

  • 當一個程序只有一個執行緒時,可以認為一個程序等於一個執行緒

  • 當一個程序有多個執行緒時,這些執行緒共享相同的資源,例如虛擬記憶體和全域性變數。

  • 此外,執行緒也有自己的私有資料,比如棧和暫存器,在上下文切換時也需要儲存。

這樣,執行緒的上下文切換其實可以分為兩種情況:

  • 首先,前後兩個執行緒屬於不同的程序。此時,由於資源不共享,切換過程與程序上下文切換相同。

  • 其次,前後兩個執行緒屬於同一個程序。此時,由於虛擬記憶體是共享的,所以切換時虛擬記憶體的資源保持不變,只需要切換執行緒的私有資料、暫存器等未共享的資料。

顯然,同一個程序內的執行緒切換比切換多個程序消耗的資源要少。這也是多執行緒替代多程序的優勢。

六、中斷上下文切換

  除了前面兩種上下文切換之外,還有另外一種場景也輸出 CPU 上下文切換的,那就是中斷。為了快速響應事件,硬體中斷會中斷正常的排程和執行過程,進而呼叫中斷處

理程式。在中斷其他程序時,需要儲存程序的當前狀態,以便中斷後程序仍能從原始狀態恢復。與程序上下文不同,中斷上下文切換不涉及程序的使用者態。

  因此,即使中斷程序中斷了處於使用者態的程序,也不需要儲存和恢復程序的虛擬記憶體、全域性變數等使用者態資源。另外,和程序上下文切換一樣,中斷上下文切換也會消耗

CPU。過多的切換次數會消耗大量的 CPU 資源,甚至嚴重降低系統的整體效能。因此,當您發現中斷過多時,需要注意排查它是否會對您的系統造成嚴重的效能問題。

七、問題排查

1、工具

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

  • pidstat ——vmstat只給出了系統總體的上下文切換情況,要想檢視每個程序的詳細情況,就需要使用pidstat,加上-w,可以檢視每個程序上下文切換的情況

  • /proc/interrupts——/proc實際上是linux的虛擬檔案系統用於核心空間和使用者空間的通訊,/proc/interrupts是這種通訊機制的一部分,提供了一個只讀的中斷使用情況。

  •  perf stat  可以統計很多和CPU相關核心資料,比如cache' miss,上下文切換,CPI等。

2、實戰

2.1 vmstat

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

分析: 檢視cs大小(實驗時cs驟升到百萬) 同時注意r列(實驗時為8),機器cpu為1,遠遠超過1,必然會有大量的CPU競爭 us和sy列,計算cpu使用率總和

(實驗加起來快100%,其中sy高達84%,說明cpu主要被核心佔用) in列,檢視大小(實驗中驟升到一萬,說明中斷處理也是潛在的問題) 綜合可知,系統的

就需佇列過長,也就是正在執行和等待CPU的程序數過多,導致了大量的上下文切換,而上下文切換導致了cpu佔用率高

2.2 pidstat檢視程序上下文切換情況

  • cswch 表示每秒自願上下文切換的次數,是指程序無法獲取所需資源,導致的上下文切換,比如說,I/O,記憶體等系統資源不足時,就會發生自願上下文切換。
  • nvcswch 表示每秒非自願上下文切換的次數,則是指程序由於時間片已到等原因,被系統強制排程,進而發生的上下文切換。

分析: pidstat檢視果然是sysbench導致了cpu達到100%,但上下文切換來自其他程序,包括非自願上下文切換最高的pidstat,以及自願上下文切換最高的kworker

和sshd 但pidtstat輸出的上下文切換次數加起來才幾百和vmstat的百萬明顯小很多,現在vmstat輸出的是執行緒,而pidstat加上-t後才輸出執行緒指標。

 

pidstat子執行緒加一起就差不多百萬了。

2.3 檢視中斷——可排查是哪些中斷引起的(變化速度最快的)

  觀察一段時間後,可以發現變化最快的是重新排程中斷(RES, REScheduling interrupt)。這種中斷型別表明處於空閒狀態的 CPU 被喚醒以排程新的任務執行。

所以這裡的中斷增加是因為太多的任務排程問題,這和前面上下文切換次數的分析結果是一致的.

 現在回到最初的問題,每秒多少次上下文切換是正常的?

  這個值實際上取決於系統本身的 CPU 效能。如果系統的上下文切換次數比較穩定的話,幾百到一萬應該是正常的。但是,當上下文切換次數超過 10000,或者

切換次數快速增加時,很可能是出現了效能問題。

perf stat 可以排查系統上下文切換速率變化

 可以觀察context-switcehes 資料的變化,有沒有突增,可以發現一些異常想象。

3、場景 

  • 根據排程策略,將CPU時間劃片為對應的時間片,當時間片耗盡,當前程序必須掛起。

  • 資源不足的,在獲取到足夠資源之前程序掛起。

  • 程序sleep掛起程序。

  • 高優先順序程序導致當前進度掛起

  • 硬體中斷,導致當前程序掛起

4、總結

  • CPU上下文切換,是保證Linux系統正常工作的核心功能之一,一般情況下不需要我們特別關注。

  • 但過多的上下文切換,會把CPU時間消耗在暫存器,核心棧以及虛擬記憶體等資料的儲存和恢復上,從而縮短程序真正執行的時間,導致系統的整體效能大幅下降。

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

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

  • 中斷次數變多了,說明 CPU 被中斷處理程式佔用,還需要通過檢視 /proc/interrupts 檔案來分析具體的中斷型別。