1.第一節:最小的操作系統(引導扇區)
1.1環境准備
1.vmware虛擬機:
建立一個新的空白的虛擬機,命名為Tinix.添加軟盤,使用自制的映像文件
虛擬機設置:
2.nasm編譯器
3.notepad++
1.2制作映像文件
1.boot.asm文件
org 07c00h mov ax,cs mov ds,ax mov es,ax call DispStr jmp $ DispStr: mov ax,BootMessage mov bp,ax mov cx,16 mov ax,01301h mov bx,000ch mov dl,0 int 10h ret BootMessage: db "Hello, OS world!" times 510-($-$$) db 0 dw 0xaa55
2.使用nasm編譯
cmd.exe中使用命令:
nasm boot.asm -o boot.bin
1.3 啟動系統
將boot.bin文件放如軟盤中,啟動Tinix系統:
至此完成自制操作系統的第一步(boot sector)引導扇區.
第二小節:保護模式
2.1 實模式到保護模式 GDT表
參考:https://blog.csdn.net/yihaolovem/article/details/23483927
備注:寄存器參考:https://www.cnblogs.com/joey-hua/p/5347257.html
https://blog.csdn.net/jnu_simba/article/details/11712675
段頁機制:https://www.cnblogs.com/chenwb89/p/operating_system_003.html
https://blog.csdn.net/suppercoder/article/details/9422093
https://blog.csdn.net/fallingu/article/details/75221276
https://blog.csdn.net/bian1029/article/details/49124593(GDT表第一項為0)
; ========================================== ; pmtest1.asm ; 編譯方法:nasm pmtest1.asm -o pmtest1.com ; ========================================== %include "pm.inc" ; 常量, 宏, 以及一些說明 org 0100h jmp LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址, 段界限 , 屬性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代碼段, 32 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址 ; GDT 結束 GdtLen equ $ - LABEL_GDT ; GDT長度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 選擇子 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt] [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h ; 初始化 32 位代碼段描述符 xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_SEG_CODE32 mov word [LABEL_DESC_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah ; 為加載 GDTR 作准備 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 ; 加載 GDTR lgdt [GdtPtr] ; 關中斷 cli ; 打開地址線A20 in al, 92h or al, 00000010b out 92h, al ; 准備切換到保護模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正進入保護模式 jmp dword SelectorCode32:0 ; 執行這一句會把 SelectorCode32 裝入 cs, 並跳轉到 Code32Selector:0 處 ; END of [SECTION .s16] [SECTION .s32]; 32 位代碼段. 由實模式跳入. [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorVideo mov gs, ax ; 視頻段選擇子(目的) mov edi, (80 * 10 + 0) * 2 ; 屏幕第 10 行, 第 0 列。 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 mov al, 'P' mov [gs:edi], ax ; 到此停止 jmp $ SegCode32Len equ $ - LABEL_SEG_CODE32 ; END of [SECTION .s32]
1、[SECTION .XXX]為何物?
SECTION和SEGMENT的作用相類似,就是代表“段”的意思。從整個程序來看,該程序分為3個模塊,分別是[SECTION .gdt]、[SECITON .s16]、[SECTION .s32]三部分。我們很容易就可以看出,其中的[SECTION .gdt]應該是數據段,其他的兩個是代碼段。通過[SECTION .XXX]將程序分成不同模塊,完成不同的功能,使得程序看起來清晰明了。
2. 段描述符宏定義和初始化段描述符
描述符宏定義:
; 宏 ------------------------------------------------------------------------------------------------------ ; ; 描述符 ; usage: Descriptor Base, Limit, Attr 定義三個變量,段基址,段界限,段屬性 ; Base: dd 4個字節 ; Limit: dd (low 20 bits available) 4個字節 ; Attr: dw (lower 4 bits of higher byte are always 0) 2個字節 %macro Descriptor 3 ;%macro是宏定義,Descriptor是宏名,3表示該宏有三個參數 dw %2 & 0FFFFh ; 段界限1,段界限值0-15位 注:匯編語言中數據不能以字母開頭 dw %1 & 0FFFFh ; 段基址1,段基地址0-15 db (%1 >> 16) & 0FFh ; 段基址2,段基地址16-23位 dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 屬性1 + 段界限2 + 屬性2 db (%1 >> 24) & 0FFh ; 段基址3,基地址24-31位 %endmacro ; 共 8 字節
Base是%1,Limit是%2,Attr是%3。
段描述符結構圖:
P,present位,1表示所描述的段存在(有效),為0表示所描述的段無效,使用該描述符會引起異常
DPL,Descriptor privilege,描述符特權級別,說明所描述段的特權級別
S,描述符類型位,1說明當前描述符為存儲段描述符,0為系統描述符或門描述符.
TYPE:
位0:A(accessed)位,表明描述符是否已被訪問;把選擇子裝入段寄存器時,該位被標記為1
位3:E(EXECUTABLE?)位,0說明所描述段為數據段;1為可執行段(代碼段)
當為數據段時,
位1為W位,說明該數據段是否可寫(0只讀,1可寫)
位2為ED位,說明該段的擴展方向(0向高位擴展,1向低位擴展)
當為可執行段是,
位1為R位,說明該執行段是否可讀(0只執行,1可讀)
位2為C位,0說明該段不是一致碼段(普通代碼段),1為一致碼段
G為粒度位,0說明LIMIT粒度為字節,1為4K字節.
D位:
1.在可執行段中,D為1,表示使用32位地址,32/8位操作數;為0表示使用16位地址,16/8位操作數
2.在由SS尋址的段描述符(堆棧段?)中,D為1表示隱含操作(如PUSH/POP)使用ESP為堆棧指針,
為0使用SP(隱含操作:未明確定義段屬性類型USE16/USE32?66H,67H?)
3.在向低擴展的存儲段中,D為1,表示段的上限為4G;為0上限為64K
參考:https://images2015.cnblogs.com/blog/790694/201606/790694-20160619001102057-493760916.png
GDT的作用是用來提供段式存儲機制,這種機制是段寄存器和GDT中的描述符共同提供的。每個描述符在GDT中占8字節,也就是 2 個雙字,或者說是 64 位。圖中,下面是低32位,上面是高32位。
將Limit低16位賦值給描述符的BYTE0和BYTE1
將Base低16位賦值給描述符的BYTE2和BYTE3
將Base右移16位后的低8位(也就是原Base的第16—23位)賦值給描述符的BYTE4
將Limit右移8位之后的第8—11位和Attr的0—7和12—15位,組合起來存儲到描述符的BYTE5和BYTE6
將Base右移24位后的低8位(也就是原Base的24—32位)賦值給描述符的BYTE7
初始化段描述符:
; 初始化 32 位代碼段描述符 xor eax, eax mov ax, cs shl eax, 4 ;左移四位 = cs*16 add eax, LABEL_SEG_CODE32 ;有效地址 = 段值*16 + offset mov word [LABEL_DESC_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah
為什么要初始化?你會發現這里只是修改了段描述符基地址,即LABEL_DESC_CODE32的BYTE2,BYTE4,BYTE7。是不是突然恍然大悟?因為在我們初始化該LABEL_DESC_CODE32描述符時,將其基地址初始化為0,所以我們要修改描述符的基地址為其實際的地址。這也是在前面介紹段描述符的時候,我提醒大家需要注意的地方,即描述符的基地址所占有的字節是BYTE2,BYTE4,BYTE7(共32bit)。
詳解:
mov ax,cs
shl eax,4
將cs的值(16bit 實模式下即為當前代碼段的基地址)
左移4bit即得到當前代碼段的物理基地址
add eax,LABEL_SEG_CODE32
此時eax即為實模式下LABEL_SEG_CODE32的物理地址
然后再用此地址分為三部分去初始化LABEL_DESC_CODE32
3.加載GDTR
; 為加載 GDTR 作准備 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 ; 加載 GDTR lgdt [GdtPtr]
這個很好理解,我們就是對GdtPtr進行賦值,主要是初始化GDT的基地址。也就是將GDT的初始地址,賦值給GdtPtr的BYTE2,BYTE3,BYTE4,BYTE5。使GdtPtr的數據結構剛好符合GDTR,然后執行lgdt [GdtPtr],加載全局描述符表寄存器。將GDT的基地址和界限賦值給GDTR。
2.2 LDT 局部描述符表
LDT和GDT本質上都是段描述符表,具有相同的結構,區別僅在於全局和局部的不同。
參考:https://blog.csdn.net/wrx1721267632/article/details/52056910
- LDT在系統中可以存在多個
- LDT不是全局可見的,它們只對引用它們的任務可見,每個任務最多可以擁有一個LDT。
- 每一個LDT自身作為一個段存在,它們的段描述符被放在GDT中。
LDTR寄存器:
至此,我們可以這樣理解GDT和LDT:GDT為一級描述符表,LDT為二級描述符表。如圖:
段選擇子:
引用GDT和LDT中的段描述符所描述的段,是通過一個16-bit的數據結構來實現的,這個數據結構叫做Segment Selector——段選擇子。它的高13位作為被引用的段描述符在GDT/LDT中的下標索引,bit 2用來指定被引用段描述符被放在GDT中還是到LDT中,bit 0和bit 1是RPL——請求特權等級,被用來做保護目的。如圖所示:
2.3 特權級
在IA32的分段機制中,特權級總共有四個特權級別,從高到低分別對應0,1,2,3。數字越小表示特權越大。
作用:防止低特權級應用訪問高特權級數據,代碼。
特權級檢驗主要通過CPL,DPL,RPL來實現。
1.CPL
當前執行程序或任務的特權級。
被存儲在CS的第0位和SS的第一位上。
通常表示代碼段的特權級。
2.DPL
表示段或門的特權級。
存儲在段描述符或門描述符的DPL字段中。
3.RPL
段選擇子的第0位和第1位。
系統調用時使用RPL作為調用者的特權級。
補充:
參考:https://www.cnblogs.com/LittleHann/p/3850655.html
門描述符:
處理器對程序的執行主要時順序和跳轉兩種方式,跳轉也就是程序控制的轉移,可以通過指令jmp、call、ret、sysenter、
sysexit、int n、iret引起,也可以由中斷和異常機制引起。
在I386CPU中,除了"段描述符"(描述某種內存段)之外還有一種描述符叫做"門描述符"(描述控制轉移的入口點,也就是異常控制中斷的入口點),通過這種門可以實現特權級的轉變和任務的切換。門描述符主要由以下幾部分組成:
1. 選擇子
2. 偏移地址
3. DPL
分為4種類型:
- 調用門
- 中斷門
- 陷阱門
- 任務門
1. 調用門描述符 調用門一般用在特權級的切換,存在於GDT中或者LDT中。調用門的選擇子指向代碼段描述符,偏移地址對應代碼段中的偏移量。當jump和call指令的操作數是調用門的時候,就會跳轉到對應的代碼處,並發生特權級的變化,也就會發生 堆棧的切換 2. 任務門描述符 任務門一般用在任務的切換,可以存放在GDT、LDT或IDT中。任務門的選擇子指向GDT中的TSS選擇符,偏移地址沒有意義。當jmp和Call指令的操作數是任務門的時候,就會發生任務的切換。 3. 中斷門描述符
4. 陷阱門描述符 中斷門描述符、陷阱門描述符用來對中斷服務例程進行尋址,從原理上來理解,中斷例程的尋址本質上也是內存的尋址
保護模式初步總結:
- 描述符中段基址、段界限、段屬性對段的一種保護
- 門描述符中特權級之間的變換
- 涉及到特權級的每一步中,處理器都會對CPL、DPL、RPL等內容進行比較。
關於段描述符和門描述符參考:https://blog.csdn.net/barech/article/details/4401417
可以理解為,段描述符是對存儲訪問的管理控制,門描述符是對調用,特權級轉變的管理控制。2019-04-25
保護模式總結更新:
- 在GDT、LDT、IDT中,每一個描述符都有自己的界限和屬性 -- 對描述符所描述對象的一種限定和保護
- 分頁機制中的PDE和PTE都含有R/W和U/S位 -- 頁級保護
- 頁式存儲使應用程序使用的是線性地址空間 -- 物理內存被保護起來
- 中斷不再像實模式下一樣使用 -- 特權檢驗,中斷調用被保護
- I/O指令不再隨便使用 -- 端口被保護起來
- 在程序運行過程中,如遇到不同特權級間的訪問,會對CPL、RPL、DPL、IOPL等內容進行檢驗,同時進行堆棧切換 -- 對不同層級的程序進行了保護。