全面剖析《自己動手寫作業系統》第三章—保護模式

來源:互聯網
上載者:User

《自己動手寫作業系統》讀後感                http://blog.csdn.net/zgh1988/article/details/7059936

全面剖析《自己動手寫作業系統》第一章   http://blog.csdn.net/zgh1988/article/details/7060032

全面剖析《自己動手寫作業系統》第二章 http://blog.csdn.net/zgh1988/article/details/7062065

 

 1 ; ========================================== 2 ; pmtest1.asm 3 ; 編譯方法:nasm pmtest1.asm -o pmtest1.com 4 ; ========================================== 5 6 %include"pm.inc"; 常量, 宏, 以及一些說明 7 8 org0100h 9jmpLABEL_BEGIN1011 [SECTION .gdt]12 ; GDT13 ;                                         段基址,      段界限     , 屬性14 LABEL_GDT:Descriptor       0,                0, 0     ; 空描述符15 LABEL_DESC_CODE32:Descriptor       0, SegCode32Len - 1, DA_C + DA_32; 非一致程式碼片段, 3216 LABEL_DESC_VIDEO:Descriptor 0B8000h,           0ffffh, DA_DRW; 顯存首地址17 ; GDT 結束1819 GdtLenequ$ - LABEL_GDT; GDT長度20 GdtPtrdwGdtLen - 1; GDT界限21dd0; GDT基地址2223 ; GDT 選擇子24 SelectorCode32equLABEL_DESC_CODE32- LABEL_GDT25 SelectorVideoequLABEL_DESC_VIDEO- LABEL_GDT26 ; END of [SECTION .gdt]2728 [SECTION .s16]29 [BITS16]30 LABEL_BEGIN:31 movax, cs32 movds, ax33 moves, ax34 movss, ax35 movsp, 0100h36 37 ; 初始化 32 位程式碼片段描述符38 xoreax, eax39 movax, cs40 shleax, 441addeax, LABEL_SEG_CODE3242movword [LABEL_DESC_CODE32 + 2], ax43shreax, 1644movbyte [LABEL_DESC_CODE32 + 4], al45movbyte [LABEL_DESC_CODE32 + 7], ah4647 ; 為載入 GDTR 作準備48 xoreax, eax49movax, ds50shleax, 451addeax, LABEL_GDT; eax <- gdt 基地址52movdword [GdtPtr + 2], eax; [GdtPtr + 2] <- gdt 基地址5354; 載入 GDTR55lgdt[GdtPtr]5657; 關中斷58cli5960; 開啟地址線A2061inal, 92h62oral, 00000010b63out92h, al6465; 準備切換到保護模式66moveax, cr067oreax, 168movcr0, eax6970; 真正進入保護模式71jmpdword SelectorCode32:0; 執行這一句會把 SelectorCode32 裝入 cs, 並跳轉到 Code32Selector:0  處72 ; END of [SECTION .s16]737475 [SECTION .s32]; 32 位程式碼片段. 由實模式跳入.76 [BITS32]7778 LABEL_SEG_CODE32:79movax, SelectorVideo80movgs, ax; 視頻段選擇子(目的)8182movedi, (80 * 10 + 0) * 2; 螢幕第 10 行, 第 0 列。83movah, 0Ch; 0000: 黑底    1100: 紅字84moval, 'P'85mov[gs:edi], ax8687; 到此停止88jmp$8990 SegCode32Lenequ$ - LABEL_SEG_CODE3291 ; END of [SECTION .s32]

