本篇文章基于linux操作系統保護模式,Inter x86處理器架構,主要是為了理解CPU特權極和計算機資源特權級的原理。
程序員在用戶程序開發過程中,會遇到兩個基本概念即用戶态和内核态,我們所說的模式切換,就是用戶态和内核态之間的切換。
用戶态和内核态其實是CPU的特權級,所以模式的切換就是CPU特權級的切換,模式等同于特權級,不同的模式表示CPU處于不同的特權級下,因此CPU特權級的切換不能局限于用戶态到内核态之間,理論上CPU可以在任何特權級之間互相切換。
CPU特權級表示處理器訪問計算機資源時,CPU所處的特權級即CPL,CPU處于不同的特權級,它能訪問的計算機資源範圍不同,計算機資源包括内存段(代碼段,數據段,棧段),IO設備,核心數據結構。
特權級根據權限大小,分為4個等級即0,1,2,3,如下圖
特權級
由上圖所示,特權級越小,它對應的數值越大,用戶程序通常被賦予3級特權,CPU訪問它時,CPU的特權級CPL為0,1,2,3就可以了,當CPU需要訪問更高特權級的計算機資源時,CPU就需要進行特權級切換,使得CPU處于更高的特權級下,内核被賦予0級特權,CPU如果需要訪問内核時,CPU的特權級CPL必須為0,系統程序被賦予的特權級在内核和用戶程序之間,系統程序指的是設備驅動程序,虛拟機等一些系統服務。
由上文所述,每個計算機資源都被貼上一個标簽即特權極,我們稱這種特權極為計算機資源特權極,貼上标簽後,隻有當CPU特權極CPL被切換為比這個計算機資源特權極更高或者同等特權極時,才能訪問這個計算機資源,例如某個計算機資源的特權極是2,那麼CPU訪問這個資源時,CPU特權極CPL至少和2相同,即數值上不能大于它,CPU特權極CPL必須處于即2,1,0時才可以訪問。
我們把計算機資源特權極根據資源類型不同分為内存段特權極,IO設備特權極,核心數據結構存儲在内存中,操作系統把它存儲在數據段中,不過有的核心數據結構被賦予單獨的特權極,其它的核心數據結構的特權極同它所在的數據段的特權極相同,因此我們總結為:計算機資源的特權極分為内存特權極和IO設備特權極,内存特權極又分為内存段特權極和核心數據結構特權極。
為了減少篇幅,便于消化理解,我會拆分為三篇文章,分為上中下三個部分,分别闡述特權極的不同話題。
計算機資源特權級之-内存特權級(DPL)
CPU特權級(CPL)的切換過程
計算機資源特權級之-IO設備特權極(IPL)
計算機資源特權級之内存特權極開發人員編寫的用戶程序通常會進行系統調用,而系統調用的代碼和數據在内核,因此一個完整的應用程序不僅包括開發人員編寫的代碼和數據,也包括内核的代碼和數據,如下圖
一個完整的應用程序
由上圖得知,一個程序無論是用戶程序,系統程序,内核都至少包括3個部分即棧段,數據段,代碼段。
代碼段存儲程序編譯後的機器指令,數據段存儲代碼段中用到的數據,例如全局變量,常量,靜态變量等,棧段存儲着棧結構數據(STACK),在函數調用過程中會動态創建和銷毀棧結構,棧結構存儲着當前函數的參數,局部變量,返回地址等,棧結構初始大小為0,随着函數的調用動态擴大和縮小,但所有的棧結構空間加起來不能超過棧段的大小。
如果用戶程序請求其它特權級下的計算機資源,勢必會調用其他特權級下代碼段,數據段,棧段,一旦調用了,操作系統就會把其他特權級下的代碼段,數據段,棧段鍊接進來,因此廣泛地說,一個完整的用戶程序可能包括所有特權級下的代碼段,數據段,棧段,因此每個用戶程序下的每個特權級下有各自獨立的代碼段,數據段,棧段,例如對于棧來說,用戶态下是用戶棧,内核态是内核棧,系統程序是系統程序棧。
内核代碼段,數據段,棧段在計算機啟動的時候,由内核自己把它的内核代碼段,數據段,棧段設置為特權級0,其它特權級下的代碼段,數據段,棧段統統由操作系統從文件中加載到内存,然後在内存中分配固定大小的代碼段,數據段,棧段,并給這些内存段設置不同的特權極,也就是說它們的特權級是由操作系統決定的,那麼這些計算機資源的特權級存儲在哪裡呢?
特權級存儲在一個叫做描述符的結構裡,如下圖所示。
描述符層次結構
如上圖所示,每個描述符占用8個字節,描述符分為段描述符,門描述符,而門描述符又分為任務門描述符,調用門描述符,中斷門描述符,陷阱門描述,其實描述符還包括LDT描述符和TSS描述符,不過本篇文章不涉及到這些,因此不把它們加入進來。
描述符又存儲在哪裡呢?答案是存儲在描述符表中,而描述符表又存儲在内存中的一個特殊的數據段中,每個描述符表可以存儲8192個描述符,那麼一個描述符表最多占用的空間就是8192 *8,等于65536個字節即64kb,描述符表分為三種,如下圖:
描述符表層次結構
由上圖所示,描述表分為三類即中斷描述符表(IDT),全局描述符表(GDT),局部描述符表(LDT)。
GDT和LDT中可以存儲段描述符,調用門描述符,任務門描述符,IDT中可以存儲調用門描述符,任務門描述符,中斷門描述符,陷阱門描述符,可見調用門描述符和任務門描述符可以存儲在上圖中的三種描述符表中,中斷門描述符和陷阱門描述符隻能存儲在IDT中,段描述符隻能存儲在GDT和LDT中。
GDT和LDT唯一的區别就是:是否全局的,GDT是全局的,所有程序共享的,LDT是局部的,隻屬于某個程序内部,IDT也是全局的,通常操作系統分配的内存段例如代碼段,數據段,棧段的段描述符即可以存儲在GDT也可以存儲LDT中,這個取決于操作系統的實現,為了便于理解,後文不再區分GDT和LDT,統一按照GDT闡述。
那麼我們如何定位一個描述符呢,我們已經知道了描述符存儲在描述符表中,那麼我們可以把描述符表看做一個數組,這個數組的元素是描述符,那麼我們隻需要知道數組的起始地址和數組的索引,就可以定位一個描述符,數組的起始地址即描述符表的起始地址已經由操作系統存儲在專門的寄存器了,索引在哪裡呢?對于IDT,它的索引是中斷相量号,對于GDT來說,它的索引是選擇子,選擇子長度為16位,其中高13位為GDT數組的真正索性,低2位是一個叫RPL的東東,大緻意思是請求特權極,點到為止,後面會詳細介紹,第3位不再介紹,無關輕重。兩張圖可以解釋描述符定位過程,如下圖:
對于中斷描述符表(IDT)定位描述符
由上圖所示,對于中斷描述符表(IDT),它的起始地址存儲在IDTR寄存器中,中斷向量号是一個無符号整數,因為描述符占用8個字節,所以我們隻需要IDTR寄存器中的起始地址加上中斷向量号*8就可以得出描述符在内存中的位置。
對于全局描述符表(GDT)定位描述符
由上圖所示,對于全局描述符表,它的起始地址存儲在GDTR寄存器中,選擇子的高13位是真正的索引,因為每個描述符占用8個字節,所以我們隻需要GDTR寄存器中的起始地址加上真正索引*8就可以得出描述符在内存中的位置。
好了,描述符和描述符表的分類,以及描述符的定位過程介紹完了,下面我們來介紹下描述符即段描述符和門描述符。
段描述符:
操作系統加載程序和數據後,會在内存中分配代碼段,數據段,棧段,他們的大小是固定的,操作系統分配好3個内存段後,會建立3個段描述符即代碼段描述符,數據段描述符,棧段描述符,段描述符的簡化版格式如下:
段描述符簡化版
由上圖所示,為了便于理解,整理了一個段描述符的簡化版本,我們逐個看起
段的起始地址:就是操作系統分配的内存段的起始地址。
段特權級DPL_SEG:操作系統根據程序類型來分配的,例如對于用戶程序,操作系統為它賦予特權極3,而設備驅動程序,操作系統為它賦予特權極很可能就是1,這是本篇最重要的概念之一,它表示了CPU訪問這個内存段時,CPU特權級(CPL)要高于或者等于它(DPL_SEG),假如DPL_SEG為2,那麼CPU特權級(CPL)要想訪問這個段,CPL必須高于或者等于2即數值上小于等于2,也就是說,0,1,2這三種CPU特權級(CPL)都可以訪問它。
段權限:包括可讀,可寫,可執行,是不是一緻性代碼段,是否被CPU訪問過等,總共占用了4位,通過任意的權限組合,可以區分出一個段是代碼段還是數據段,例如代碼段通常是可執行,可讀,不可寫,另外代碼段根據是不是一緻性分為:不一執行代碼段和一緻性代碼段,大部分的代碼段都是非一緻性的,一緻性的代碼段是幹啥的,先不管它,後面說到CPU特權級切換時再說。
段大小:就是一個段的大小,表示了一個段從起始地址到段結束地址之間的範圍。
其它:其它的位置如E位,表示段是向上或者向下擴展,通過這個位可以區分一個段是數據段還是棧段,向下擴展表示是棧段,向上擴展表示是數據段。
好了,段描述符介紹完了,再來看看門描述符。
門描述符分為任務門描述符,調用門描述符,中斷門描述符,陷阱門描述的,門描述主要包括兩部分,如下圖為門描述的簡化版本
第一部分是一個處理程序的地址,我們可以把處理程序理解為一個函數,第二部分是這個門描述符的特權極,我們叫它DPL_DOOR。
我們上文說過,内存特權極分為内存段特權極和核心數據結構特權極,門描述符就是核心數據結構,它有自己獨立的特權級,我們稱它為DPL_DOOR即門特權極。
這裡可以簡單總結下:内存特權級分為内存段特權級(DPL_SEG,它在段描述符中)和門特權級(DPL_DOOR,它在門描述符中),内存段特權級(DPL_SEG)表示CPU訪問這個内存段時,CPU特權級(CPL)必須高于或者等于DPL_SEG,從數值的角度來看即CPL<=DPL_SEG,門特權級(DPL_DOOR)表示CPU訪問門描述符時,CPU特權級(CPL)必須高于或者等于DPL_DOOR,從數值的角度來看即CPL<=DPL_DOOR,無論内存段還是門描述符都屬于計算機資源,通常訪問這些資源時,CPU特權級高于或者等于這些資源所需的特權級也是正常的,從這個角度理解就可以了。
門描述符的作用就是定位一段處理程序,對于調用門描述符,中斷門描述符,陷阱門描述,它們定位這段處理程序是通過選擇子和處理程序偏移量,我們知道一個選擇子可以定位到一個段描述符,這個段描述符存儲了内存段的起始地址,對于處理程序來說,它是存儲在代碼段中的,因此知道了選擇子就可以知道一個代碼段的起始地址,然後處理程序不總是在代碼段的起始位置,它通常需要一個偏移量,因此代碼段的起始地址加上這個偏移量就定位到了處理程序的起始地址,對于任務門描述符,沒有偏移量,它的偏移量存儲在其它地方,我們後續不會再闡述任務門,因此不再對它進行介紹。
那麼段描述符和門描述符的區别是什麼呢,我們大概已經理解了吧,段描述符描述一個内存段的起始地址,大小,段特權級(DPL_SE),權限等,内存段可以是代碼段,數據段,棧段,這個内存段裡面存儲什麼,并不關心,而門描述符則描述了一段具體的處理程序,這段具體的程序肯定在代碼段中的指定位置(因此需要選擇子和偏移量),并且已經确定它是實現什麼功能了,例如一個中斷門描述符可以指向缺頁中斷處理程序,這個缺頁中斷處理程序隻負責缺頁的相關邏輯。
關于内存特權級闡述到這裡了,我們來總結下吧
總結
每個計算機資源例如本文闡述的内存段(數據段,代碼段,棧段)和核心存儲結構(門描述符)都被操作系統打上了特權級标簽,我們稱這些内存資源的特權級為DPL,在文章裡内存段特權級我們叫做段特權級(DPL_SEG),門描述符的特權級我們叫做門特權級(DPL_DOOR),通常我們訪問内存段或者門描述符時,CPU特權級(CPL)必須高于或者等于内存特權級DPL(DPL_SEG和DPL_DOOR),從數值的角度看CPL<=DPL(DPL_SEG和DPL_DOOR)。
我們開發的程序中的代碼,數據被操作系統加載後,會在内存中創建内存段即代碼段,數據段,棧段,然後針對每個段創建一個段描述符,存儲在全局描述符表中(GDT),我們根據段描述符在GDT中的索引和段特權級DPL_SEG,創建選擇子,選擇子的高13位存儲索引,低2位存儲請求特權級(RPL),然後将3個選擇子存儲在内存中。
RPL為請求特權級,通常我們會通過請求一個選擇子,然後根據選擇子獲取對應的描述符,然後根據描述符去訪問内存段資源和門描述符,那麼RPL就表示選擇子是由哪個特權級下的代碼請求的,可以是操作系統,也可以是用戶程序,通常一個選擇子的RPL等于這個選擇子指向的描述符中的特權級(DPL(DPL_SEG和DPL_DOOR)),RPL可以被修改,可以是善意的修改,也可以是惡意的修改,就先講到這裡,RPL的其它闡述就放在特權級切換文章中闡述,這裡不再深究。
當CPU要執行某段代碼時,操作系統會将代碼段選擇子加載到CS寄存器中,然後CPU可以根據CS寄存器中的選擇子查找GDT,找到段描述符,然後根據段描述符找到代碼段的起始地址,然後就可以從起始地址處執行代碼了,在代碼的執行過程中,會用到全局變量數據,常量或者靜态數據,那麼操作系統會将該程序的數據段選擇子加載到DS寄存器中,然後CPU可以根據DS寄存器中的選擇子查找GDT,找到了段描述符,然後根據段描述符找到數據段的起始地址,再加上指令中操作數(數據的偏移量),定位到數據,随着函數或者處理程序的不斷調用,會涉及到出棧和入棧,因此操作系統會将該程序的棧段選擇子加載到SS寄存器中,然後CPU可以根據SS寄存器中的選擇子查找GDT,找到了段描述符,然後根據段描述符找到棧段的起始地址,然後創建棧結構,棧結構是可以随着函數的調用嵌套的,也可以随着一個函數的返回而銷毀,每個棧結構都有個指針即棧頂指針,棧頂指針存儲在棧指針寄存器中(ESP),用于指向當前棧的棧頂,還有一個指針即棧底指針,用于表示一個棧結構的起始位置,它存儲在棧底寄存器(EBP),對于一個棧結構來說EBP是固定不變的。
那麼門描述符幹啥用的呢,門描述符用于指向一段處理程序,這段處理程序實現某個具體功能,那麼門描述符的具體用途是什麼呢,我們可以先留個底,門描述符的用途就是實現系統調用,再廣泛一點就是實現不同特權級下切換的。
好了,内存特權級介紹這裡了,下篇文章将重點闡述不同特權級下的代碼和數據如何切換,需要消化本篇文章的概念後,才能理解下一篇文章。
番外篇段描述符是在操作系統加載程序時,創建内存段的同時,創建了段描述符和選擇子,各類門描述符是操作系統啟動的時候,自動創建的并且内置到GDT或者IDT中的。
,