日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

一个操作系统的设计与实现——第12章 任务(三):3特权级任务

發布時間:2023/11/12 C# 91 coder
生活随笔 收集整理的這篇文章主要介紹了 一个操作系统的设计与实现——第12章 任务(三):3特权级任务 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

特權級是保護模式的核心概念之一,但我們的操作系統一直沒有引入這個概念。這是因為,特權級只有在3特權級任務存在時才有意義。本章將要實現的是3特權級任務的加載與任務切換。

12.1 特權級

12.1.1 特權級的功能

特權級(Privilege Level),是保護模式中用于限制任務權限的機制。特權級有4級,分別是0~3特權級。0特權級權限最大,供操作系統使用;3特權級權限最小,供普通任務使用。中間的兩級在我們的操作系統中不使用。

特權級起到的作用如下:

  1. 在不使用特殊機制的前提下,禁止代碼段寄存器切換特權級。也就是說,0特權級任務只能執行0特權級的代碼,不能執行3特權級的代碼;反之,3特權級任務只能執行3特權級的代碼,不能執行0特權級的代碼。這樣,操作系統內部的代碼就能得到保護,3特權級任務不能隨意使用
  2. 在任何情況下,使用數據段寄存器只能訪問平級或更低級的數據。也就是說,0特權級任務能夠訪問0特權級的數據,也能訪問3特權級的數據;反之,3特權級任務只能訪問3特權級的數據,不能訪問0特權級的數據。這樣,操作系統內部的數據就能得到保護,3特權級任務不能隨意訪問
  3. EFLAGS的第12~13位是IOPL(IO特權級)位,初值為0。只有特權級大于等于這兩位,即數值上小于等于這兩位的代碼段才能執行inout指令。在我們的操作系統中不修改這兩位,因此,只有0特權級任務才有權限執行inout指令。這樣,IO端口就能得到保護,3特權級任務不能隨意訪問
  4. 有少量非常關鍵的指令屬于特權指令,這些指令只有0特權級任務有權限執行。在我們的操作系統中,特權指令包括以下幾種:
    • hlt指令。顯然,低特權級任務不能隨意將CPU掛起
    • sticli指令,以及其他試圖修改IF位的指令,如popf。低特權級任務不能關中斷,否則搶占式任務切換就失效了
    • 試圖修改IOPL的指令,如popf。低特權級任務不能修改IO特權級,否則對IO端口的保護機制就失效了
    • 任何試圖讀取或修改控制寄存器、GDTR、LDTR(局部描述符表,在我們的操作系統中未使用)、IDTR、TR(見下文)的指令。如mov cr0, eaxlgdt等。這些指令對CPU有重大影響,例如,修改CR0能開關保護模式和分頁模式,這顯然不能供低特權級任務隨意使用
    • invlpg指令。低特權級任務不能干預TLB

12.1.2 DPL,RPL與CPL

特權級由三部分組成:描述符特權級(Descriptor Privilege Level,DPL),請求特權級(Requested Privilege Level,RPL)和當前特權級(Current Privilege Level,CPL)。DPL位于描述符中,如段描述符,中斷門等;RPL是段選擇子的低2位;CPL恒等于CS的RPL

CPL決定了當前任務的執行權限。上文中的"只有0特權級任務有權限執行"、"3特權級任務只能訪問3特權級的數據"等描述中的特權級,指的就是CPL。由于CPL恒等于CS的RPL,所以切換CS的過程,就是切換CPL的過程。

DPL決定了一個描述符的最低訪問權限。向段寄存器加載段選擇子時,CPU要求:在數值上,CPL <= DPL && RPL <= DPL。即,只有特權級平級,或更高級的指令才有權限訪問此描述符。

RPL看上去是個沒什么用的概念。如果沒有RPL,CPL可以恒等于代碼段的DPL;向段寄存器加載段選擇子時,只需要CPL <= DPL即可。然而,RPL解決的是一個比較邊緣的問題。設想:操作系統給3特權級任務提供了一個讀硬盤函數,并要求3特權級任務提供數據段選擇子以存放結果。此時,用戶可以想辦法猜到0特權級數據段選擇子,并將其提供給操作系統,這樣一來,3特權級任務就能讀取0特權級數據了。雖然操作系統可以通過軟件手段檢測任務提供的段選擇子是否可行,但這樣做很麻煩且效率不高。給段選擇子附加RPL后,即使3特權級任務故意把RPL寫成0,操作系統也能在拿到段選擇子后,強制將RPL改成3,再進行后續操作,從而將特權級檢查交給CPU進行,即提高了效率,又比較方便。