在下面我要講述3.1中所遇到的問題和迷惑。主要以pmtest1.asm為例,從以下幾方面進行分析和講解。
1)[SECTION .XXX]為何物?
2)通用描述元表(GDT)、描述符(Descriptor)、通用描述元表寄存器(GDTR)、選擇子(SelectorXXX) 是什嗎?用來做什嗎?
3)實模式下的定址方式與保護模式下的定址方式的區別?
4)描述符宏定義和初始化段描述符
5)為載入gdtr做準備工作
6)其他
1、[SECTION .XXX]為何物?
SECTION和SEGMENT的作用相類似,就是代表“段”的意思,從整個程式來看,該程式分為3個模組,分別是[SECTION .gdt]、[SECITON .s16]、[SECTION .s32]三部分。我們很容易就可以看出,其中的[SECTION .gdt]應該是資料區段,其他的兩個是程式碼片段。通過[SECTION .XXX]將程式分成不同模組,完成不同的功能,使得程式看起來清晰明了。
2、描述符(Descriptor)、通用描述元表(GDT)、通用描述元表寄存器(GDTR)、選擇子(SelectorXXX) 是什嗎?用來做什嗎?
 
 
段,在80X86中,分段機制將記憶體空間分成一個或者多個線性地區,我們把這些線性地區稱為段。我們需要將這些段區分開來,於是分段機製為每個段賦予3個屬性,分別是:1、段基址(Base address):指定段線上性地址空間中的開始地址。2、段界限(Limit):表示了段內最大可用位移量,也就是說它定義了段的長度。3、段屬性(Attribute):指定了段的特性,包括:可讀,可寫或者可執行,特權等級等特性。
段描述符(Descriptor),在程式中,我們需要定義一個資料結構來表示段,它同樣包含三個元素,段基址(Base),段界限(Limit),段屬性(Attribute),我們稱它為段描述符(Descriptor)。段描述符和段就是同一個概念,每個段描述符要佔用8個位元組的空間。
段描述符表(Descriptor Table),在一個程式中,不只存在一個段(段描述符)。所以我們需要將這些段描述符組織起來,於是定義了一個儲存段描述符的數組,稱為段描述符表。
段選擇子(SelectorXXX),把所有段描述符都儲存在段描述符表中,當我們使用其中某一個段的時候,我們並不直接指向該段,而是通過該段描述符在段描述符表中的位置來訪問的。故段選擇子,就是一個16位的標識符,用來標識該段描述符在描述符表中的位置。
 
 
段描述符表可以分為兩類,一類是通用描述元表GDT(Global Descriptor Table),一類是局部描述符表LDT(Local Descriptor Table)。系統中供所有的任務共用的是通用描述元表,而不同的任務卻是使用自己的局部描述符表。
緊接著,如何讓系統知道段描述符表在什麼地方呢?處理器提供了記憶體管理寄存器,分別是通用描述元表寄存器(GDTR)、局部描述符表寄存器(LDTR)。GDTR寄存器中用於存放通用描述元表GDT的32位線性基地址和16位的表的長度值。LDTR寄存器中用於存放局部描述符表LDT的32位線性基地址和16位的表的長度值。通過系統指令,lgdt將GDT的線性基址和長度值載入到GDTR寄存器中,lldt將LDT的線性基址和長度值載入到LDTR寄存器中。
下面我們來剖析器中的代碼:
11 [SECTION .gdt]12 ; GDT13 ;                                         段基址,      段界限     , 屬性14 LABEL_GDT:Descriptor       0,                0, 0     ; 空描述符15 LABEL_DESC_CODE32:Descriptor       0, SegCode32Len - 1, DA_C + DA_32; 非一致程式碼片段, 3216 LABEL_DESC_VIDEO:Descriptor 0B8000h,           0ffffh, DA_DRW; 顯存首地址17 ; GDT 結束1819 GdtLenequ$ - LABEL_GDT; GDT長度20 GdtPtrdwGdtLen - 1; GDT界限21dd0; GDT基地址2223 ; GDT 選擇子24 SelectorCode32equLABEL_DESC_CODE32- LABEL_GDT25 SelectorVideoequLABEL_DESC_VIDEO- LABEL_GDT26 ; END of [SECTION .gdt]
在程式中,14、15、16行定義了3個段描述符,LABEL_GDT(空描述符),LABEL_DESC_CODE32(32位程式碼片段描述符),LABEL_SESC_VIDEO(顯示記憶體描述符)。每個描述符都包含了3個屬性,段基址、段界限、段屬性。
將三個描述符組織到一起構成一個全域段描述符表(GDT)。12-17行完成了GDT的定義。
GdtLen為GDT的長度。
GdtPtr為一個資料結構,裡麵包含兩個元素,第一個元素是2 bytes的GDT界限。第二個元素是4 bytes的GDT的基地址。該資料結構與通用描述元表寄存器(GDTR)的資料結構相同,所以在載入GDTR的時候(原始碼55行),就是將該GdtPtr載入到GDTR中。
由於第一個段LABEL_GDT是空描述符,它僅僅代表該GDT的初始地址,所以該描述符為空白描述符,一般情況下,不為它建立選擇子。然後該程式建立了兩個選擇子(24、25行)SelectorCode32和SelectorVideo,分別對應著這兩個段LABEL_SESC_CODE32和LABEL_DESC_VIDEO。
這段代碼的結構大家應該明白了吧,下面我要分別介紹段描述符,通用描述元表寄存器,選擇子的資料結構。
段描述符(Descriptor)的結構圖如下:
 
 
 
段描述符佔有8個位元組,在這裡我只想提醒大家一下,段基址分別佔有BYTE2、BYTE3、BYTE4和BYTE7。在下面初始化段描述符的時候需要用到這些。
段選擇子的結構圖如所示:
 
 
在這裡簡單的介紹一下,它使用(15…3)來表示索引,故每一個描述符表最多隻用213個描述符,除去第一個空描述符,則可以使用的描述符為8191個描述符。
TI標誌著該選擇子所指向的段描述符是全域描述符,還是局部描述符。當TI=0時,表示全域描述符,當TI=1時,表示局部描述符。
RPL請求優先順序,稍後下一節將會提到。
通用描述元表寄存器(GDT)的結構圖如下所示:
 
 
 
 
在這裡,你可以和上面程式中的GdtPtr資料結構做比較,是不是格式相同。2個位元組表示GDT界限,4個位元組表示GDT基地址。
 
