進入內核態究竟是什么意思?
知乎上有一個問題:
進入內核態究竟是什么意思?
暫且忘記這個問題,讓我們從另一個問題出發,一步步引出這個問題的答案。
特權指令問題
現代計算機里面,同時運行了很多程序,比如Office軟件、瀏覽器、QQ、還有你現在正在看這篇文章的微信,這些程序運行起來就是一個個的進程,每個進程都有各自的內存地址空間,為了防止大家越界,大打出手亂了規矩,得要有個管家來管理大家,這個管家就是操作系統。
好了,不管是前面說的那些應用程序,還是操作系統,本質上都是CPU去執行的二進制程序指令吧,而一個CPU能夠執行的所有指令都在它的指令集里面了。
試想一下,假如指令集中的所有指令都一視同仁,任何程序都可以使用這些指令,那會出現什么問題?
那操作系統的權威性如何保證?怎么管理這一堆進程,還不反了天了?因為誰都可以執行這些指令,大家都是平起平坐啊!
所以,必須將指令集中的一部分比較敏感的劃出來,這部分只能讓操作系統來執行,其他程序不能執行,以此來捍衛操作系統的地位!
哪些指令比較敏感?
涉及到操作系統管理計算機資源的指令就是敏感指令。
以x86架構的CPU為例,中斷的打開與關閉、外部設備的輸入與輸出、進程地址空間基地址CR3寄存器的修改、中斷描述符表IDT基地址寄存器的修改、全局描述符表LDT基地址寄存器的修改···這些都是敏感操作,是普通應用程序絕對不能碰的指令:
- cli
- sti
- in
- out
- lidt
- lgdt
- ···
那如何做到讓這些敏感指令操作系統能夠執行,而普通程序又不能執行呢?
CPU在執行指令的時候又沒辦法區分這條指令是應用程序的還是操作系統的,它只是一個沒有感情的執行機器,只會悶頭執行···
為了解決這個問題,x86 CPU搞了個特權級出來,或者說是CPU提供了四個工作模式,從Ring0-Ring3,每一個模式又稱為一個環,一共四個環。0環是最高權限模式,可以執行所有指令。Ring3是最低權限模式,只能執行指令集中的一部分指令。
也就是說,CPU搞了四個工作模式出來,只有工作在Ring0模式下,才能執行上面的那些特權指令,在其他模式下如果要去執行那些指令,CPU就會拋出異常!
有了CPU的這一硬件技術支持,問題就好辦了,我們可以讓CPU在執行操作系統代碼的時候運行在Ring0模式,在執行普通應用程序代碼的時候運行在Ring3模式,這樣就解決了特權指令的問題。
內核地址空間
但這里又回到之前那個問題了:CPU如何知道現在執行的代碼是不是操作系統的呢?
一個最容易想到的解決辦法就是:把操作系統的代碼放在內存中一個特殊的區域,當CPU執行的指令地址來自這個區域時,就切換工作模式到Ring0,離開這個區域后,就切換到其他模式。
光這樣還不夠,還得加一個措施:這個區域不能讓應用程序來訪問,否則誰都能來讀寫,那還了得?
所以,除了指令增加特權級以外,在內存的訪問上,也得加上特權級。
x86架構的CPU是基于分段式+分頁式相結合的內存管理方式,所以Intel倒騰了幾下,給不同的內存段限定了不同的訪問模式,并把它記錄到了段的描述符中。
在訪問內存的時候,CPU就會拿當前段寄存器中標示的權限和要訪問的目標內存所在段段訪問權限進行對比,符合要求才能訪問,否則也會拋出異常!
上面這一部分有一些抽象了,簡單來說,操作系統在內存中圈了一塊地,把自己的代碼放在這塊地中,并設置了訪問權限:閑人免進,非Ring0權限禁止入內。
隨后,操作系統又把自己圈的這塊地映射到了每一個進程的虛擬地址空間中,這樣一來,所有進程抬頭一看自己的進程地址空間,都會看到它了:好家伙,這一塊區域被操作系統占了,咱也不敢寫,咱也不敢看。
操作系統圈的這塊地,就是內核地址空間!一般位于進程地址空間中較高的區域,以32位下Windows為例,它是在0x80000000~0xFFFFFFFF這個區域。
我們把位于這個空間中的代碼叫做操作系統的內核代碼,有時候也簡稱內核。而把應用程序代碼所活動的區域叫做用戶地址空間。
進一步,我們常把CPU執行內核代碼的模式稱為內核態,把執行用戶程序時的模式稱為用戶態。
CPU執行代碼的過程,就是不斷游走于用戶態和內核態的過程。
進入和離開
現在還有最后一個問題:內核態的進入和離開怎么實現?
假如沒有任何約束,那普通應用程序,不是隨便執行一條jmp指令就能跳進內核地址空間執行了?
應用程序可以隨便進進出出,高興了就來一個內核一日游,那還不天下大亂了?
況且話說回來,內核所在的內存空間因為權限保護,應用程序也是沒辦法jmp過去的,前面不說了嗎:閑人免進!
那怎么辦呢?
CPU提供了專門的入口,用來從用戶態進入內核態。
這幾個入口是:
1、中斷:
當硬件設備有消息來了之后,會通過中斷通知CPU,比如你移動了鼠標,敲下了鍵盤,收到了一個數據包,收到了時鐘的滴答聲···
當中斷發生時,CPU會將當前執行的上下文保存到棧中,轉入內核執行中斷處理程序。
通過中斷進入內核,入口是記錄在中斷描述符表IDT中的,由操作系統在系統啟動的時候就安排好了。
2、異常:
當CPU執行過程中發現一些異常情況,比如執行除法指令的除數是0,訪問的內存地址無效,或者訪問的內存地址屬于特權頁面等這些情況,CPU都會觸發異常。
異常和中斷的流程有一些類似,遇到異常時,CPU也會將執行的上下文保存在棧中,轉入內核執行中斷處理程序。
通過異常進入內核的入口和中斷一樣,也是記錄在IDT中的,同樣是操作系統在系統啟動的時候就安排好了。
3、系統調用:
在系統編程中,我們經常會調用很多操作系統提供的API函數,比如文件操作、內存操作、網絡操作等等,這些函數都是操作系統封裝出來的應用程序編程API,只是一個接口,真正的底層實現是位于內核中的系統調用函數。
應用層上的API通過CPU專門的指令(如sysenter/syscall)進入內核來完成對應的功能,進入內核后的入口同樣也是操作系統提前安排好了的。
總結
最后,讓我們回答最開始知乎的那個問題:進入內核態究竟是什么意思?
CPU為了進行權限管控,引入了特權級的概念,CPU工作在不同的特權級下能夠執行的指令和能夠訪問的內存區域是不一樣的。
計算機在啟動之初,CPU運行在高特權級下,操作系統率先獲得了執行權限,在內存中圈了一塊地,將自己的程序代碼放了進去,并設定了這一部分內存只有高特權級才能訪問。
隨后,操作系統在創建進程的時候,都會把自己所在的這塊內存區域映射到每一個進程地址空間中,這樣所有進程都能看到自己的進程空間中,有一塊叫“內核”的區域,這一塊區域是無法擅入的。
所謂的“進入內核態”是指:當中斷、異常、系統調用等情況發生的時候,CPU切換工作模式到高特權級模式Ring0,并轉而執行位于內核地址空間處的代碼。
看完這篇,你明白進入內核態是怎么一回事了嗎?
看不懂沒關系,下面用一張圖來總結一下:
怎么樣,這下懂了吧?
本文轉載自微信公眾號「編程技術宇宙」,可以通過以下二維碼關注。轉載本文請聯系編程技術宇宙公眾號。