上下文儲存 中斷 深入理解CPU上下文切換

2021-10-16 07:14:04 字數 3642 閱讀 5454

我們都知道cpu上下文切換,會增加系統負載。那什麼是cpu上下文,為什麼要切換?

我們都知道,linux 是乙個多工作業系統,它支援遠大於 cpu 數量的任務同時執行。當然,這些任務實際上並不是真的在同時執行,而是因為系統在很短的時間內,將 cpu 輪流分配給它們,造成多工同時執行的錯覺。

而在每個任務執行前,cpu 都需要知道任務從**載入、又從**開始執行,也就是說,需要系統事先幫它設定好cpu 暫存器和程式計數器(program counter,pc)。

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

而這些儲存下來的上下文,會儲存在系統核心中,並在任務重新排程執行時再次載入進來。這樣就能保證任務原來的狀態不受影響,讓任務看起來還是連續執行。

根據任務的不同,cpu的上下文切換可以分為不同的場景,也就是程序上下文切換、執行緒上下文切換、中斷上下文切換。

linux 按照特權等級,把程序的執行空間分為核心空間和使用者空間,分別對應著下圖中, cpu 特權等級的 ring 0 和 ring 3。

換個角度看,也就是說,程序既可以在使用者空間執行,又可以在核心空間中執行。程序在使用者空間執行時,被稱為程序的使用者態,而陷入核心空間的時候,被稱為程序的核心態。

從使用者態到核心態的轉變,需要通過系統呼叫來完成。比如,當我們檢視檔案內容時,就需要多次系統呼叫來完成:首先呼叫 open() 開啟檔案,然後呼叫 read() 讀取檔案內容,並呼叫 write() 將內容寫到標準輸出,最後再呼叫 close() 關閉檔案。

那麼,系統呼叫的過程有沒有發生 cpu 上下文的切換呢?答案自然是肯定的。

cpu 暫存器裡原來使用者態的指令位置,需要先儲存起來。接著,為了執行核心態**,cpu 暫存器需要更新為核心態指令的新位置。最後才是跳轉到核心態執行核心任務。

而系統呼叫結束後,cpu 暫存器需要恢復原來使用者儲存的狀態,然後再切換到使用者空間,繼續執行程序。所以,一次系統呼叫的過程,其實是發生了兩次 cpu 上下文切換

不過,需要注意的是,系統呼叫過程中,並不會涉及到虛擬記憶體等程序使用者態的資源,也不會切換程序。這跟我們通常所說的程序上下文切換是不一樣的:

所以,系統呼叫過程通常稱為特權模式切換,而不是上下文切換。但實際上,系統呼叫過程中,cpu 的上下文切換還是無法避免的。

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

首先,你需要知道,程序是由核心來管理和排程的,程序的切換只能發生在核心態。所以,程序的上下文不僅包括了虛擬記憶體、棧、全域性變數等使用者空間的資源,還包括了核心堆疊、暫存器等核心空間的狀態。

因此,程序的上下文切換就比系統呼叫時多了一步:在儲存當前程序的核心狀態和 cpu 暫存器之前,需要先把該程序的虛擬記憶體、棧等儲存下來;而載入了下一程序的核心態後,還需要重新整理程序的虛擬記憶體和使用者棧。

如下圖所示,儲存上下文和恢復上下文的過程並不是「免費」的,需要核心在 cpu 上執行才能完成。

根據測試報告,每次上下文切換都需要幾十納秒到數微秒的 cpu 時間。這個時間還是相當可觀的,特別是在程序上下文切換次數較多的情況下,很容易導致 cpu 將大量時間耗費在暫存器、核心棧以及虛擬記憶體等資源的儲存和恢復上,進而大大縮短了真正執行程序的時間。這也正是上一節中我們所講的,導致平均負載公升高的乙個重要因素。

另外,我們知道, linux 通過 tlb(translation lookaside buffer)來管理虛擬記憶體到物理記憶體的對映關係。當虛擬記憶體更新後,tlb 也需要重新整理,記憶體的訪問也會隨之變慢。特別是在多處理器系統上,快取是被多個處理器共享的,重新整理快取不僅會影響當預處理器的程序,還會影響共享快取的其他處理器的程序。

知道了程序上下文切換潛在的效能問題後,我們再來看,究竟什麼時候會切換程序上下文。

