3.什麼是CPU上下文切換01?
1.前言:
cpu的上下文切換一般多出現在多個程序競爭cpu的情景
2.CPU的上下文切換:
2.1介紹:
程序在競爭 CPU 的時候並沒有真正執行,為什麼還會導致系統的負載升高呢? 答案在於:CPU 上下文切換就是罪魁禍首。
Linux 是一個多工作業系統,它支援遠大於 CPU 數量的任務(通常所說的後臺程序)同時執行,但是,這並不意味這這些任務會在同一時刻就一起執行,而是系統會在很短的時間內將cpu輪流地分配給它們就行使用。造成多工同時執行的錯覺。
這裡在執行每個執行任務前,cpu需要知道任務需要從哪裡載入,又從哪裡開始執行,也就是說需要系統事先先幫他設定好cpu的暫存器和程式計數器(Program Counter)
cpu暫存器,是cpu內建的容量小、但是速度極快的記憶體。而程式計數器,則是用來儲存cpu正在執行的指令位置,或者即將執行的下一條指令的位置,它們都是cpu在執行任何任務前,必須的依賴環境,因此也被叫做cpu上下文。
因此,我們可以這樣理解CPU上文切換,cpu上下文切換,就是先把一個任務的CPU上下文(也就是cpu的暫存器和程式計數器)儲存起來,然後載入新任務的上下文到這個暫存器和程式計數器
,最後再跳轉到程式計數器所指的新位置,執行新任務。
而這些儲存下來的上下文,會儲存在系統核心中,並在任務重新排程執行時再次載入起來。這樣就能保證任務原來的狀態不受影響,讓任務看起來哈市連續執行。
這樣看起來cpu上下文切換就是更新cpu暫存器的值,本身是為了快速執行任務而設計的,但是為什麼會影響cpu的效能呢?
這是因為任務除了有程序和執行緒外,還有一種硬體通過觸發訊號,導致中斷處理程式的呼叫,這也是一種常見的任務。
所以,根據任務的不同,CPU的上下文切換就可以分為幾種不同的場景,也就是程序上下文切換、執行緒上下文切換、以及中斷上下文切換。
2.2程序上下文切換
Linux按照特權等級,把程序的執行空間分為核心空間和使用者空間,分別對應著下圖中,cpu特權等級的Ring 0 和Ring 3.
- 核心空間(Ring 0)具有最高許可權,可以直接訪問所有資源
- 使用者空間(Ring 3)只能訪問受限資源,不能直接訪問記憶體等硬體裝置,必須通過系統條用陷入到核心中,才能訪問這些特權資源。
也就說程序既可以在使用者空間執行,也可以在核心空間中執行,程序在使用者空間執行時,被稱為程序的使用者態,而陷入核心空間的時候,被稱為程序的核心態。
從使用者態到核心態的轉變,需要通過系統呼叫來完成。比如,當我們檢視檔案內容時,就需要多次系統呼叫來完成:首先呼叫open()開啟檔案,然後呼叫read()讀取檔案內容,並呼叫write()將內容寫到標準輸出,最後再呼叫close()關閉檔案。
那麼,系統呼叫的過程有沒有發生cpu上下文的切換呢?--->答案自然是有的
cpu暫存器裡原來使用者態的指令位置,需要先儲存起來,接著,為了執行核心態程式碼,cpu暫存器需要更新為核心態指令的新位置,最後才是跳轉到核心態執行核心任務。
而系統呼叫結束後,cpu暫存器需要恢復原來儲存的使用者態,然後再切換到使用者空間,繼續執行程序。所以,一次系統呼叫的過程,其實是發生了兩次cpu上下文切換。
不過,需要注意的是,系統呼叫過程中,並不會涉及到虛擬記憶體等程序使用者態的資源,也不會切換程序。這跟我們通常所說的程序上下文切換是不一樣的:
- 程序上下文切換,是指從一個程序切換到另外一個程序執行。
- 而系統呼叫的過程中一直是同一個程序在執行。
所以,系統呼叫過程通常稱為特權模式切換,而不是上下文切換。但實際上,系統呼叫過程中,cpu的上下文切換還是無法避免的。
那麼,程序上下文切換系統呼叫有什麼區別呢?
首先,你需要知道,程序是由核心來管理和排程的,程序的切換隻能發生在核心態。所以,程序的上下文不僅包括了虛擬記憶體、棧、全域性變數等使用者空間的資源,還包括核心棧、暫存器等核心空間的狀態。
因此,程序的上下文切換就比系統呼叫時多了一步:在儲存當前程序的核心狀態和cpu暫存器之前,需要先把該程序的虛擬記憶體、棧等儲存下來;而載入了下一程序的核心態後,還需要重新整理程序的虛擬記憶體和使用者棧。
如下圖所示,儲存上下文和恢復上下文的過程並不是’免費‘的,需要核心在cpu上執行才能完成。
根據TSuna的測試報告,每次上下文切換都需要幾十納秒到數微妙的cpu時間。這個時間還是相當可觀的,特別是在程序上下文切換次數較多的情況下,很容易導致cpu將大量時間耗費在暫存器、核心棧以及虛擬記憶體等資源的儲存和恢復上,進而大大縮短了真正執行程序的時間。這也正是上一節中我們所講的,導致平均負載升高的一個重要因素。
這裡我們知道了程序上下文切換潛在的效能問題後,我們再來看看,究竟什麼時候會切換程序上下文。
顯然,程序切換時才需要切換上下文,換句話說,只有在程序排程的時候,才需要切換上下文,Linux為每個cpu都維護了一個就緒佇列,將活躍程序(即正在執行和正在等待cpu的程序)按照優先順序和等待cpu的時間排序,然後選擇最需要cpu的程序,也就是優先順序最高和等待cpu時間最長的程序來執行。
那麼,程序在什麼時候才會被排程到cpu上執行呢?
最容易想到的一個時機,就是程序執行完終止了,它之前使用的cpu會釋放出來,這個時候再從就緒佇列中,拿一個新的程序過來執行
上下文切換情景:
- 其一,為了保證所有程序可以得到公平排程,cpu時間被劃分為一段段的時間片,這些時間片再被輪流分配給各個程序。這樣,當某個程序的時間片耗盡了,就會被系統掛起,切換到其它正在等待cpu的程序執行。
- 其二,程序在系統資源不足(比如記憶體不足)時,要等到資源滿足後才可以執行,這個時候程序也會被掛起,並由系統排程其它程序執行。
- 其三,當程序通過睡眠函式sleep這樣的方法將自己主動掛起時,自然也會重新排程。
- 其四,當有優先順序更高的程序執行時,為了保證高優先順序程序的執行,當前程序會被掛起,由高優先順序程序來執行。
- 其五。發生硬體中斷時,cpu上的程序會被中斷掛起,轉而執行核心中的中斷服務程式。
因此,瞭解這幾個場景是非常有必要的,因為一旦出現上下文切換的效能問題,它們就是幕後凶手。
2.3 執行緒上下文切換
執行緒和程序最大的區別在於,執行緒是排程的基本單位,而程序則是資源擁有的基本單位。說白了,所謂核心中的任務排程,實際上的排程物件是執行緒,而程序只是給執行緒提供了虛擬記憶體、全域性變數等資源。所以,對於執行緒和程序,我們可以這麼理解:
- 當程序只有一個執行緒時,可以程序就等於執行緒
- 當程序擁有多個執行緒時,這些執行緒會共享相同的虛擬記憶體和全域性變數等資源。這些資源在上下文切換時是不需要修改的。
- 另外,執行緒也有自己的私有資料,比如棧和暫存器等,這些在上下文切換時也是需要儲存的。
這樣一來,執行緒的上下文切換其實就可以分為兩種情況:
第一種,前後兩個執行緒屬於不同程序,此時,因為資源不共享,所以切換過程就跟程序上下文切換時一樣
第二種,前後兩個執行緒屬於同一個程序,此時,因為虛擬記憶體是共享的,所以在切換時,虛擬記憶體這個資源就保持不動,只需要切換執行緒的私有資料、暫存器等不共享資料。
因此,同為上下文切換,但是同進程內的執行緒切換,要比多程序間的切換消耗更少的資源,而這,也正是多執行緒代替多程序的一個優勢。
2.4 中斷上下文切換
除了前面兩種上下文切換,還有一個場景也會切換cpu上下文,那就是中斷。
為了快速響應硬體的事件,中斷處理會打斷程序的正常排程和執行,轉而呼叫中斷處理程式,響應裝置事件。而在打斷其他程序時,就需要將程序當前的狀態儲存下來,這樣在中斷結束後,程序仍然可以從原來的狀態恢復執行。
跟程序上下文不同,中斷上下文切換並不涉及到程序的使用者態。所以,即便中斷過程打斷了一個正處在使用者態的程序,也不需要儲存和恢復這個程序的虛擬記憶體、全域性變數等使用者態資源。中斷上下文,其實只包括核心態中斷服務程式執行所必需的狀態,包括 CPU 暫存器、核心堆疊、硬體中斷引數等。
對同一個 CPU 來說,中斷處理比程序擁有更高的優先順序,所以中斷上下文切換並不會與程序上下文切換同時發生。同樣道理,由於中斷會打斷正常程序的排程和執行,所以大部分中斷處理程式都短小精悍,以便儘可能快的執行結束。
另外,跟程序上下文切換一樣,中斷上下文切換也需要消耗 CPU,切換次數過多也會耗費大量的 CPU,甚至嚴重降低系統的整體效能。所以,當你發現中斷次數過多時,就需要注意去排查它是否會給你的系統帶來嚴重的效能問題。
3.小結:
總結一下,不管是哪種場景導致的上下文切換,你都應該知道:
- CPU 上下文切換,是保證 Linux 系統正常工作的核心功能之一,一般情況下不需要我們特別關注
- 但過多的上下文切換,會把 CPU 時間消耗在暫存器、核心棧以及虛擬記憶體等資料的儲存和恢復上,從而縮短程序真正執行的時間,導致系統的整體效能大幅下降。