任务和特权级保护(二)——《x86汇编语言:从实模式到保护模式》读书笔记32
之前做了那么多鋪墊,我們終于可以看看第14章的代碼了。
對于引導代碼和用戶程序,依然采用第13章的;對于內核程序(c14_core.asm),編譯的時候有幾行報錯了,只要加上dword即可解決。
1. 為什么要用調用門
在第13章,為了能使用內核提供的例程,用戶程序是用call far指令直接轉移到內核例程(非一致代碼段)。因為CPL=目標代碼段描述符的DPL=RPL=0,符合下面表格的條件,所以轉移是沒有問題的。
但是在本章,用戶程序工作在3特權級,而非0特權級,所以是無法直接轉移的。不過也不用悲觀,我們還是有辦法的,可以通過調用門來轉移。
2. 什么是調用門
關于調用門的知識,可以參考我的博文:調用門詳解
調用門的格式如下圖:
3. 調用門的安裝
811 ;以下開始安裝為整個系統服務的調用門。特權級之間的控制轉移必須使用門 812 mov edi,salt ;C-SALT表的起始位置 813 mov ecx,salt_items ;C-SALT表的條目數量 814 .b3: 815 push ecx 816 mov eax,[edi+256] ;該條目入口點的32位偏移地址 817 mov bx,[edi+260] ;該條目入口點的段選擇子 818 mov cx,1_11_0_1100_000_00000B ;特權級3的調用門(3以上的特權級才 819 ;允許訪問),0個參數(因為用寄存器 820 ;傳遞參數,而沒有用棧) 821 call sys_routine_seg_sel:make_gate_descriptor 822 call sys_routine_seg_sel:set_up_gdt_descriptor 823 mov [edi+260],cx ;將返回的門描述符選擇子回填 824 add edi,salt_item_len ;指向下一個C-SALT條目 825 pop ecx 826 loop .b3先復習一下過程make_gate_descriptor.
331 make_gate_descriptor: ;構造門的描述符(調用門等) 332 ;輸入:EAX=門代碼在段內偏移地址 333 ; BX=門代碼所在段的選擇子 334 ; CX=段類型及屬性等(各屬 335 ; 性位都在原始位置) 336 ;返回:EDX:EAX=完整的描述符816~821:調用過程make_gate_descriptor構造調用門(請參考調用門的格式),P=1,DPL=3,參數個數=0;
822:調用過程set_up_gdt_descriptor把構造好的調用門安裝到GDT中,返回對應的選擇子(TI=0,RPL=0);
823:將返回的調用門選擇子回填,覆蓋原先的段選擇子。如下圖(下圖是內核符號表中一個表項的示意圖)所示:
4. 調用門的測試
828 ;對門進行測試 829 mov ebx,message_2 830 call far [salt_1+256] ;通過門顯示信息(偏移量將被忽略)表面上,這是一個間接絕對遠調用,通過指令中的內存地址,可以間接取得32位偏移量和16位的代碼段選擇子;但是,處理器在執行這條指令的時候,會用選擇子訪問GDT,結果發現是一個調用門,所以忽略32位的偏移量(上圖中的綠色部分)。
調用門安裝完成后,GDT的示意圖如下:
不僅間接絕對遠調用是這樣,直接絕對遠調用也是這樣,如果選擇子指向的是調用門,偏移量也會被忽略。例如
call 0x0040:0x00001234結合上圖,因為0x40處是調用門,所以偏移0x00001234被忽略。
5. 加載用戶程序與創建用戶任務
5.1 任務控制塊(Task Control Block,TCB)
5.1.1 TCB的格式
加載程序并創建一個任務,需要用到很多數據,比如程序大小、加載位置等等。內核應當為每一個任務創建一個內存區域,來記錄任務的信息和狀態,這個內存區域就稱為任務控制塊(Task Control Block,TCB)。
需要說明的是:TCB不是處理器的要求,而是我們為了自己方便而發明的。
關于TCB的結構,如原書圖14-12(P264)。為了讀者方便,我在這里把圖再繪制一遍。
請注意,這個格式是作者發明的,并不是說TCB就必須是這種格式。
5.1.2. TCB鏈表
為了能夠追蹤所有的任務,可以把每個TCB串起來,形成一個鏈表。
在代碼的核心數據段中,聲明了標號tcb_chain,初始化了一個雙字,值為0.
其實,這相當于一個指針,用來指向第一個任務的TCB。當它為0時,表示沒有任務。所有任務都按照被創建的先后順序鏈接在一起形成一個無頭單向非循環鏈表。
835 ;創建任務控制塊。這不是處理器的要求,而是我們自己為了方便而設立的 836 mov ecx,0x46 837 call sys_routine_seg_sel:allocate_memory 838 call append_to_tcb_link ;將任務控制塊追加到TCB鏈表以上三行用于分配TCB的空間(0x46字節),然后把這個TCB掛到鏈表上(尾插法)。
5.2. 加載用戶任務
840 push dword 50 ;用戶程序位于邏輯50扇區 841 push ecx ;壓入任務控制塊起始線性地址 842 843 call load_relocate_program以上三行用于加載和重定位用戶程序。
5.2.1. 使用棧傳遞參數
464 load_relocate_program: ;加載并重定位用戶程序 465 ;輸入: PUSH 邏輯扇區號 466 ; PUSH 任務控制塊基地址 467 ;輸出:無 468 pushad 469 470 push ds 471 push es 472 473 mov ebp,esp ;為訪問通過堆棧傳遞的參數做準備這是過程load_relocate_program開頭的幾行,執行完第473行后,棧的狀態如下圖所示:
這里主要是復習如何用棧傳遞參數。需要說明的是:
1. 用EBP寄存器來尋址的時候,默認使用段寄存器SS;
2. 在32位模式下,棧操作的默認操作數大小是雙字;
3. 處理器執行壓棧指令的時候,如果發現操作數是段寄存器,則將段寄存器的16位值擴展為32位(高16位全0),然后執行壓棧操作;出棧指令執行相反的操作,將32位的值截斷,僅保留低16位,并傳送到相應的段寄存器;
4. 由于load_relocate_program是通過32位近調用進入的(第843行),所以只壓入EIP的內容,沒有壓入CS;
以上三行執行完后,ES指向0-4GB數據段;ESI指向TCB的基地址。
5.2.2. 在TCB中填寫LDT的基地址和初始界限值
GDT一般用于存放全局空間的段描述符。對于任務私有的段描述符,也可以放在GDT中,但是最好放在自己私有的LDT中。
480 ;以下申請創建LDT所需要的內存 481 mov ecx,160 ;允許安裝20個LDT描述符 482 call sys_routine_seg_sel:allocate_memory 483 mov [es:esi+0x0c],ecx ;登記LDT基地址到TCB中 484 mov word [es:esi+0x0a],0xffff ;登記LDT初始的界限到TCB中5.2.3. 從硬盤加載用戶程序到內存
5.2.4. 在LDT中安裝描述符
用戶程序的頭部的格式和第13章完全相同。
5.2.5. 重定位符號表
這個過程和第13章的基本相同。注意,用戶符號表中的調用門選擇子,其RPL=3;
5.2.6 創建0、1、2特權級的棧
通過調用門的控制轉移,有可能會改變CPL。如果通過調用門把控制轉移到了更高特權級的非一致代碼段中,那么CPL就會被設置為目標代碼段的DPL值,并且會引起堆棧切換。
為此,必須為每個任務定義額外的棧。對于我們的用戶任務,需要為它創建特權級0、1、2的棧。而且,這些棧應當在LDT中有對應的段描述符。
這些棧是內核為用戶程序動態創建的,而且需要登記在TSS中,以便處理器固件能夠自動訪問到它們。不過目前我們還沒有創建TSS,所以,有必要先將這些棧的信息登記在TCB中暫時保存(如下圖)。
創建x(x=0,1,2)特權級的棧的步驟如下:
1. 申請內存,為棧分配空間;
2. 在LDT中創建棧段描述符(DPL=x);
3. 在TCB中登記棧的信息,包括棧的大小、基地址、選擇子(RPL=x)以及ESPx的初始值(=0);
我覺得棧的大小和基地址的登記是沒有必要的,因為TSS中不需要填寫這些字段。
囿于篇幅,本文就到這里。勞逸結合,休息一下…
【未完待續】
總結
以上是生活随笔為你收集整理的任务和特权级保护(二)——《x86汇编语言:从实模式到保护模式》读书笔记32的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python3.4安装pygame_py
- 下一篇: TSS详解 ——《x86汇编语言:从实模