CPU分时、中断和上下文切换
準備知識:
實時和分時
嵌入式操作系統可以分為實時操作系統和分時操作系統兩類。我們現實之中使用的絕大多數是分時操作系統,比如windows或者linux。但是比如汽車就必須使用實時操作系統,舉一個經常使用的實例,中高檔汽車中使用的氣囊。當報告車輛碰撞的傳感器中斷CPU后,操作系統應快速地分配展開氣囊的任務,并且不允許任何其他非實時處理進行干擾,晚一秒鐘展開氣囊比沒有氣囊的情況更糟糕,這就是一個典型的必須使用硬實時的系統。而最近特斯拉處于風口浪尖,剎車不靈,就是因為剎車輔助系統沒有及時開啟,因為他使用的還是個分時系統。所以從技術上講,特斯拉并不可靠。由于我們這里是個引子,我們不做多介紹。感興趣的同學可以看看此文:https://blog.csdn.net/zhourui1982
中斷
中斷就是打斷處理器當前的執行流程,去執行另外一些和當前工作不相干的指令,執行完之后,還可以返回到原來的程序流程繼續執行。舉個例子:“時鐘中斷”是特別重要的一個中斷,利用晶振產生的方波信號輸入,使得cpu按照這個頻率去產生中斷指令,也就是我們的時間片輪轉調度算法的基礎(時間片就是我們分配給每個任務的執行時間)。整個操作系統的活動都受到它的激勵,系統利用時鐘中斷維持系統時間、促使環境的切換,以保證所有進程共享CPU,這使得我們同時使用多個應用程序成為可能;利用時鐘中斷進行記帳、監督系統工作以及確定未來的調度優先級等工作。可以說,“時鐘中斷”是整個操作系統的脈搏。總結:中斷就是主動讓出cpu的執行權限,讓別的程序來執行。
?
內核態和用戶態
操作系統在加載的時候,會把所管理的內存劃分為兩個區域,一些處于內核態(受到操作系統保護),一些處于用戶態。用戶態是不能直接訪問內核態的地址空間的。對 32 位操作系統而言,它的尋址空間(虛擬地址空間,或叫線性地址空間)為 4G(2的32次方),也就是說一個進程的最大地址空間有4GB,其中,0-3G是屬于用戶空間,3-4G是內核空間。操作系統的核心是內核(kernel),它獨立于普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。為了保證內核的安全,現在的操作系統一般都強制用戶進程不能直接操作內核。具體的實現方式基本都是由操作系統將虛擬地址空間劃分為兩部分,一部分為內核空間,另一部分為用戶空間。針對 Linux 操作系統而言,最高的 1G 字節(從虛擬地址 0xC0000000 到 0xFFFFFFFF)由內核使用,稱為內核空間。而較低的 3G 字節(從虛擬地址 0x00000000 到 0xBFFFFFFF)由各個進程使用,稱為用戶空間。
換句話說就是, 最高 1G 的內核空間是被所有進程共享的!
在用戶態下,進程運行在用戶地址空間中,被執行的代碼要受到 CPU 的諸多檢查,它們只能訪問映射其地址空間的頁表項中規定的在用戶態下可訪問頁面的虛擬地址,且只能對任務狀態段(TSS)中 I/O 許可位圖(I/O Permission Bitmap)中規定的可訪問端口進行直接訪問。
內核有 4 項工作:
在正確實施的情況下,內核對于用戶是不可見的,它在自己的小世界(稱為內核空間)中工作,并從中分配內存和跟蹤所有內容的存儲位置。用戶所看到的內容(例如 Web 瀏覽器和文件)則被稱為用戶空間。這些應用通過系統調用接口(SCI)與內核進行交互。舉例來說,內核就像是一個為高管(硬件)服務的忙碌的個人助理。助理的工作就是將員工和公眾(用戶)的消息和請求(進程)轉交給高管,記住存放的內容和位置(內存),并確定在任何特定的時間誰可以拜訪高管、會面時間有多長。
為什么要這么劃分呢?
系統執行的代碼通過以下兩種模式之一在 CPU 上運行:內核模式或用戶模式。在內核模式下運行的代碼可以不受限制地訪問硬件,在 CPU 的所有指令中,有些指令是非常危險的,如果錯用,將導致系統崩潰,比如清內存、設置時鐘等。如果允許所有的程序都可以使用這些指令,那么系統崩潰的概率將大大增加。如果留給程序員的話也會大大加大編程和環境的復雜度,這樣設計使得硬件資源堆程序猿來說屏蔽了。而用戶模式則會限制 SCI 對 CPU 和內存的訪問。內存也存在類似的分隔情況(內核空間和用戶空間)。這兩個小細節構成了一些復雜操作的基礎,例如安全防護、構建容器和虛擬機的權限分隔。這也意味著:如果進程在用戶模式下失敗,則損失有限,無傷大雅,可以由內核進行修復。另一方面,由于內核進程要訪問內存和處理器,因此內核進程的崩潰可能會引起整個系統的崩潰。由于用戶進程之間會有適當的保護措施和權限要求,因此一個進程的崩潰通常不會引起太多問題。對于以前的 DOS 操作系統來說,是沒有內核空間、用戶空間以及內核態、用戶態這些概念的。可以認為所有的代碼都是運行在內核態的,因而用戶編寫的應用程序代碼可以很容易的讓操作系統崩潰掉。對于 Linux 來說,通過區分內核空間和用戶空間的設計,隔離了操作系統代碼(操作系統的代碼要比應用程序的代碼健壯很多)與應用程序代碼。即便是單個應用程序出現錯誤也不會影響到操作系統的穩定性,這樣其它的程序還可以正常的運行(Linux 可是個多任務系統啊!)。
所以,區分內核空間和用戶空間本質上是要提高操作系統的穩定性及可用性。
如何從用戶空間進入內核空間
其實所有的系統資源管理都是在內核空間中完成的。比如讀寫磁盤文件,分配回收內存,從網絡接口讀寫數據等等。我們的應用程序是無法直接進行這樣的操作的。但是我們可以通過內核提供的接口來完成這樣的任務。
比如應用程序要讀取磁盤上的一個文件,它可以向內核發起一個 "系統調用" 告訴內核:"我要讀取磁盤上的某某文件"。
這時需要一個這樣的機制: 用戶態程序切換到內核態, 但是不能控制在內核態中執行的指令。這種機制叫系統調用, 在CPU中的實現稱之為陷阱指令(Trap Instruction)
他們的工作流程如下:
用戶態程序將一些數據值放在寄存器中, 或者使用參數創建一個堆棧(stack frame), 以此表明需要操作系統提供的服務.用戶態程序執行陷阱指令,CPU切換到內核態, 并跳到位于內存指定位置的指令, 這些指令是操作系統的一部分, 他們具有內存保護, 不可被用戶態程序訪問。這些指令稱之為陷阱(trap)或者系統調用處理器(system call handler). 他們會讀取程序放入內存的數據參數, 并執行程序請求的服務,系統調用完成后, 操作系統會重置CPU為用戶態并返回系統調用的結果。CPU的使命就是執行程序中的指令,而且CPU內部有很多用于存放數據的寄存器,其中比較重要的一個寄存器叫EIP寄存器,它用于存儲下一條要執行的指令。除了EIP寄存器之外,還有一個比較重要的寄存器叫ESP寄存器,它用于保存程序的棧頂位置。除此之外,CPU還有很多其他用途的寄存器,如:通用寄存器EAX、EDX和段寄存器CS、DS等等。寄存器知識:http://www.360doc.com/content/19/1205/13/277688_877598997.shtml。當一個程序被執行(稱為進程)的時候,這些寄存器的值通常會被修改。所以當要切換進程執行的時候,只需要把這些寄存器的值保存下來,然后把新進程寄存器的值賦值到CPU中,那么就完成進程切換了,通常我們把這個過程稱為上下文切換。 其實際含義是任務切換, 或者CPU寄存器切換。當多任務內核決定運行另外的任務時, 它保存正在運行任務的當前狀態, 也就是CPU寄存器中的全部內容。這些內容被保存在任務自己的堆棧中, 入棧工作完成后就把下一個將要運行的任務的當前狀況從該任務的棧中重新裝入CPU寄存器, 并開始下一個任務的運行, 這一過程就是context switch。
對于一個進程來講,從用戶空間進入內核空間并最終返回到用戶空間,這個過程是十分復雜的。舉個例子,比如我們經常接觸的概念 "堆棧",其實進程在內核態和用戶態各有一個堆棧。運行在用戶空間時進程使用的是用戶空間中的堆棧,而運行在內核空間時,進程使用的是內核空間中的堆棧。所以說,Linux 中每個進程有兩個棧,分別用于用戶態和內核態。
下圖簡明的描述了用戶態與內核態之間的轉換:
‘
那么從用戶態進程到內核態進程是怎么發生的?
- 用戶線程
- 由應用程序創建、調度、撤銷,不需要內核的支持(內核不感知)
- 由于不需要內核的支持,便不涉及用戶態/內核態的切換,消耗的資源較少,速度也較快
- 由于需要應用程序控制線程的輪換調度,當有一個用戶線程被阻塞時,整個所屬進程便會被阻塞,同時在多核處理器下只能在一個核內分時復用,不能充分利用多核優勢
- 內核線程
- 由內核創建、調用、撤銷,并由內核維護線程的上下文信息及線程切換
- 由于內核線程由內核進行維護,當一個內核線程被阻塞時,不會影響其他線程的正常運行,并且多核處理器下,一個進程內的多個線程可以充分利用多核的優勢同時執行
- 由于需要內核進行維護,在線程創建、切換過程中便會涉及用戶態/內核態的切換,增加系統消耗
核心是什么?是內核實現幫助我們實現了多核的分時復用的調度算法
在linux操作系統中,往往都是通過fork函數創建一個子進程來代表內核中的線程(用戶線程陷入內核態其實就是在內核里面使用fork系統調用創建了一個子線程),在fork完一個子進程后,還需要將父進程中大部分的上下文信息復制到子進程中,消耗大量cpu時間用來初始化內存空間,產生大量冗余數據。為了避免上述情況,輕量級進程(Light Weight Process, LWP)便出現了,其使用clone系統調用創建子進程,過程中只將部分父進程數據進行復制,沒有被復制的資源可以通過指針進行數據共享,這樣一來LWP的運行單元更小、運行速度更快。當然這取決于線程模型是怎樣的可以使用fork也可以使用clone。可參考此文:在linux操作系統中,往往都是通過fork函數創建一個子進程來代表內核中的線程,在fork完一個子進程后,還需要將父進程中大部分的上下文信息復制到子進程中,消耗大量cpu時間用來初始化內存空間,產生大量冗余數據。關于fork系統調用https://www.cnblogs.com/cccc2019fzs/p/13110431.html
java新起一個線程就是用的clone命令:關于java的線程和操作系統線程的關系:https://www.yuque.com/cdsnow/blog/hd1cz8
fork(進程)或者clone(線程)的過程中具體切換了什么內容呢?
一個進程的上下文可以分為三個部分:用戶級上下文、寄存器上下文以及系統級上下文。
進程上下文是進程執行活動全過程的靜態描述。我們把已執行過的進程指令和數據在相關寄存器與堆棧中的內容稱為進程上文,把正在執行的指令和數據在寄存器與堆棧中的內容稱為進程正文,把待執行的指令和數據在寄存器與堆棧中的內容稱為進程下文。
用戶級上下文:正文、數據、用戶棧以及共享存儲區;
寄存器上下文:程序寄存器(IP),即CPU將執行的下條指令地址,處理機狀態寄存器(EFLAGS),棧指針,通用寄存器;
系統級上下文:進程表項(proc結構)和U區,在Linux中這兩個部分被合成task_struct,區表及頁表(mm_struct , vm_area_struct, pgd, pmd, pte等),核心棧等。
全部的上下文信息組成了一個進程的運行環境。當發生進程調度時,必須對全部上下文信息進行切換,新調度的進程才能運行。進程就是上下文的集合的一個抽象概念。
一般進程切換分兩步
1.切換頁目錄以使用新的地址空間
2.切換內核棧和硬件上下文。
對于linux來說,線程和進程的最大區別就在于地址空間。
對于線程切換,第1步是不需要做的,第2是進程和線程切換都要做的。所以明顯是進程切換代價大
線程上下文切換和進程上下問切換一個最主要的區別是線程的切換虛擬內存空間依然是相同的,但是進程切換是不同的。這兩種上下文切換的處理都是通過操作系統內核來完成的。內核的這種切換過程伴隨的最顯著的性能損耗是將寄存器中的內容切換出。
另外一個隱藏的損耗是上下文的切換會擾亂處理器的緩存機制。簡單的說,一旦去切換上下文,處理器中所有已經緩存的內存地址一瞬間都作廢了。還有一個顯著的區別是當你改變虛擬內存空間的時候,處理的頁表緩沖(processor’s Translation Lookaside Buffer (TLB))或者相當的神馬東西會被全部刷新,這將導致內存的訪問在一段時間內相當的低效。但是在線程的切換中,不會出現這個問題。
什么時候產生上下文切換呢?
既然用戶態的進程必須切換成內核態才能使用系統的資源,那么我們接下來就看看進程一共有多少種方式可以從用戶態進入到內核態。
整體結構
接下來我們從內核空間和用戶空間的角度看一看整個 Linux 系統的結構。它大體可以分為三個部分,從下往上依次為:硬件 -> 內核空間 -> 用戶空間。如下圖所示(此圖來自互聯網):
在硬件之上,內核空間中的代碼控制了硬件資源的使用權,用戶空間中的代碼只有通過內核暴露的系統調用接口(System Call Interface)才能使用到系統中的硬件資源。其實,不光是 Linux,Windows 操作系統的設計也是大同小異。
實際上我們可以將每個處理器在任何指定時間點上的活動概括為下列三者之一:
- 運行于用戶空間,執行用戶進程。
- 運行于內核空間,處于進程上下文,代表某個特定的進程執行。
- 運行于內核空間,處于中斷上下文,與任何進程無關,處理某個特定的中斷。
以上三點幾乎包括所有的情況,比如當 CPU 空閑時,內核就運行一個空進程,處于進程上下文,但運行在內核空間。
說明:Linux 系統的中斷服務程序不在進程的上下文中執行,它們在一個與所有進程都無關的、專門的中斷上下文中執行。之所以存在一個專門的執行環境,就是為了保證中斷服務程序能夠在第一時間響應和處理中斷請求,然后快速地退出。
這段摘自:https://www.cnblogs.com/sparkdev/p/8410350.html
參考:https://blog.csdn.net/qq_42756396/article/details/108089158?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242
https://blog.csdn.net/u013178472/article/details/81115080
https://www.cnblogs.com/cccc2019fzs/p/13110431.html
https://blog.csdn.net/vjhghjghj/article/details/105353550?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-4.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-4.control
https://blog.csdn.net/weixin_39816946/article/details/110395042?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242
https://blog.csdn.net/qq_42756396/article/details/108089158?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242
總結
以上是生活随笔為你收集整理的CPU分时、中断和上下文切换的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot事件与监听机制
- 下一篇: 计算机IO系列(二)BIO/NIO/多路