1. 程式人生 > >任務和特權級保護(一)——《x86組合語言:從真實模式到保護模式》讀書筆記27

任務和特權級保護(一)——《x86組合語言:從真實模式到保護模式》讀書筆記27

本文及後面的幾篇文章是原書第14章的讀書筆記。

1.LDT(區域性描述符表)

在之前的學習中,不管是核心程式還是使用者程式,我們都是把段描述符放在GDT中。但是,為了有效實施任務間的隔離,處理器建議每個任務都應該有自己的描述符表,稱為區域性描述符表LDT(Local Descriptor Table),並且把專屬於這個任務的那些段描述符放到LDT中。

和GDT一樣,LDT也是用來存放段描述符的。不同之處在於,LDT只屬於某個任務。或者說,每個任務都有自己的LDT,每個任務私有的段,都應當在LDT中進行描述。

需要注意的是:LDT的0號槽位,是有效的,可以使用的。

GDT是全域性性的,為所有的任務服務,是它們所共有的,我們只需要一個GDT就夠了。為了追蹤GDT,訪問它內部的描述符,處理器使用了GDTR暫存器。
和GDT不同,LDT的數量不止一個,具體有多少,根據任務的數量而定。為了追蹤和訪問這些LDT,處理器使用了LDTR(區域性描述符表暫存器)。在一個多工的系統中,會有很多工輪流執行,正在執行的那個任務,稱為當前任務(Current Task)。因為LDTR只有一個,所以,它只用於指向當前任務的LDT。每當發生任務切換時,LDTR的內容被更新,以指向新任務的LDT。

當向段暫存器載入段選擇子的時候,段選擇子的TI(Bit2)位是表指示器(Table Indicator)。
TI=0:處理器從GDT中載入描述符
TI=1:處理器從LDT中載入描述符

因為描述符索引佔用了13個位元,所以每個LDT最多能容納的描述符個數是8192(2的13次方),也就是說每個LDT只能定義8192個段;又因為每個段描述符佔用8個位元組,所以LDT的最大長度是64KB(2的16次方)。

2.TSS(任務狀態段)

在一個多工環境中,當任務發生切換時,必須儲存現場(比如通用暫存器,段暫存器,棧指標等)。為了儲存被切換任務的狀態,並且在下次執行它時恢復現場,每個任務都應當有一片記憶體區域,專門用於儲存現場資訊,這就是任務狀態段(Task State Segment)。
TSS的格式如下圖所示:

TSS的最小尺寸是104(0x68=104)位元組。
和LDT類似,處理器用任務暫存器TR(Task Register)指向當前任務的TSS。和GDTR、LDTR一樣,TR在處理器中也只有一個。當任務發生切換的時候,TR的內容會跟著指向新任務的TSS。這個過程是這樣的:首先,處理器將要掛起的任務的現場資訊儲存到TR指向的TSS;然後,使TR指向新任務的TSS,並從這個TSS中恢復現場。

下圖是我根據原書圖14-1繪製而成的,對我們理解GDTR、TR、LDTR和多工的關係很有幫助。

3.全域性空間和區域性空間

每個任務實際上包括兩個部分:全部部分和私有部分。全域性部分是所有任務共有的,含有作業系統的資料、庫程式、系統呼叫等;私有部分是每個任務自己的資料和程式碼,與任務要實現的功能有關,彼此並不相同。

從記憶體的角度來看,所謂的全域性部分和私有部分,其實是地址空間的劃分,即全域性地址空間(簡稱全域性空間)和區域性地址空間(簡稱區域性空間)。
對地址空間的訪問離不開分段機制,全域性地址空間用GDT來指定,區域性地址空間由每個任務私有的LDT來指定。

從程式設計師的角度看,任務的全域性空間包含了作業系統的段,是由別人編寫的,但是他可以呼叫這些段的程式碼,或者獲取這些段中的資料;任務的區域性空間的內容是由程式設計師自己編寫的。通常,任務在自己的區域性空間執行,當它需要作業系統提供的服務時,轉入全域性空間執行。

4.特權級保護概述

在分段機制的基礎上,處理器引入了特權級的概念,並由韌體負責實施特權級保護。
特權級(Privilege Level),是存在於描述符及其選擇子中的一個數值。當這些描述符或者選擇子所指向的物件要進行某種操作,或者被別的物件訪問時,該數值用於控制它們所能進行的操作,或者限制它們的可訪問性。

