改變
- 32位引入了保護模式,保護模式實現的核心是描述符
- 它可以存放在內存的任何位置,至於基地址和界限則存放在GDTR中
- 所以他提供了很多種保護
- 當我們跳轉的時候,他會檢查描述符是否存在,你是否有權限跳轉到該段
- 當你運行指令的時候,他會判斷取指令的地址是否越過段界限
- 【這裏比較奇怪的是堆棧段的段界限計算方式】
- 當計算機啓動的時候,默認是在16位實模式下,當在32位模式下運行的時候,段描述符是不可缺少的
- 所以我們需要在16位模式下創建引導扇區的代碼段,堆棧段,數據段三個描述符,並且放置到相應位置
- 然後將描述符的選擇子回寫到主引導扇區的頭部段裏面【即創建段描述符和段重定位】
- 在切換到32位模式之後,繼續運行主引導扇區的代碼,此時可以將操作系統的內核代碼加載到內存當中
- 遵循同樣的套路,他讀取內核代碼頭部的信息,知曉內核代碼段的長度,數據段的長度以及彙編地址偏移,加上放置內核的物理基地址,根據這些信息創建段描述符,並且放置到GDT的序列中,然後將選擇子回寫到頭部最後執行跳轉
- 至於內核加載程序的流程也是一樣的
加載流程
- 從硬盤加載頭部信息
- 讀取頭部信息組裝段描述
- 將描述符放置到GDT中
- 獲得段選擇子回寫到頭部
- 然後進行跳轉
過程重定位
- 操作系統本身會提供很多封裝好的函數供用戶程序使用
- 可是調用call和Jump都需要知道跳轉的段地址和函數偏移
- 但操作系統一方面不可能把這些信息透露給你,而是操作系統的段和偏移是隨時改變的
- 在16位時代解決這個問題,使用軟中斷
- 在32位時代,使用過程重定位,用戶程序將自己使用到的操作系統的函數名稱全部放置在頭部salt表中
- 內核在自己的內部有一張函數名稱和地址的對照表salt表
- 在加載用戶程序的時候,就掃描用戶程序的salt表,如果和內核的函數名匹配,就將用戶程序對應位置的函數名稱改爲內核函數的地址
任務和特權級
- 在描述符中有兩位是用來表示特權級的,即00,01,10,11一共四種特權級,0123,操作系統在加載用戶程序的時候會自動爲其分配特權級,一般爲3,數字越小則特權級越高
- 段選擇子是16位的,其中13位用來表示真正的選擇子,剩下1位表示LDT或者GDT【1表示爲LDT】,還剩下兩位是用來表示RPL請求特權級
- 在上一節中,我們訪問系統的API,都是直接提供該函數的段選擇子和該函數的偏移。但是在含有特權級之後,用戶程序3級不能直接轉移到0級程序。
- CPL是指當前代碼段的特權級
- 需要通過門或者依從。因此將系統API的段選擇子和偏移製作成門描述符,只有當CPL》門描述符的特權級《段旋轉子的特權級時才允許訪問
- 當調用門的時候,偏移其實是沒有用的【已經寫在描述符中了】,通過Jump跳轉步會改變當前CPL【如果是非依從】,通過call會改變當前CPL【如果是非依從】,爲了防止棧空間溢出,要提供對應等級的棧。
- 比如3級需要額外提供0,1,2級的棧。
- 至於依從,則是在
- 對於端口的訪問也並不全是開放的,由FLAGS的IOPL進行限制,當CPL高於IOPL的時候,全都可以訪問。
- 但是小於IOPL的時候,需要讀取當前TSS的IO映射區許可位,測試該許可位就可以知道能否讀取該端口
- 某些指令是特權指令,只有0級纔可以訪問,但是某些除了0級以外,其他特權級也可能,至於是否可以訪問,要根據其IOPL來決定。
流程
-
因此對於內核的流程來說,
-
首先製造門並且將門的描述符放置在GDT中同時回填到salt中
-
創建任務的TCB,用來記錄各項信息,並將其放置到TCB鏈表中
-
給該任務的LDT分配內存並記錄到TCB中
-
然後讀取用戶程序頭部的信息並根據這些信息創建對應的描述符,放置到LDT中【進行段重定位】
-
然後進行過程重定位
-
再創建該任務所需要的額外的棧並放置到LDT中
-
最後該任務的LDT本身也是一個內存段,創建他對應的描述符到GDT中,將選擇子登記到TCB中
-
最後創建用戶的TSS,放置到GDT中,登記到TCB中
總結
- 對於內核程序來說,更改的其實沒有。引導程序沒有變化
- 當從16位轉換到32位的時候,自動將CPL更換爲0級,然後Jump的時候,跳轉到內核0級的代碼段和數據段
- TCB,TSS,LDT,門
一些疑惑
- 目前的一些疑惑
- 當從16位模式轉換到32位模式的時候,處理器都進行了什麼處理
- TSS寄存器爲什麼沒有保存寄存器的狀態
- 非依從是什麼意思
- EFLAGS 的初始值是什麼
轉移控制權到任務
- 因爲當前的特權級都是0,cpu本身是禁止代碼段從高到低轉換的
- 因此這裏使用retf,假裝例程調用完畢的返回
- 因爲正常調用操作系統的例程將參數入棧,然後將當前CS和IP入棧。【如果涉及到特權級轉換】因爲要切換棧,所以臨時保存當前SS和ESP,然後從TSS中讀取對應特權級的棧選擇子和棧指針放置到SS和ESP,然後將以前的SS和ESP入棧,再壓入參數,CS和IP
- 所以使用retf的時候,需要依次壓入CS,IP,然後調用reftf即可。
- 【奇怪的是,如果對於需要切換棧,還需要從中讀取SS和ESP進行重置,這又是依靠誰來製作的呢?】
變與不變
- 既然可以從16位到32位,那麼當然也可以從32位到64位
- 32位相比於16位來說,就是把原來可以直接得到的東西,放進了一個屋子裏面,然後加上密碼鎖,你只有符合他的權限等級,屋子纔會讓你進。
- 我們以前訪問東西,是直接用段地址+偏移,但是現在這些數據全部被封裝在描述符中,我們通過選擇子來獲取描述符
- 所以描述符就是一層鎖,你想獲取或者使用這個描述符,就必須要高於或者等於它的權限,然後這個描述符纔可以到你手裏,放置在對應的高速緩存中
- 因此對於TSS,LDT,門 都加了一把鎖,存放到GDT中,因爲這些數據都屬於操作系統用來管理程序使用的數據,其他用戶程序不可以無緣無故的訪問它。
- 比如TSS,如果我通過研究得知了操作系統的GDT位置,然後又知曉了TSS的存放位置,那我就可以通過TSS知道對應程序的棧信息等等。
安全步驟執行流程
- 操作系統可以將自己的任務管理程序劃分爲一個特權級爲0的任務
- 首先TR存放TSS的選擇子,LDTR存放當前LDT的選擇子,GDTR存放的是當前GDT的基地址和界限
- 當用戶程序運行的時候,例如我們將
mov DS ax
,如果該ax的倒數第三位是1 表明是LDT的數據, 處理器就從 LDTR的高速緩存中取來 LDT的描述符,其中含有基址和界限,然後加上其選擇子乘以8獲得其在該任務內存空間LDT中的地址,獲得其描述符 - 另外還有,DS,CS所能放置的選擇子其本身也是有對應限制的。比如CS只能讀不能寫,DS可以讀可以寫,如果將可讀可寫的描述符放置到CS中就會引發中斷。
- 這個時候再檢驗特權級,即CRL是否高於ax中的RPL,如果高於,就放置到DS數據段寄存器當中。【當我們將數據放置到DS,ES,CS,SS的時候總會進行檢驗,一旦檢驗通過,我們就可以用這個DS隨意取數據了】
- 當任務發生切換的時候,我們需要保證TR總是指向當前TSS的選擇子,然後LDTR總是指向當前LDT選擇子,而且TSS中存放的有LDT的選擇子。而且要把上一個任務的所有狀態保存到對應TSS中,因爲還沒發生切換,所以根據TR可以得到TSS的位置,然後將新的一些數據寫入TSS進行保存,再從任務門中獲取新的TSS的選擇子,並恢復到各個寄存器中,然後將TR指向新的選擇子,並從TSS中獲得LDT的選擇子,將LDT指向它。
中斷向量表的變化
- 在16位模式下的時候,從0號地址開始存放的中斷向量表,在32位模式下就不能使用了,因爲這些中斷向量表放置的是左移四位然後加上偏移地址的傳統計算方式數據
- 在32位下還要繼續使用中斷,就使用中斷門,當發生中斷的時候,就根據其中斷號,在GDT中獲取其對應的描述符,然後轉移到對應地址執行代碼。就是門的使用方式。
- 除此之外還有陷阱門和任務門,任務門是當發生中斷後,CPU察覺到這是一個任務門的描述符後,從中獲取其要切換的TSS對應的選擇子,然後從GDT中取得TSS描述符後進行任務切換。