【自制作業系統07】深入淺出特權級
一、到目前為止的程式流程圖
本講我們不繼續寫任何程式碼,而是專門拿出一講來說說特權級的事,為後續的工作做一個知識儲備。這段內容太難啃了,也可能我恰好對這塊不太感冒,反正我是噁心了好久才啃下來。
為了讓大家清楚目前的程式進度,畫了到目前為止的程式流程圖,如下
二、什麼時候處理器會進行特權級檢查
為什麼要進行特權級檢查,我就不說太多了,簡單理解,作業系統不希望使用者程序訪問核心資料,所以需要給指令呀還有資料呀都附上一個特權級的屬性,讓程式受限制。
特權級分為 0 1 2 3 四種,我們常說的 使用者態 就是最低等級的 3 特權級,核心態 就是最高等級的 0 特權級。
處理器在 訪問資料 或 跳轉到程式碼 時,需要進行特權級檢查,特權級檢查的具體細則在下面有具體描述。這裡我先把大致的思想方向總結出來:
- 不論是訪問資料,還是跳轉到程式碼,特權級檢查僅發生在 重新載入選擇子 時,而不是每條指令都檢查一遍。
- 對於 訪問資料 來說,只能高特權級的指令訪問地特權級的資料
- 對於 跳轉到程式碼 來說,只能平級跳轉,如果想從低特權級跳到高特權級需要通過 門,如果想從高特權級跳到低特權級需要通過 返回指令
一些術語
- CPL:處理器當前特權級
- DPL:段或門的特權級,段描述符或者門描述符的 DPL 欄位(45-46位)
- RPL:請求特權級,選擇子 RPL 欄位(0-1位)
特權級檢查的時機與條件
受訪者為資料時
檢查時機:特權級檢查會發生在往 資料段暫存器 中載入 段選擇子 的時候,資料段暫存器包括 DS 和附加段暫存器 ES、FS、GS,如
mov ds,ax
檢查條件:CPL <= 目標資料段DPL && RPL <= 目標資料段DPL (只能高特權級的指令訪問地特權級的資料)
受訪者為程式碼時
檢查時機:特權級檢查會發生在能夠 改變 程式碼 段暫存器 CS 和 指令指標暫存器 EIP 的指令中,即這些指令要麼改變 EIP,要麼改變 CS 和 EIP。例如 call、jmp、int、ret、sysexit 等能改變程式執行流的指令,如
call 核心選擇子
- 檢查條件
- 無門結構且目標為非一致程式碼段:CPL = RPL = 目的碼段DPL
- 無門結構且目標為一致程式碼段:CPL >= 目標資料段DPL && RPL >= 目標資料段DPL
- 有門結構:DPL_GATE >= CPL >= DPL_CODE && RPL <= DPL_GATE(從低特權級跳到高特權級需要通過門)
總結成最精煉的一句話就是:資料只能高的訪問低的,程式碼只能從低的跳到高的(門或一致),從高到低只有返回指令可以完成
三、門描述符
門描述符一共有四種,分別是
- 任務門描述符
- 中斷門描述符
- 跳轉門描述符
- 呼叫門描述符
這些描述符也是記錄在 全域性描述符表 GDT 中的,與之前說的 段描述符 一樣。所以這裡把之前的段描述符,以及今天要說的四種門描述符,都畫在下面的圖中
四、呼叫門跳轉流程
門描述符的訪問流程是類似的,這裡我們用 呼叫門 來舉例。
沒有門描述符的時候,我們用 jmp 指令指向一個普通的段描述符,經過一次拼接(段基址 + 偏移地址)就得到了邏輯地址。
呼叫門是用 jmp 或者 call 指令跳轉過去的,當指向一個呼叫門時,無非就是多一次拼接而已,最開始的 選擇子:偏移地址 中的 選擇子 用來定位一個門描述符,偏移地址 則被忽略了,如下圖。
五、呼叫門跳轉特權級檢查
直觀地說就是:當前特權級必須比門特權級高,又必須比最終要跳到的程式碼段的特權級低
下面用一個呼叫門的具體例子梳理一下整個過程,由於書中的描述太精彩了,我看完之後對整個流程的理解又有了一大飛躍,所以我原封不動貼上過來:
假設當前處理器正在 DPL 為 3 的程式碼段上執行,即正在執行使用者程式,故處理器當前特權級 CPL 為 3。此時使用者程序想獲取安裝的實體記憶體大小,該資料儲存在作業系統的資料段中,該段 DPL 為 0。由於當前執行的是使用者程式,CPL 為 3,所以無法訪問 DPL 為 0 的資料段。於是它使用呼叫門向系統救助。呼叫門是作業系統安裝在全域性描述符表 GDT 中的,為了讓使用者程序可以使用此呼叫門,作業系統將該呼叫門描述符的 DPL 設為 3。該呼叫門只需要一個引數,就是使用者程式用於儲存系統記憶體容量的緩衝區所在資料段的選擇子和偏移地址。呼叫門描述符中記錄的就是核心服務程式所在程式碼段的選擇子及在程式碼段內的偏移量。使用者程序用“call 呼叫門選擇子”的方式使用呼叫門,此呼叫門選擇子是由作業系統提供的,該選擇子的 RPL 為 3,此時如果使用者偽造一個呼叫門選擇子也沒用,因為此選擇子是用來索引門描述符的,並不用來指向緩衝區的選擇子,呼叫門選擇子中的高 13 位索引值必須要指向門描述符在 GDT 中的位置,選擇子中低 2 位的 RPL 偽造也沒意義,因為此時 CPL 為 3,是短板,以它為主。此時處理器便進行特權級檢查,CPL 為 3,RPL 為 3,門描述符 DPL 為 3,即數值上(CPL≤DPL && RPL≤DPL)成立,初步檢查通過。接下來還要再將 CPL 與門描述符中選擇子所對應的程式碼段描述符 DPL 比較,這是呼叫門對應的核心服務程式的 DPL,為敘述方便將其記作 DPL_CODE。由於 DPL_CODE 是核心程式的特權級,所以DPL_CODE 為 0,CPL 為 3,即數值上滿足 CPL≥DPL_CODE,CPL 比目標特權級低,檢查通過,該使用者程式可以用呼叫門,於是處理器的當前特權級 CPL 的值用 DPL_CODE 代替,記錄在 CS.RPL 中,此時CPL 變為 0。接下來,處理器便以 0 特權級的身份開始執行該核心服務程式,由於該服務程式的引數是使用者提交的緩衝區所在的資料段的選擇子及偏移量,為避免使用者將緩衝區指向了核心的資料區,安全起見,在該核心服務程式中,作業系統將這個使用者所提交的選擇子的 RPL 變更為使用者程序的 CPL,也就是指向緩衝區所在段的選擇子的 RPL 變成了 3。前面說過,引數都是核心在 0 級棧中獲得的,雖然使用者程序將緩衝區的選擇子及偏移量壓在了 3 特權級棧中,但由於呼叫門的特權級變換,引數已經由處理器在韌體一級上自動複製到 0 特權級棧中了。使用者的程式碼段暫存器 CS 也在特權級發生變化時,由處理器自動壓入到 0 特權級棧中,所以作業系統需要的引數都可以在自己的 0 特權級棧中找到。使用者緩衝區的選擇子修改過後,接下來核心服務程式將使用者所需要的記憶體容量大小寫到這個選擇子和使用者提交的偏移量對應的緩衝區。如果使用者程式想搞破壞,所提交的這個緩衝區選擇子指向的目標段不是使用者程序自己的資料段,而是核心資料段或核心程式碼段,由於目標段的 DPL 為 0,雖然此時已在核心中執行,CPL 為 0,但選擇子 RPL 已經被改為 3,數值上不滿足 CPL≤DPL && RPL≤DPL,往緩衝區中的寫入被拒絕,處理器引發異常。如果使用者程式提交的緩衝區選擇子確實指向使用者程式自己的資料段,DPL 則為 3,數值上滿足 CPL≤DPL && RPL≤DPL,往緩衝區中的寫入則會成功。如果中斷服務程式內部再有訪問核心自己記憶體段的操作,還會按照數值上(CPL≤DPL && RPL≤DPL)的策略進行新一輪的特權檢測。通常,如果不是使用者程式向核心提交緩衝區地址來接收資料的話,核心不會主動訪問使用者的記憶體段,多是訪問自己的資料段或程式碼段,核心服務程式中若訪問核心自己的記憶體段,由於記憶體段的 DPL 為 0,所以段選擇子的 RPL 也必須為 0
六、總結
特權級檢查又是作業系統與處理器打配合的經典案例,處理器會在硬體層面做特權級檢查的工作,而作業系統負責在軟體層面定義特權級需要的相關資料(如選擇子和門描述符)
正常情況下程式碼只能平級跳轉,除非是用門結構實現低跳高,或者返回指令實現高跳低。而資料只能是高特權級指令訪問低特權級的資料。
在記憶體中的資料和指令本沒有特權級的概念,本身也沒有訪問者或受訪者的概念。特權級被賦予在選擇子的 RPL 位,或者描述符的 DPL 位,配合著這兩個東西,指令和資料才有特權級的屬性,單獨的程式碼和資料討論特權級是沒有意義的。這也順利成章地證明了處理器不會每執行一條指令就去檢查特權級,只是某些條件下才進行一次特權級檢查。
寫在最後:開源專案和課程規劃
如果你對自制一個作業系統感興趣,不妨跟隨這個系列課程看下去,甚至加入我們,一起來開發。
參考書籍
《作業系統真相還原》這本書真的贊!強烈推薦
專案開源
專案開源地址:https://gitee.com/sunym1993/flashos
當你看到該文章時,程式碼可能已經比文章中的又多寫了一些部分了。你可以通過提交記錄歷史來檢視歷史的程式碼,我會慢慢梳理提交歷史以及專案說明文件,爭取給每一課都準備一個可執行的程式碼。當然文章中的程式碼也是全的,採用複製貼上的方式也是完全可以的。
如果你有興趣加入這個自制作業系統的大軍,也可以在留言區留下您的聯絡方式,或者在 gitee 私信我您的聯絡方式。
課程規劃
本課程打算出系列課程,我寫到哪覺得可以寫成一篇文章了就寫出來分享給大家,最終會完成一個功能全面的作業系統,我覺得這是最好的學習作業系統的方式了。所以中間遇到的各種坎也會寫進去,如果你能持續跟進,跟著我一塊寫,必然會有很好的收貨。即使沒有,交個朋友也是好的哈哈。
目前的系列包括
- 【自制作業系統01】硬核講解計算機的啟動過程
- 【自制作業系統02】環境準備與啟動區實現
- 【自制作業系統03】讀取硬碟中的資料
- 【自制作業系統04】從真實模式到保護模式
- 【自制作業系統05】開啟記憶體分頁機制
- 【自制作業系統06】終於開始用 C 語言了,第一行核心程式碼!