《操作系统真象还原》第四章——保护模式入门

本文详细介绍了在计算机系统中如何使用全局描述符表(GDT)和局部描述符表(LDT)来管理代码段、数据段、显存以及设置特权级别。内容涵盖了GDT的结构、描述符的属性设置、控制寄存器CR0的使用以及进入保护模式的过程。
摘要由CSDN通过智能技术生成

前置知识

段描述符格式

  • 段界限:表示段边界的扩展最值
    • 此段界限是一个单位量,其单位要么是字节,要么是4KB,具体可由G位进行指定
    • 最终段界限=此段界限值*单位
    • 实际段界限计算公式:(此段界限值+1)*(G位)-1
  • G:粒度位,用于解释段界限的单位
    • 为0:表示段界限的单位表示为字节
    • 为1:表示段界限的单位表示为4KB
  • S描述符类型
    • 为0,表示系统段
    • 为1,表示非系统段(代码段或者数据段)
  • type:用于指示描述符的子类型
  • DPL:Descriptor Privilege Level,即描述符特权级
    • 特权级共有0、1、2、3,用于解释段界限的含义
  • P段存在位,用于指示描述符所对应的段是否存在
    • P位是由处理器负责检查的。每当通过描述符访问内存中的段时,如果P位是“0”,处理器就会产生一个异常中断
    • 该中断处理过程是由操作系统提供的,该处理过程的任务是负责将该段从硬盘换回内存,并将P位置1
  • D/B:标志位,为了能够在32位处理器上兼容运行16位保护模式的程序。该标志位对不同的段有不同的效果。
    • 对于代码段,此位称作“D”位,用于指示指令中默认的有效地址和操作数尺寸;
      • D=0表示指令中的有效地址或者操作数是16位的;
      • D=1,指示32位的有效地址或者操作数。
    • 对于栈段和向下扩展的数据段来说,该位被叫作“B”位,用于指定在进行隐式的栈操作时,是使用寄存器SP还是寄存器ESP,隐式的栈操作指令包括push、pop和call等。
      • 如果该位是“0”,在访问那个段时,使用寄存器SP;
      • 否则就是使用寄存器ESP。
  • AVL:表示软件是否可以使用(Available),通常由操作系统来用,处理器并不使用它。
  • L:用于设置是否是64位代码段
    • 为1,表示64位代码段
    • 为0,表示32位代码段

选择子

  • RPL:请求特权级
  • TI(table indicator):表示在GDT(全局描述符表)还是在LDT(局部描述符表)中索引描述符
    • 为0,在GDT中进行索引
    • 为1,在LDT中进行索引 
  • 3~15位,具体描述符的索引值,2^13=8192,故最多可以索引8192个段 

 GDT寄存器GDTR

  • 作用:用于指向GDT的内存地址
  • 访问格式:lgdt 48位内存数据

由上述GDTR寄存器的格式可以看出,GDT的大小占16位,也就是65536个字节,而每个描述符占8个字节,因此GDT最多可以容纳的描述符数量是65536/8=8192个 

控制寄存器CR0

控制寄存器是CPU的窗口,既可以用来展示CPU的内部状态,也可以用于控制CPU的运行机制

其中CR0的PE位是保护模式的开关

  • 为0时,表示在实模式下运行
  • 为1时表示在保护模式下运行 

进入保护模式

boot.inc

;-----loader and kernel-----
    LOADER_BASE_ADDR equ 0x900                      ;loader在内存中位置
    LOADER_START_SECTOR equ 0x2                     ;loader在磁盘中的逻辑扇区地址,即LBA地址


;-----  gdt描述符属性  ---------
    DESC_G_4K   equ   1_00000000000000000000000b    ;设置段界限的单位为4KB
    DESC_D_32   equ    1_0000000000000000000000b    ;设置代码段/数据段的有效地址(段内偏移地址)及操作数大小为32位
    DESC_L    equ       0_000000000000000000000b    ;64位代码段标记位,现在是32位操作系统,因此标记为0即可。
    DESC_AVL  equ        0_00000000000000000000b

;定义段界限位
    ;段界限的第2部分,即描述符的高32位中的第16~19位,最终的代码段段界限为0xFFFFF
    DESC_LIMIT_CODE2 equ 1111_0000000000000000b     ;定义代码段要用的段描述符高32位中16~19段界限为全1
    DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2           ;定义数据段要用的段描述符高32位中16~19段界限为全1
    DESC_LIMIT_VIDEO2  equ 0000_000000000000000b    ;定义我们要操作显存时对应的段描述符的高32位中16~19段界限为全0
    DESC_P  equ 1_000000000000000b                  ;定义了段描述符中的P标志位,表示该段描述符指向的段是否在内存中

;定义描述符的特权级别位
    DESC_DPL_0 equ 00_0000000000000b
    DESC_DPL_1 equ 01_0000000000000b
    DESC_DPL_2 equ 10_0000000000000b
    DESC_DPL_3 equ 11_0000000000000b
;定义类型位
    DESC_S_CODE equ 1_000000000000b                 ;代码段和数据段都是非系统段,故类型字段s设置为1
    DESC_S_DATA equ DESC_S_CODE                     ;代码段和数据段都是非系统段,故类型字段s设置为1
    DESC_S_sys equ 0_000000000000b                  ;系统段的类型字段设置为0
;定义子类型位
    DESC_TYPE_CODE equ 1000_00000000b               ;x=1,c=0,r=0,a=0 代码段是可执行的,非一致性,不可读,已访问位a清0
    DESC_TYPE_DATA equ 0010_00000000b               ;x=0,e=0,w=1,a=0,数据段不可执行,向上扩展,可写,已访问位a清0

