程序在競爭 cpu 的時候並沒有真正執行,cpu 上下文切換就是罪魁禍首。我們都知道,linux 是乙個多工作業系統,它支援遠大於 cpu 數量的任務同時執行。當然,這些任務實際上並不是真的在同時執行,而是因為系統在很短的時間內,將 cpu 輪流分配給它們,造成多工同時執行的錯覺。而在每個任務執行前,cpu 都需要知道任務從**載入、又從**開始執行,也就是說,需要系統事先幫它設定好 cpu 暫存器和程式計數器(program counter,pc)。cpu 暫存器,是 cpu 內建的容量小、但速度極快的記憶體。而程式計數器,則是用來儲存 cpu 正在執行的指令位置、或者即將執行的下一條指令位置。它們都是 cpu 在執行任何任務前,必須的依賴環境,因此也被叫做 cpu 上下文。
根據任務的不同,cpu 的上下文切換就可以分為幾個不同的場景,也就是程序上下文切換、執行緒上下文切換以及中斷上下文切換。
linux 按照特權等級,把程序的執行空間分為核心空間和使用者空間,cpu 特權等級的 ring 0 和 ring 3。核心空間(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 暫存器、核心堆疊、硬體中斷引數等。
對同乙個 cpu 來說,中斷處理比程序擁有更高的優先順序,所以中斷上下文切換並不會與程序上下文切換同時發生。同樣道理,由於中斷會打斷正常程序的排程和執行,所以大部分中斷處理程式都短小精悍,以便盡可能快的執行結束。另外,跟程序上下文切換一樣,中斷上下文切換也需要消耗 cpu,切換次數過多也會耗費大量的 cpu,甚至嚴重降低系統的整體效能。所以,當你發現中斷次數過多時,就需要注意去排查它是否會給你的系統帶來嚴重的效能問題。
cpu 上下文切換,是保證 linux 系統正常工作的核心功能之一,一般情況下不需要我們特別關注。但過多的上下文切換,會把 cpu 時間消耗在暫存器、核心棧以及虛擬記憶體等資料的儲存和恢復上,從而縮短進**正執行的時間,導致系統的整體效能大幅下降。
上下文切換
1 系統呼叫 一次系統呼叫其實是發生了兩次cpu上下文的切換 首先將使用者態的cpu暫存器中的指令儲存在系統核心中。為了執行核心態 需要將暫存器更新為核心態指令的位置,然後跳轉到核心空間去執行任務。當核心態的指令執行完成的時候,cpu暫存器將恢復儲存在系統核心中的上一次執行的使用者態,然後切換到使用...
上下文切換
上下文切換 有時也稱做程序切換或任務切換 是指 cpu 從乙個程序或執行緒切換到另乙個程序或執行緒。上下文切換與模式切換 上下文切換只能發生在核心態中。核心態是 cpu 的一種有特權的模式,在這種模式下只有核心執行並且可以訪問所有記憶體和其他系統資源。其他的程式,如應用程式,在最開始都是執行在使用者...
上下文切換
vmstat cs 每秒上下文切換次數 的疑惑 in 每秒cpu中斷次數 shell vmstat 1 procs memory swap io system cpu r b swpd free buff cache si so bi bo in cs us sy id wa 1 0 0 45939...