3、實模式下的定址方式與保護模式下的定址方式有什麼不同?
在實模式下,也就是在8086系統下的定址方式。 Intel 8086是16位的CPU,它有著16位的寄存器(Register),16位的資料匯流排(Data Bus)以及20位的地址匯流排(Address Bus)和1MB的定址能力。一個地址是由段和位移兩部分組成的,物理地址遵循這樣的計算公式:
物理地址(Physical Address) = 段值(Segment) * 16 + 位移(Offset)
其中段值和位移都是16位的。故定址範圍為1MB。
在保護模式下,有了分段機制,所以它的定址方式發生了很大的變化。具體如所示:
 
 
 
 
在保護模式下,首先使用段選擇子 在 段描述符表 中尋找到相對應的 段描述符,找到32位段基址,然後在與32位的位移量相加,得到線性地址。段基址和段位移量都是32位的,所以定址範圍大小為4GB。在程式中jmp dword SlectorCode32:0的作用,就是進入保護模式下的定址方式。其中,在使用某個段時,它的段選擇子是儲存在段寄存器中的。
這裡面存在著一個問題,是否我們每次定址都要先去通用描述元表寄存器(GDTR)中,尋找到通用描述元表(GDT)的基址,然後再次根據選擇子的索引跳轉到該描述符所在的位置,然後取得段描述符中的基址,如果這樣的話,我們裡裡外外採訪了幾次記憶體,太浪費時間了。實際上段寄存器結構是這樣的:
 
 
這樣的好處就是,我們可以直接擷取段描述符。
 
4、描述符宏定義和初始化段描述符
 1 ; 描述符 2 ; usage: Descriptor Base, Limit, Attr 3 ;        Base:  dd 4 ;        Limit: dd (low 20 bits available) 5 ;        Attr:  dw (lower 4 bits of higher byte are always 0) 6 %macro Descriptor 3 7dw%2 & 0FFFFh; 段界限 1(2 位元組) 8dw%1 & 0FFFFh; 段基址 1(2 位元組) 9db(%1 >> 16) & 0FFh; 段基址 2(1 位元組)10dw((%2 >> 8) & 0F00h) | (%3 & 0F0FFh); 屬性 1 + 段界限 2 + 屬性 2(2 位元組)11db(%1 >> 24) & 0FFh; 段基址 3(1 位元組)12 %endmacro ; 共 8 位元組

2、3、4、5行注釋告訴我們,該宏定義需要三個變數,分別是段基址(4 bytes),段界限(4 bytes),段屬性(dw)。

              

回顧剛才的段描述符結構,該宏定義,就是將變數Base,Limit,Attr分別安插到描述符中相應的位置。Base是1,Limit是2,Attr是3

7 是將Limit低16位賦值給描述符的BYTE0和BYTE1

8 是將Base低16位賦值給描述符的BYTE2和BYTE3

9 是將Base右移16位後的低8位(也就是原Base的第16—23位)賦值給描述符的BYTE4

10是將Limit右移8位之後的第8—11位和Attr的0—7和12—15位,組合起來儲存到描述符的BYTE5和BYTE6

11是將Base右移24位後的低8位(也就是原Base的24—32位)賦值給描述符的BYTE7

初始化段描述符代碼:

37 ; 初始化 32 位程式碼片段描述符38 xoreax, eax39 movax, cs40 shleax, 441addeax, LABEL_SEG_CODE3242movword [LABEL_DESC_CODE32 + 2], ax43shreax, 1644movbyte [LABEL_DESC_CODE32 + 4], al45movbyte [LABEL_DESC_CODE32 + 7], ah

為什麼要初始化?你會發現這裡只是修改了段描述符LABEL_DESC_CODE32的BYTE2,BYTE4,BYTE7。是不是突然恍然大悟?因為在我們初始化該LABEL_DESC_CODE32描述符時,將其基地址初始化為0,所以我們要修改描述符的基地址為其實際的地址。這也是在前面介紹段描述符的時候,我提醒大家需要注意的地方,即描述符的基地址所佔有的位元組是BYTE2,BYTE4,BYTE7。

5、為載入gdtr做準備工作

47 ; 為載入 GDTR 作準備48 xoreax, eax49movax, ds50shleax, 451addeax, LABEL_GDT; eax <- gdt 基地址52movdword [GdtPtr + 2], eax; [GdtPtr + 2] <- gdt 基地址5354; 載入 GDTR55lgdt[GdtPtr]

這個很好理解,我們就是對GdtPtr進行賦值,主要是初始化GDT的基地址。也就是將GDT的初始地址,賦值給GdtPtr的BYTE2,BYTE3,BYTE4,BYTE5。使GdtPtr的資料結構剛好符合GDTR,然後執行lgdt [GdtPtr],載入通用描述元表寄存器。將GDT的基地址和界限賦值給GDTR。

6、其他

至於接下來的 關中斷、開啟地址線A20、切換到保護模式、進入保護模式,跳轉到32位程式碼片段等一系列的問題,可以從書中找到合適的解釋。

 

參考書籍:Linux核心完全剖析基於0.12核心

 

 

 


 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.