Linux核心系列—作業系統開發之進入32位保護模式,linux保護模式
源碼如下:
; ==========================================; pmtest1.asm; 編譯方法:nasm pmtest1.asm -o pmtest1.bin; ==========================================%include"pm.inc"; 常量, 宏, 以及一些說明org07c00hjmpLABEL_BEGIN[SECTION .gdt]; GDT; 段基址, 段界限 , 屬性LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致程式碼片段LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址; GDT 結束GdtLenequ$ - LABEL_GDT; GDT長度GdtPtrdwGdtLen - 1; GDT界限dd0; GDT基地址; GDT 選擇子SelectorCode32equLABEL_DESC_CODE32- LABEL_GDTSelectorVideoequLABEL_DESC_VIDEO- LABEL_GDT; END of [SECTION .gdt][SECTION .s16][BITS16]LABEL_BEGIN:movax, csmovds, axmoves, axmovss, axmovsp, 0100h; 初始化 32 位程式碼片段描述符xoreax, eaxmovax, csshleax, 4addeax, LABEL_SEG_CODE32movword [LABEL_DESC_CODE32 + 2], axshreax, 16movbyte [LABEL_DESC_CODE32 + 4], almovbyte [LABEL_DESC_CODE32 + 7], ah; 為載入 GDTR 作準備xoreax, eaxmovax, dsshleax, 4addeax, LABEL_GDT; eax <- gdt 基地址movdword [GdtPtr + 2], eax; [GdtPtr + 2] <- gdt 基地址; 載入 GDTRlgdt[GdtPtr]; 關中斷cli; 開啟地址線A20inal, 92horal, 00000010bout92h, al; 準備切換到保護模式moveax, cr0oreax, 1movcr0, eax; 真正進入保護模式jmpdword SelectorCode32:0; 執行這一句會把 SelectorCode32 裝入 cs,; 並跳轉到 SelectorCode32:0 處; END of [SECTION .s16][SECTION .s32]; 32 位程式碼片段. 由實模式跳入.[BITS32]LABEL_SEG_CODE32:movax, SelectorVideomovgs, ax; 視頻段選擇子(目的)movedi, (80 * 11 + 79) * 2; 螢幕第 11 行, 第 79 列。movah, 0Ch; 0000: 黑底 1100: 紅字moval, 'P'mov[gs:edi], ax; 到此停止jmp$SegCode32Lenequ$ - LABEL_SEG_CODE32; END of [SECTION .s32]
運行結果如下,在螢幕最右邊有一個紅色的P:
源碼解析:
1.首先程式跳轉至LABEL_BEGIN處,jmp LABEL_BEGIN。將ds、es、ss段寄存器全部初始化為當前程式碼片段。
2.初始化32位程式碼片段描述符
在實模式下,也就是8086的16位的CPU的定址方式是段x16+位移,而在保護模式下,段寄存器值變成了一個索引,這個索引指向段描述符,也就是GDT中對應的那個描述符,描述符中包含了最終的基地址。
38-41行表示將當前程式碼段左移4位也就是x16,再加上LABEL_SEG_CODE32的位移地址,最終形成LABEL_SEG_CODE32的物理地址,42-45表示把此物理地址載入到段描述符對應的段基址位置,段描述符格式如所示
段基地址0-15位也就是LABEL_DESC_CODE32 + 2地址處,將16位的ax傳入,第43行向右移動16位表示已經傳入的16位消除,然後將剩餘的兩個8位高低寄存器值傳入對應的段基地址處。
Descriptor是在pm.inc中定義的宏,如所示,表示的就是段描述符的格式:
3.載入GDTR,GDRT的結構圖如下所示
GDTR是唯一的一個指向段描述符表的寄存器,48-55行就是把段描述符表的基地址載入到GDTR寄存器當中。48行清除eax值,49-52是把LABEL_GDT的物理地址載入到20行的GdtPtr的雙字處,也就是GDT基地址。55行把GdtPtr地址的內容載入到GDTR,雙位元組dw對應的是16位界限,雙字dd對應的是32位基地址。
4.關中斷和開啟A20
保護模式下中斷處理機制和實模式不同,所以先關閉中斷,指令為cli。開啟A20為曆史原因防止位移超出最大值時復原。58-63行
5.切換到保護模式
只要把寄存器cr0的第0位置為1就行了。66-68行
6.真正進入保護模式
jmp dword SelectorCode32:0,這一行是真正進入保護模式的代碼,SelectorCode32是個段選擇子,段選擇子的作用是找到對應的段描述符從而找到對應的段基地址。段選擇子的格式如所示
從位3開始表示為段描述符表的索引,這句代碼執行完之後就會把描述符LABEL_DESC_CODE32中的段基址也就是LABEL_SEG_CODE32載入到cs。
dword的作用是因為當前還是16位的代碼,如果沒有dword,SelectorCode32:0的位移地址如果超出16位那麼只會截取16位。
到此已經完全進入32位保護模式了。