正因為如此,CPL才由CS的RPL決定,而不是由代碼段描述符的DPL決定。因為DPL只是一個最低標準,RPL才能描述真正的特權級。

12.1.3 特權級的提升

操作系統存在的意義是為3特權級任務提供服務,而不是死守自己的0特權級代碼,不讓任何人使用。上文提到,數據段在任何情況下都不能被低特權級任務訪問,但代碼段不同,操作系統可以將一部分函數開放給3特權級任務使用。

然而,操作系統的函數畢竟是0特權級的,3特權級任務想要使用這些函數,就需要有一套升降級機制,在調用0特權級函數之前先升級,調用完成后再降級。

對于這個需求,CPU提供的標準機制是調用門(Call gate),但這種機制要求為每個函數分別安裝一個調用門,這是非常麻煩且效率低下的,所以,在我們的操作系統(以及幾乎所有的現代操作系統)中都不使用這個機制。

事實上,中斷門也有提升特權級的能力。因此,中斷門可以用于向3特權級任務提供0特權級函數。具體步驟如下:

  1. 發起一個中斷
  2. 檢查CPL <= DPL(中斷沒有RPL)。如果通過,則允許任務進入中斷門。只有由指令發起的中斷會進行這一步,外中斷和CPU自己發起的中斷無視中斷門的DPL
  3. 檢查CPL >= 中斷門中CS的RPL,如果通過,則允許CS升級并調用中斷門中的函數

綜上,想要使用中斷門,就必須滿足兩個條件:

  1. 有權限進入中斷門。這由中斷門的DPL決定。這樣做的目的是對3特權級任務能夠使用的中斷門進行限制
  2. 進入中斷門后,必須發生升級或平級,不允許降級

12.1.4 0特權級棧、TSS與TSS描述符

在特權級切換時,CPU還有一個特殊要求:SS的RPL必須時刻等于CPL。這意味著,每個3特權級任務必須有兩個棧,分別供0特權級與3特權級使用。當特權級發生切換時,棧也要跟著切換。那么,這兩個棧存放在哪呢?

CPU規定:0特權級棧需要放置在任務狀態段(Task State Segment,TSS)中。TSS是一個至少為104字節(見下文)的表,結構如下:

TSS需要以TSS描述符的形式安裝在GDT中。TSS描述符是系統段的一種,結構如下:

TSS描述符中的B位,即忙(Busy)位,由CPU在加載TSS時自動置1,構造TSS描述符時應將其置0。其他位的含義同段描述符。

與GDT、IDT類似,CPU也為TSS提供了一個專用寄存器。不過,這個寄存器不叫TSSR,而是叫任務寄存器(Task Register,TR)。在TSS描述符安裝到GDT中以后,需要使用ltr TSS描述符的選擇子指令將TSS加載到TR。TSS描述符的選擇子可以存放在16位寄存器或內存中。

TSS(以及任務門,Task gate)是CPU提供的用于任務切換的標準機制,然而還是老問題:TSS使用起來非常麻煩,需要給每個任務都安裝一個,且效率很低。所以,在我們的操作系統(以及幾乎所有的現代操作系統)中都不使用這個機制。但TSS還有另一個功能,那就是獲取任務的0特權級棧。具體來說,當中斷發生時,引入特權級概念后的過程如下:

  1. 檢查CPL是否有權限進入中斷門
  2. 檢查中斷門中的CS是否能使CPL升級或平級
  3. 如果中斷門中的CS與CPL平級,跳過此步驟;否則,暫存SS和ESP,然后將SS和ESP分別切換為TSS中的SS0和ESP0,再將暫存的SS通過高位補0的方式填充至32位后壓棧,接著將暫存的ESP壓棧
  4. 將EFLAGS壓棧,然后將EFLAGS的IF位清零
  5. 將CS通過高位補0的方式填充至32位后壓棧
  6. 將EIP壓棧
  7. 跳轉至中斷門中的中斷處理函數