Intel處理器可以識別4個特權級別,分別是0~3,數值越小特權級越高。
如下圖所示(圖片來自維基百科):這是Intel處理器所提供的4級環狀結構。

通過,作業系統是為所有程式服務的,可靠性最高,而且必須對軟硬體有完全的控制權,所以它的主體部分必須擁有特權級0,處於整個環形結構的中心。因此,作業系統的主體部分通常被稱作核心(Kernel、Core)。

特權級1和2通常賦予那些可靠性不如核心的系統服務程式,比較典型的就是裝置驅動程式。不過,在很多流行的作業系統中,驅動程式也是0特權級。

應用程式的可靠性被視為是最低的,而且通常不需要直接訪問硬體和一些敏感的系統資源,通過呼叫裝置驅動程式和作業系統例程就能完成絕大多數工作,所以賦予它們最低的特權級別3.

5.CPL,DPL,RPL

想搞清楚段級保護,必須要弄懂這三個概念。

5.1.CPL

CPL:當前特權級(Current Privilege Level),存在於CS暫存器的低兩位。
當處理器正在一個程式碼段中取指令和執行時,這個程式碼段所在的特權級叫做當前特權級。正在執行的這個程式碼段,其選擇子位於段暫存器CS中,CS中的低兩位就是當前特權級的數值。
一般來說,作業系統的程式碼正在執行時,CPL就等於0;

相反,普通的應用程式則工作在特權級3上。應用程式的載入和執行,是由作業系統主導的,作業系統一定會將其放在特權級3上(具體的做法,我們會慢慢學到)。當應用程式開始執行時,CPL自然會是3.

需要注意的是,不能僵化地看待任務和任務的特權級別。當任務在自己的區域性空間執行時,CPL等於3;當它通過呼叫系統服務,進入作業系統核心,在全域性空間執行時,CPL就變成了0.(具體過程我們會在後面講解。)

5.2.DPL

DPL:描述符特權級(Descriptor Privilege Level),存在於段描述符中的DPL欄位。
DPL是每個描述符都有的欄位,故又稱描述符特權級。描述符總是指向它所描述的目標物件,代表著該物件。因此,DPL實際上是目標物件的特權級。
如果你忘了描述符的格式,可以看看下圖。

5.3.RPL

RPL:請求特權級(Requested Privilege Level),存在於段選擇子的低兩位。
要想將控制從一個程式碼段轉移到另一個程式碼段,通常是使用jmp或者call指令,並在指令中提供目的碼段的選擇子和偏移;為了訪問記憶體中的資料,也必須先將段選擇子載入到段暫存器,比如DS、ES、FS、GS中。不管是實施控制轉移還是訪問資料段,這都可以看成是一個請求,請求者提供一個段選擇子,請求訪問指定的段。從這個意義上來說,RPL也就是指請求者的特權級別。

也許你會疑惑:有CPL和DPL進行判斷不就可以了嗎?為什麼還需要一個RPL呢?
因為當低特權級的應用程式使用call far指令通過呼叫門將控制轉移到較高特權級的非一致程式碼段(例如作業系統提供的例程,假設此程式碼段的DPL=0)時,會改變當前的特權級,而在目的碼段的特權級上執行,對於本例來說CPL的數值就會變成作業系統例程段的DPL的數值,即0。如果沒有RPL,那麼此時CPL許可權是最高的,也就可以去訪問任何資料,這就不安全了。所以引入RPL,讓它代表訪問許可權,因此在檢查CPL的同時,也會檢查RPL.一般來說如果RPL的數值比CPL大(許可權比CPL的低),那麼RPL會起決定性作用。

6.IO特權級

在處理器的標誌暫存器EFLAGS中,位12、13是IOPL位,也就是輸入/輸出特權級(I/O Privilege Level),它代表著當前任務的I/O特權級別。

如果CPL在數值上小於等於IOPL,那麼所有的I/O操作都是允許的,針對任何硬體埠的訪問都可以通過。

相反,如果CPL的數值大於IOPL,也並不意味著所有的硬體埠都對當前任務關上了大門。事實上,處理器的意思是總體上不允許,但個別埠除外。至於是哪些個別埠,要找到當前任務的TSS,並檢索I/O許可位串(具體細節我們以後會說)。

只有當CPL=0時,程式才可以使用POPFIRET指令修改這個欄位。