GTD 定义
上一章简单介绍了GDT,现在来看看GDT具体格式如何,又是怎么加载进内存的。
首先GDT已四个空字节开头。
gdt_null : ; the mandatory null descriptor
dd 0 x0 ; ’dd ’ means define double word ( i.e. 4 bytes )
dd 0 x0
随后存放每一个segment descriptor,大小固定为 8 byte, 形如
gdt_code : ; the code segment descriptor
; base =0x0 , limit =0 xfffff ,
; 1st flags : ( present )1 ( privilege )00 ( descriptor type )1 -> 1001 b
; type flags : ( code )1 ( conforming )0 ( readable )1 ( accessed )0 -> 1010 b
; 2nd flags : ( granularity )1 (32 - bit default )1 (64 - bit seg )0 ( AVL )0 -> 1100 b
dw 0 xffff ; Limit ( bits 0 -15)
dw 0 x0 ; Base ( bits 0 -15)
db 0 x0 ; Base ( bits 16 -23)
db 10011010 b ; 1st flags , type flags
db 11001111 b ; 2nd flags , Limit ( bits 16 -19)
db 0 x0 ; Base ( bits 24 -31)
再用一些label 来标记起始位置,以及用常量记录各个segnment 对起始位置的偏移。
GDT 加载
加载GDT需要先切换到32-bit模式
切换主要包含以下步骤:
- 禁用中断
因为中断的处理与寻址息息相关,如果切换mode的过程中发生中断,中断处理程序很可能不能正确执行 - 加载GDT表
- 设置cpu寄存器 cr0
这个寄存器控制cpu的工作模式 - 调用一个far junp 来情况指令缓存
由于cpu流水线的工作模式,会提前加载后面执行的指令,如果这个过程中切换了模式会导致指令不能正常工作,而遇到far jump 指令,由于该指令的不确定性,cpu会在执行完该指令后才会重新回到流水线的工作模式,故起到清空流水线缓存指令的效果 - 更新所有段寄存器
- 更新堆栈寄存器
- 调用第一条指令,既32-bit 模式下的指令入口
[bits 16]
switch_to_pm:
mov bx, MSG_TEST
call print
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
mov cr0, eax
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
[bits 32]
init_pm: ; we are now using 32-bit instructions
mov ax, DATA_SEG ; 5. update the segment registers
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
mov esp, ebp
call BEGIN_PM ; 7. Call a well-known label with useful code