也就是說,雖然不使用TSS進行任務切換,但仍然需要一個TSS,其存在的唯一目的就是提供0特權級棧。

上文提到,TSS"至少為104字節"。這是因為TSS還能提供一個被稱為IO位圖的功能,這個位圖延長在TSS后面,由TSS中的IO位圖基址控制。IO位圖用于越過IOPL,給特定的一些IO端口開白名單。在我們的操作系統中不使用IO位圖,但也不能將其置0。CPU要求:如果IO位圖基址的值大于等于TSS描述符中的TSS限長,則表示IO位圖不存在。在我們的操作系統中,可將其置103(或0xff等更大的值)。

12.1.5 特權級的降低

3特權級任務能夠通過中斷門提升特權級,但這畢竟是暫時的。在中斷處理函數調用完成后,就需要回到3特權級。

特權級的降低由iret指令實現。引入特權級概念后,該指令的執行過程如下:

  1. 從棧中依次彈出EIP、CS、EFLAGS。如果彈出的CS的RPL為0,iret指令就此完成;否則,繼續執行以下步驟
  2. 依次檢查DS、ES、FS、GS的RPL是否為3。如果不是,則將其修改為0。這一步的目的是:避免因中斷返回使0特權級的段選擇子泄漏到3特權級。GDT的第一個描述符必須為空的目的就在于此
  3. 繼續從棧中彈出ESP和SS,將棧恢復到3特權級棧。因此,TSS中不需要存放3特權級棧,3特權級棧的SS和ESP位于0特權級棧中

12.2 3特權級任務的實現原理

12.2.1 3特權級代碼段與數據段

GDT中需要安裝一個3特權級代碼段描述符,和一個3特權級數據段描述符,以供3特權級任務使用。這兩個描述符除了DPL為3外,其他屬性與0特權級描述符相同。

12.2.2 TSS

GDT中需要安裝一個TSS描述符,并使用ltr指令加載這個TSS。

12.2.3 3特權級任務的切換

3特權級任務的切換也基于時鐘中斷。但上一章中的"6個段寄存器不會發生改變"這一結論現在已經不成立了。所以,在時鐘中斷處理函數中,不僅需要將8個通用寄存器壓棧,還需要將除了CS和SS以外的4個段寄存器壓棧。

此外,由于每個任務都有一個ESP0,所以在任務切換時,需要修改TSS中的ESP0。SS0對于每個任務來說都是一樣的,所以無需修改。

12.2.4 3特權級任務的創建

與0特權級任務類似,3特權級任務的創建也基于偽造棧技術。

現在,由于4個段寄存器也被壓棧,對于0特權級任務來說,需要在棧上偽造15個寄存器的值;對于3特權級任務來說,需要在棧上偽造17個寄存器的值。

3特權級任務不僅需要0特權級棧,還需要3特權級棧。在我們的操作系統中,3特權級棧為一頁,其虛擬地址固定為0xc0000000向下的0x1000字節。

12.2.5 3特權級任務的加載與重定位

3特權級任務往往不是操作系統的一部分,而是由用戶提供的,存放在硬盤上的一個程序。從硬盤上加載程序可由硬盤驅動完成,解析ELF也不是難事,剩下的問題是:這個程序該如何重定位呢?

平坦模型失去了重定位能力,重定位由分頁模式以一種完全不同的方式實現。具體來說,編譯器可以為程序提供一套虛擬地址,只要虛擬地址落在任務地址空間內即可。操作系統在加載任務時,不主動分配虛擬地址,而是使用ELF文件提供的虛擬地址,并為這些虛擬地址分配物理地址,并安裝PDE、PTE,然后,將ELF文件中的程序段展開到這些虛擬地址中。

12.3 3特權級任務的實現

12.3.1 添加3特權級描述符、TSS描述符

請看本章代碼12/Mbr.s

第143~145行,定義了三個新的段描述符。分別是3特權級代碼段描述符,段選擇子是(3 << 3) | 0x3;3特權級數據段描述符,段選擇子是(4 << 3) | 0x3;TSS描述符,段選擇子是5 << 3

TSS定義在內核中,在MBR中不知道其地址,所以,TSS描述符目前僅用于占位。

12.3.2 修改時鐘中斷處理函數

