建造一个操作系统(1)

306 阅读2分钟

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模式
切换主要包含以下步骤:

  1. 禁用中断
    因为中断的处理与寻址息息相关,如果切换mode的过程中发生中断,中断处理程序很可能不能正确执行
  2. 加载GDT表
  3. 设置cpu寄存器 cr0
    这个寄存器控制cpu的工作模式
  4. 调用一个far junp 来情况指令缓存
    由于cpu流水线的工作模式,会提前加载后面执行的指令,如果这个过程中切换了模式会导致指令不能正常工作,而遇到far jump 指令,由于该指令的不确定性,cpu会在执行完该指令后才会重新回到流水线的工作模式,故起到清空流水线缓存指令的效果
  5. 更新所有段寄存器
  6. 更新堆栈寄存器
  7. 调用第一条指令,既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