顯然,程序切換時才需要切換上下文,換句話說,只有在程序排程的時候,才需要切換上下文。linux 為每個 cpu 都維護了乙個就緒佇列,將活躍程序(即正在執行和正在等待 cpu 的程序)按照優先順序和等待 cpu 的時間排序,然後選擇最需要 cpu 的程序,也就是優先順序最高和等待 cpu 時間最長的程序來執行。

那麼,程序在什麼時候才會被排程到 cpu 上執行呢?

最容易想到的乙個時機,就是程序執行完終止了,它之前使用的 cpu 會釋放出來,這個時候再從就緒佇列裡,拿乙個新的程序過來執行。其實還有很多其他場景,也會觸發程序排程,在這裡我給你逐個梳理下。

其一,為了保證所有程序可以得到公平排程,cpu 時間被劃分為一段段的時間片,這些時間片再**流分配給各個程序。這樣,當某個程序的時間片耗盡了,就會被系統掛起,切換到其它正在等待 cpu 的程序執行。

其二,程序在系統資源不足(比如記憶體不足)時,要等到資源滿足後才可以執行,這個時候程序也會被掛起,並由系統排程其他程序執行。

其三,當程序通過睡眠函式 sleep 這樣的方法將自己主動掛起時,自然也會重新排程。

其四,當有優先順序更高的程序執行時,為了保證高優先順序程序的執行,當前程序會被掛起,由高優先順序程序來執行。

最後乙個,發生硬體中斷時,cpu 上的程序會被中斷掛起,轉而執行核心中的中斷服務程式。

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

說完了程序的上下文切換,我們再來看看執行緒相關的問題。

執行緒與程序最大的區別在於,執行緒是排程的基本單位,而程序則是資源擁有的基本單位。說白了,所謂核心中的任務排程,實際上的排程物件是執行緒;而程序只是給執行緒提供了虛擬記憶體、全域性變數等資源。所以,對於執行緒和程序,我們可以這麼理解:

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

第一種, 前後兩個執行緒屬於不同程序。此時,因為資源不共享,所以切換過程就跟程序上下文切換是一樣。

第二種,前後兩個執行緒屬於同乙個程序。此時,因為虛擬記憶體是共享的,所以在切換時,虛擬記憶體這些資源就保持不動,只需要切換執行緒的私有資料、暫存器等不共享的資料。

到這裡你應該也發現了,雖然同為上下文切換,但同程序內的執行緒切換,要比多程序間的切換消耗更少的資源,而這,也正是多執行緒代替多程序的乙個優勢。

為了快速響應硬體的事件,中斷處理會打斷程序的正常排程和執行,轉而呼叫中斷處理程式,響應裝置事件。而在打斷其他程序時,就需要將程序當前的狀態儲存下來,這樣在中斷結束後,程序仍然可以從原來的狀態恢復執行。

對同乙個 cpu 來說,中斷處理比程序擁有更高的優先順序,所以中斷上下文切換並不會與程序上下文切換同時發生。同樣道理,由於中斷會打斷正常程序的排程和執行,所以大部分中斷處理程式都短小精悍,以便盡可能快的執行結束。

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

程序上下文與中斷上下文的理解

一.什麼是核心態和使用者態 使用者態 使用者程式執行空間。1.程序上下文 1 程序上文 其是指程序由使用者態切換到核心態是需要儲存使用者態時cpu暫存器中的值,程序狀態以及堆疊上的內容,即儲存當前程序的程序上下文,以便再次執行該程序時,能夠恢復切換時的狀態,繼續執行。2 程序下文 其是指切換到核心態...

程序上下文與中斷上下文的理解

使用者態 使用者程式執行空間。1.程序上下文 1 程序上文 其是指程序由使用者態切換到核心態是需要儲存使用者態時cpu暫存器中的值,程序狀態以及堆疊上的內容,即儲存當前程序的程序上下文,以便再次執行該程序時,能夠恢復切換時的狀態,繼續執行。2 程序下文 其是指切換到核心態後執行的程式,即程序執行在核...

程序上下文與中斷上下文的理解

使用者態 使用者程式執行空間。1.程序上下文 1 程序上文 其是指程序由使用者態切換到核心態是需要儲存使用者態時cpu暫存器中的值,程序狀態以及堆疊上的內容,即儲存當前程序的程序上下文,以便再次執行該程序時,能夠恢復切換時的狀態,繼續執行。2 程序下文 其是指切換到核心態後執行的程式,即程序執行在核...