請看本章代碼12/Int.s

第3行,聲明了外部鏈接的printStr函數。

第7行,聲明了外部鏈接的TSS。TSS定義在本章代碼12/Task.hpp中。

第108~111行, 將四個數據段寄存器壓棧。

第137~138行,將TSS中的ESP0修改為新任務的ESP0。

第141~144行,彈出新任務的四個數據段寄存器。

12.3.3 系統調用

上文提到,可以使用中斷將0特權級函數提供給3特權級任務使用。這個方案看似需要很多中斷號,但實際上有更好的設計:構造一個函數表,其中存放的是操作系統為3特權級任務提供的函數。然后,只使用一個中斷,在中斷處理函數中調用函數表中的函數,具體調用哪個函數由調用者通過一個索引值指定。上述操作被稱為系統調用,調用者提供的索引值被稱為系統調用號。

在我們的操作系統中,調用者應使用EAX存放系統調用號;EBX、ECX、EDX用于存放參數(如果有)。在系統調用的中斷處理函數中,不管有幾個參數,都將EBX、ECX、EDX壓棧,然后使用EAX找到一個函數并調用。

請看本章代碼12/Int.s

intSyscall是系統調用的中斷處理函數。

第150~152行,不管實際需要幾個參數,都將EBX、ECX、EDX壓棧。

第153行,使用EAX中存放的系統調用號調用[syscallList + eax * 4]這個函數。

第154行,將棧恢復。

第156行,使用iret指令從中斷返回。

第210行,在intList的最后添加intSyscall函數。現在的IDT擴充至49個中斷門,系統調用的中斷號是0x30

第212~213行,定義系統調用表。目前只支持一個系統調用:printStr函數,其系統調用號為0。

接下來,請看本章代碼12/Int.hpp

第7行,將IDT的大小修改為49。

第25行,將intSyscall函數安裝到IDT中。注意:由于系統調用是給3特權級任務使用的,所以中斷門的DPL必須為3,這樣3特權級任務才有權限使用這個中斷門。

12.3.4 內存管理系統的修改

請看本章代碼12/Memory.h

第9行,聲明了installTaskPage函數。

接下來,請看本章代碼12/Memory.hpp

installTaskPage函數是本章新增的函數,其用于在指定的虛擬地址處分配物理地址,并將虛擬地址與物理地址建立聯系。所以,這個函數相當于__allocatePage函數的簡化版。

第85行,從TCB中取得當前任務的虛擬地址位圖,接下來需要手動設置這個位圖。

第87行,判斷虛擬地址是否超出了位圖范圍。進行這個判斷的原因是,這個函數不僅用于安裝ELF文件中的地址,這些地址都很接近0;還用于安裝3特權級棧,地址是0xc0000000 - 0x1000,這個地址遠遠超過了一頁位圖能表示的128M內存,所以,此時無需設置位圖。

第89~92行,根據虛擬地址設定位圖中已使用的位。

第95~100行,為虛擬地址的每一頁安裝物理地址。這段代碼的實現原理和第66~71行一致。

12.3.5 0特權級任務加載器的修改

請看本章代碼12/Task.hpp

由于任務切換時添加了4個段寄存器的壓棧,所以偽造棧的過程也要隨之修改。

第96行,將原先的11 * 4修改為15 * 4

第108~114行,重新調整棧上偽造的數據。

12.3.6 安裝并加載TSS

請看本章代碼12/Task.h

第16行,聲明了外部鏈接的TSS。

接下來,請看本章代碼12/Task.hpp

第10行,定義了TSS,但暫時沒有初始化。

__makeTSSDescriptor函數用于構造TSS描述符。

讀者要格外小心這個函數的實現,例如"TSS地址的最高8位",從TSS描述符的結構圖上看,其位于第56~63位,但如果實現為(tssBase & 0xff000000) << 56,那就完全錯了。tssBase & 0xff000000得到的這個數字相當于已經左移了24位,所以,只需要再左移32位即可。

__installTSS函數用于安裝TSS。

第23~24行,初始化TSS中的SS0與IO位圖基址。內核用不到ESP0,所以無需初始化。

第28行,使用sgdt指令獲取GDTR。

第30行,在GDT中安裝TSS描述符。