;拼接代码段的描述符
DESC_CODE_HIGH4 equ (0x00<<24) + DESC_G_4K \
                    + DESC_D_32 + DESC_L + \
                    DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + \
                    DESC_S_CODE + DESC_TYPE_CODE + 0x00
;拼接数据段的描述符
DESC_DATA_HIGH4 equ (0x00<<24) + DESC_G_4K \
                    + DESC_D_32 + DESC_L + \
                    DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + \
                    DESC_S_DATA + DESC_TYPE_DATA + 0x00

;拼接显存段的描述符位                    
DESC_VIDEO_HIGH4 equ (0x00<<24) + DESC_G_4K \
                    + DESC_D_32 + DESC_L + \
                    DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + \
                    DESC_S_DATA + DESC_TYPE_DATA + 0x0b

;-----  选择子属性  ---------
    RPL0 equ 00b
    RPL1 equ 01b
    RPL2 equ 10b
    RPL3 equ 11b

    TI_GDT equ 000b
    TI_LDT equ 100b

原书勘误,显存描述符应该为的最后应该是0x0b,而不是0x00

DESC_VIDEO_HIGH4 equ (0x00<<24) + DESC_G_4K \
                    + DESC_D_32 + DESC_L + \
                    DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + \
                    DESC_S_DATA + DESC_TYPE_DATA + 0x0b

%include "boot.inc"

SECTION loader vstart=LOADER_BASE_ADDR
;初始化栈指针地址
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start

;------------- 构建gdt及其内部的描述符 -------------
    GDT_BASE: 
        dd 0x00000000
        dd 0x00000000

    ;代码段描述符的低4字节部分,其中高两个字节表示段基址的0~15位,在这里定义为0x0000
    ;低两个字节表示段界限的0~15位,由于使用的是平坦模型,因此是0xFFFF
    CODE_DESC:  
        dd 0x0000FFFF
        dd DESC_CODE_HIGH4;段描述符的高4字节部分
    DATA_STACK_DESC: 
        dd 0x0000FFFF
        dd DESC_DATA_HIGH4

    ;定义显存段的描述符
    ;文本模式下的适配器地址为0xb8000~0xbffff,为了方便显存操作,显存段不使用平坦模型
    ;因此段基址为0xb8000,段大小为0xbffff-0xb8000=0x7fff,
    ;段粒度位4k,因此段界限的值为0x7fff/4k=7
    VIDEO_DESC: 
        dd 0x80000007
        dd DESC_VIDEO_HIGH4

    GDT_SIZE equ $-GDT_BASE
    GDT_LIMIT equ GDT_SIZE-1
    times 60 dq 0 ;此处预留60个描述符的空位
    
;------------- 构建选择子 -------------
    SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0
    SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0
    SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0


;------------- 定义gdtr(指向GDT的寄存器) -------------
    gdt_ptr dw GDT_LIMIT
            dd GDT_BASE
            
;------------- 加载器的显式信息 -------------
    loadermsg db '2 loader in real .'


loader_start:
;------------------------------------------
;INT 0x10 功能号:0x13 功能描述:打印字符串
;------------------------------------------
;输入:
    ;AH:功能号
    ;AL:显示输出方式
    ;   0——字符串中只含显示字符,显示属性在BH中,显示后光标位置不变
    ;   1——字符串中只含显示字符,显示属性在BH中,显示后光标位置改变
    ;   2——字符串中含显示字符和显示属性,显示后光标位置不变
    ;   3——字符串中含显示字符和显示属性,显示后光标位置改变
    ;BH:页码
    ;BL:属性
    ;CX:字符串长度
    ;(DH、DL):坐标(行、列)
    ;ES:BP 字符串地址
;无返回值
    mov sp,LOADER_BASE_ADDR
    mov bp,loadermsg
    mov cx,17
    mov ax,0x1301
    mov bx,0x001f;页号为0,蓝底粉红字
    mov dx,0x1800
    int 0x10

;------------- 准备进入保护模式 -------------
;1.打开A20
;2.加载gdt
;3.置cr0的PE位为1

    ;------------- 打开A20 -------------
    in al,0x92
    or al,0000_0010B
    out 0x92,al
    
    ;------------- 加载gdt -------------
    lgdt [gdt_ptr]

    ;------------- 置cr0的PE位为1 -------------
    mov eax,cr0
    or eax,0x00000001
    mov cr0,eax

    jmp dword SELECTOR_CODE:p_mode_start;刷新流水线

[bits 32]
p_mode_start:
     
    mov ax,SELECTOR_DATA           ;初始化段寄存器,将数据段的选择子分别放入各段寄存器
    mov ds,ax
    mov es,ax                      
    mov ss,ax

    mov esp,LOADER_STACK_TOP        ;初始化站栈指针,将栈指针地址放入bsp寄存器
    mov ax,SELECTOR_VIDEO           ;初始化显存段寄存器,显存段的选择子放入gs寄存器
    mov gs,ax

    mov byte [gs:160],'p'

    jmp $


编译

nasm -I ./include/ -o mbr.bin mbr.S
nasm -I ./include/ -o loader.bin loader.S

磁盘写入

注意loader.S写入磁盘时count参数为2

dd if=./osCode/mbr.bin of=./bochs/hd60M.img bs=512 count=1 conv=notrunc
dd if=./osCode/loader.bin of=./bochs/hd60M.img bs=512 count=2 seek=2 conv=notrunc

运行

输出p说明GDT成功建立,如下调试所示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值