咱們都知道,Linux 是一個多任務操做系統,它支持遠大於 CPU 數量的任務同時運行。固然,這些任務實際上並非真的在同時運行,而是由於系統在很短的時間內,將 CPU 輪流分配給它們,形成多任務同時運行的錯覺。編程
而在每一個任務運行前,CPU 都須要知道任務從哪裏加載、又從哪裏開始運行,也就是說,須要系統事先幫它設置好CPU 寄存器和程序計數器設計模式
CPU 寄存器和程序計數器就是 CPU 上下文,由於它們都是 CPU 在運行任何任務前,必須的依賴環境。緩存
就是先把前一個任務的 CPU 上下文(也就是 CPU 寄存器和程序計數器)保存起來,而後加載新任務的上下文到這些寄存器和程序計數器,最後再跳轉到程序計數器所指的新位置,運行新任務。性能優化
而這些保存下來的上下文,會存儲在系統內核中,並在任務從新調度執行時再次加載進來。這樣就能保證任務原來的狀態不受影響,讓任務看起來仍是連續運行。微信
根據任務的不一樣,能夠分爲如下三種類型數據結構
Linux 按照特權等級,把進程的運行空間分爲內核空間和用戶空間,分別對應着下圖中, CPU 特權等級的 Ring 0 和 Ring 3。架構
進程既能夠在用戶空間運行,又能夠在內核空間中運行。進程在用戶空間運行時,被稱爲進程的用戶態,而陷入內核空間的時候,被稱爲進程的內核態。ide
從用戶態到內核態的轉變,須要經過系統調用來完成。好比,當咱們查看文件內容時,就須要屢次系統調用來完成:首先調用 open() 打開文件,而後調用 read() 讀取文件內容,並調用 write() 將內容寫到標準輸出,最後再調用 close() 關閉文件。函數
在這個過程當中就發生了 CPU 上下文切換,整個過程是這樣的:
一、保存 CPU 寄存器裏原來用戶態的指令位
二、爲了執行內核態代碼,CPU 寄存器須要更新爲內核態指令的新位置。
三、跳轉到內核態運行內核任務。
四、當系統調用結束後,CPU 寄存器須要恢復原來保存的用戶態,而後再切換到用戶空間,繼續運行進程。性能
因此,一次系統調用的過程,實際上是發生了兩次 CPU 上下文切換。(用戶態-內核態-用戶態)
不過,須要注意的是,系統調用過程當中,並不會涉及到虛擬內存等進程用戶態的資源,也不會切換進程。這跟咱們一般所說的進程上下文切換是不同的:進程上下文切換,是指從一個進程切換到另外一個進程運行;而系統調用過程當中一直是同一個進程在運行。
因此,系統調用過程一般稱爲特權模式切換,而不是上下文切換。系統調用屬於同進程內的 CPU 上下文切換。但實際上,系統調用過程當中,CPU 的上下文切換仍是沒法避免的。
首先,進程是由內核來管理和調度的,進程的切換隻能發生在內核態。因此,進程的上下文不只包括了虛擬內存、棧、全局變量等用戶空間的資源,還包括了內核堆棧、寄存器等內核空間的狀態。
所以,進程的上下文切換就比系統調用時多了一步:在保存內核態資源(當前進程的內核狀態和 CPU 寄存器)以前,須要先把該進程的用戶態資源(虛擬內存、棧等)保存下來;而加載了下一進程的內核態後,還須要刷新進程的虛擬內存和用戶棧。
以下圖所示,保存上下文和恢復上下文的過程並非「免費」的,須要內核在 CPU 上運行才能完成。
根據 Tsuna 的測試報告,每次上下文切換都須要幾十納秒到數微秒的 CPU 時間。這個時間仍是至關可觀的,特別是在進程上下文切換次數較多的狀況下,很容易致使 CPU 將大量時間耗費在寄存器、內核棧以及虛擬內存等資源的保存和恢復上,進而大大縮短了真正運行進程的時間。這也正是致使平均負載升高的一個重要因素。
另外,咱們知道, Linux 經過 TLB(Translation Lookaside Buffer)來管理虛擬內存到物理內存的映射關係。當虛擬內存更新後,TLB 也須要刷新,內存的訪問也會隨之變慢。特別是在多處理器系統上,緩存是被多個處理器共享的,刷新緩存不只會影響當前處理器的進程,還會影響共享緩存的其餘處理器的進程。
線程與進程最大的區別在於:線程是調度的基本單位,而進程則是資源擁有的基本單位。說白了,所謂內核中的任務調度,實際上的調度對象是線程;而進程只是給線程提供了虛擬內存、全局變量等資源。
因此,對於線程和進程,咱們能夠這麼理解:
爲了快速響應硬件的事件,中斷處理會打斷進程的正常調度和執行,轉而調用中斷處理程序,響應設備事件。而在打斷其餘進程時,就須要將進程當前的狀態保存下來,這樣在中斷結束後,進程仍然能夠從原來的狀態恢復運行。
跟進程上下文不一樣,中斷上下文切換並不涉及到進程的用戶態。因此,即使中斷過程打斷了一個正處在用戶態的進程,也不須要保存和恢復這個進程的虛擬內存、全局變量等用戶態資源。中斷上下文,其實只包括內核態中斷服務程序執行所必需的狀態,包括 CPU 寄存器、內核堆棧、硬件中斷參數等。
對同一個 CPU 來講,中斷處理比進程擁有更高的優先級,因此中斷上下文切換並不會與進程上下文切換同時發生。一樣道理,因爲中斷會打斷正常進程的調度和執行,因此大部分中斷處理程序都短小精悍,以便儘量快的執行結束。
另外,跟進程上下文切換同樣,中斷上下文切換也須要消耗 CPU,切換次數過多也會耗費大量的 CPU,甚至嚴重下降系統的總體性能。因此,當你發現中斷次數過多時,就須要注意去排查它是否會給你的系統帶來嚴重的性能問題。
本文整理自極客時間:《Linux性能優化實戰》
PS:本文原創發佈於微信公衆號「不僅Java」,後臺回覆如下關鍵字獲取經典必讀書籍: Java、MySQL、Redis、Linux、mq、數據結構、設計模式、編程思想、架構。
公衆號專一分享 Java 乾貨、讀書筆記、成長思考。