第32行,重新加載GDT。此時,TSS的段選擇子5 << 3可用。

第33行,使用ltr指令加載TSS。

第51行,在taskInit函數中添加對__installTSS函數的調用。

12.3.7 3特權級任務的加載

請看本章代碼12/Task.h

第23行,聲明了loadTaskPL3函數。

接下來,請看本章代碼12/Task.hpp

loadTaskPL3函數是本章新增的函數,其用于加載3特權級任務。

不同于loadTaskPL0函數,loadTaskPL3函數的參數是硬盤的起始扇區號與扇區數。所以,此函數可以一步到位的從硬盤上加載3特權級任務。

第124~136行,與loadTaskPL0函數的開頭部分一致。用于分配新任務的TCB,CR3,以及虛擬地址位圖,并設置好新的CR3。

第138行,將扇區數轉換為頁數。這里使用了以下公式:

\[\lceil \frac{a}{b} \rceil =\lfloor \frac{a\,\,+\,\,b\,\,-\,\,1}{b} \rfloor \]

第139行,分配ELF緩沖區。

第141行,調用硬盤驅動中的讀硬盤函數,將任務從硬盤讀取到緩沖區。

第143~145行,讀取ELF文件頭中的3個信息:程序頭表地址,程序頭表中每個表項的大小,以及程序頭表中表項的數量。

第147~154行,保存當前的CR3,并將其暫時切換到新任務的CR3上。這里的內聯匯編使用了第6章討論的獨占約束"=&r"。這一步的目的是:任務加載到的虛擬地址屬于任務自己的地址空間,所以,應在任務的CR3中分配內存。

第156行,遍歷程序頭表中的每個表項。

第158行,判斷當前表項的類型,只關注類型為1的表項。

第160行,從表項中讀取源地址。

第161行,從表項中讀取目的地址。

第163行,將ELF要求的內存大小向上取整到頁數。這里使用了上文中的公式。

第165行,使用installTaskPage函數在ELF要求的加載地址處安裝頁。

第167~168行,加載程序段并構造BSS段。

第172行,從ELF文件頭中讀取任務的入口點。

至此,ELF已經加載完畢。

第174行,回收ELF緩沖區。這里雖然使用的是任務的CR3,但內核地址空間是共享的,只要有權限,任何CR3都能分配和回收內核頁。

第176~196行,偽造3特權級任務的0特權級棧,一共需要偽造17個寄存器的值。

第198行,為3特權級棧安裝物理頁。

第200~204行,將CR3換回。

第206~208行,初始化新任務的虛擬地址位圖,然后將其添加到任務隊列中。這兩行代碼與loadTaskPL0函數中的一致。

12.4 測試

請看本章代碼12/Test.c

這個任務現在運行在3特權級下,所以其沒有權限使用操作系統中的所有函數,唯一能用的是0號系統調用。使用系統調用還有一個好處:由于系統調用是中斷,而中斷過程中不會發生任務切換,所以系統調用是自帶鎖的。

第5~9行,在循環中不斷發起0號系統調用,打印Task字符串。

由于我們的操作系統目前仍然不支持任務回收,所以任務不能退出。

接下來,請看本章代碼12/Makefile

第6行,編譯Test.c

第8行,鏈接Test.o

對于ld命令來說,如果不設定-Ttext-segment參數,則任務的默認加載地址為0x8048000。我們不需要也不能使用這么大的加載地址,因為這個數字甚至超過了128M。因此,鏈接Test.o時需要加上-Ttext-segment 0參數。

第11行,將Test寫入虛擬硬盤。

接下來,請看本章代碼12/Kernel.c

第15~16行,將測試任務加載兩次。

第22~26行,在循環中不斷發起0號系統調用,打印Kernel字符串。這樣做有兩個目的:

  1. 利用系統調用自帶鎖這一性質
  2. 觀察中斷門的平級調用

12.5 調試

本章的任務運行在3特權級下,因此,如果想要手動進行任務切換以調試程序,就需要將__installIDT函數中的0x8e00臨時修改為0xee00,使得3特權級任務也能進入0x20中斷門。

在bochs調試器中,TSS可以通過info tss命令查看。

總結

以上是生活随笔為你收集整理的一个操作系统的设计与实现——第12章 任务(三):3特权级任务的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。