CPU 上下文切換是什么
CPU 上下文切換,就是先把前一個任務的 CPU 上下文(也就是 CPU 寄存器和程序計數器)保存起來,然后加載新任務的上下文到這些寄存器和程序計數器,最后再跳轉到程序計數器所指的新位置,運行新任務。
CPU上下文的分類
CPU 上下文切換根據任務的不同,可以分為以下三種類型 : 進程上下文切換 - 線程上下文切換 - 中斷上下文切換
引起上下文切換的原因有哪些?
對於搶占式操作系統而言, 大體有幾種:
1、當前任務的時間片用完之后,系統CPU正常調度下一個任務;
2、當前任務碰到IO阻塞,調度線程將掛起此任務,繼續下一個任務;
3、多個任務搶占鎖資源,當前任務沒有搶到,被調度器掛起,繼續下一個任務;
4、用戶代碼(yield()方法)掛起當前任務,讓出CPU時間;
5、硬件中斷;
進程上下文切換
引起進程上下文切換的原因:
- 進程時間片耗盡;
- 系統資源不足(如內存不足);
- 進程通過睡眠函數 sleep 把自己掛起來;
- 當有優先級更高的進程運行時,為了去運行高優先級進程,當前進程會被掛起;
- 發生硬中斷,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 的進程運行。
其二,進程在系統資源不足(比如內存不足)時,要等到資源滿足后才可以運行,這個時 候進程也會被掛起,並由系統調度其他進程運行。
其三,當進程通過睡眠函數 sleep 這樣的方法將自己主動掛起時,自然也會重新調度。
其四,當有優先級更高的進程運行時,為了保證高優先級進程的運行,當前進程會被掛 起,由高優先級進程來運行。
最后一個,發生硬件中斷時,CPU 上的進程會被中斷掛起,轉而執行內核中的中斷服務程序。
了解這幾個場景是非常有必要的,因為一旦出現上下文切換的性能問題,它們就是幕后凶 手。
線程上下文切換
引起線程上下文切換的原因如下
線程與進程最大的區別在於,線程是調度的基本單位,而進程則是資源擁有的基本單位。說白了,所謂內核中的任務調度,實際上的調度對象是線程;而進程只是給線程提供了虛擬內存、全局變量等資源。
所以,對於線程和進程,我們可以這么理解:當進程只有一個線程時,可以認為進程就等於線程。當進程擁有多個線程時,這些線程會共享相同的虛擬內存和全局變量等資源。這些資源在上下文切換時是不需要修改的。另外,線程也有自己的私有數據,比如棧和寄存器等,這些在上下文換時也是需要保存的。
線程的上下文切換其實就可以分為兩種情況:
第一種, 前后兩個線程屬於不同進程。此時,因為資源不共享,所以切換過程就跟進程上下文切換是一樣。
第二種,前后兩個線程屬於同一個進程。此時,因為虛擬內存是共享的,所以在切換時, 虛擬內存這些資源就保持不動,只需要切換線程的私有數據、寄存器等不共享的數據。
雖然同為上下文切換,但同進程內的線程切換,要比多進程間的切換消耗更少的資源,而這,也正是多線程代替多進程的一個優勢。
中斷上下文切換
這邊中斷更多的是硬件設備的發生的中斷,如鼠標 單擊,鍵盤按壓等,叫硬中斷。
為了快速響應硬件的事件,中斷處理會打斷進程的正常調度和執行,轉而調用中斷處理程序,響應設備事件。而在打斷其他進程時,就需要將進程當前的狀態保存下來,這樣在中斷結束后,進程仍然可以從原來的狀態恢復運行。
跟進程上下文不同,中斷上下文切換並不涉及到進程的用戶態。所以,即便中斷過程打斷了一個正處在用戶態的進程,也不需要保存和恢復這個進程的虛擬內存、全局變量等用戶 態資源。中斷上下文,其實只包括內核態中斷服務程序執行所必需的狀態,包括 CPU 寄存器、內核堆棧、硬件中斷參數等。
對同一個 CPU 來說,中斷處理比進程擁有更高的優先級,所以中斷上下文切換並不會與進程上下文切換同時發生。同樣道理,由於中斷會打斷正常進程的調度和執行,所以大部分中斷處理程序都短小精悍,以便盡可能快的執行結束。
