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