8086的两种工作模式_自制操作系统学习4 进入32位保护模式

01b009ac4150e146da951105e9d509f2.png

一、概念

1. 为什么要进保护模式

8086最大寻址范围是1M,而超过64K的内存区域访问要靠切换段基址。
当Intel的CPU发展到32位后,寻址空间达到了4G。32位下改变了寻址方式,使用名为GDT的表来管理内存,其实就是查表法,在GDT里记录每个内存段的段基址、段界限、段属性等信息。GDT里每个表项称为描述符Descriptor。
进入32位后,原先的AX,BX,CX,DX,SI,DI,SP,BP从16位扩展(Extend)到了32位,并改名EAX,EBX,ECX,EDX,ESI,EDI,ESP,EBP。
CS,DS,ES,SS这几个16位段寄存器保留,再增加FS,GS两个段寄存器。

CPU在保护模式下使用段描述符采用段描述符缓冲寄存器。在获得一个段描述符之后,后面访问相同段,就会直接访问该寄存器。

2. x86基本运行模式

1. 实地址模式

英特尔的x86系列处理器为兼容早期的8086处理器,在上电后处于这个模式。8086有20位地址线,1M的线性地址空间。

2. 保护模式

一个受保护并支持多任务的环境。大部分OS运行在这个模式下。80386有32位地址线,4G线性地址空间。 80386把4G只分为一个段,段基址0x00000000,段长0xFFFFFFFF(4G)

84cd336b46c57438869c48b99924ae84.png

3. 64位模式

由AMD根据速龙64处理器提出的模式,这种模式下允许处理器进入64位的内存空间,并操作64位寄存器。
多数的16位和32位处理器并不能工作在长模式下。 x86-64的实模式准确的说是像16位处理器,而x86-64的保护模式像是32位处理器。 要让芯片有64位的处理能力,就要切换到64位模式下。

4. 正在进入保护模式

5. 正在进入64位模式

3. 段描述符

在保护模式下,寻址内存的段值仍是16位的CS,DS等寄存器,但它仅是一个索引,指向了GDT表的一个描述符。段描述符是8字节(64bit),用来描述一个内存段。

7febff42dea374c90e7fe2de60be94c6.png

说明:

  • G:Granularity,颗粒度标志,1表示段界限单位=4K,0表示段界限单位=1Byte
  • D/B:指明这个段中操作的长度。如果这位被设置,那么这个段是32位段,否则是16位段。
  • L:64位标志,只在IA-32e模式中使用。
  • AVL:Available,可用位,可以为操作系统软件使用。
  • P:Present,存在位,如果为0表示该段不在内存中。
  • S:为1是代码数据段,否则是系统段。
  • DPL:Descriptor Privilege Level,特权级标志。
  • TYPE:最复杂的东西,涉及到段的类型,不过可以读protect.inc里的注释来了解。

这里段基址需要32位地址表示、段界限用20位表示。段界限的单位可以是字节,也可以是4K。一个段最大值可能是2202^{20}220(1M)或2322^{32}232(4G)

4388f7aad51c679a3a1206a2258517e8.png

总结:

  • 描述这些信息的内容放在内存中,称为全局描述表(Global Descriptor Table,GDT)
  • GDT的位置可以由程序员指定,放在1M以下。7c00是512字节引导区,所以可以把GDT放在0x7e00后,GDT的界限最大可以到0x17DFF (64K)。 7c00以下放BIOS数据区、中断向量表等。
  • Intel使用寄存器GDTR来保存GDT信息
  • GDTR是48位专用寄存器,0-15位是GDT边界位置,16-47位放的GDT基地址
212d357804876d778c9127cddb7f9d18.png

二、进入保护模式的步骤:

386以后的处理器,有CR0-CR4 寄存器,用于控制模式,分页等高级功能。其中CR0寄存器的PE位表示运行状态,0是实模式、1是保护模式。

46e0f74bec6aaf7afa1b402e1160cf11.png
  1. 准备GDT
  2. 创建一个6个字节的伪描述符指向GDT
  3. 用 lgdt 加载 gdtr
  4. 打开 A20
  5. 跳转,进入保护模式

其中CR0中有一位是PE位,保护模式使能位。

三、进入保护的汇编代码

cpu32.s

[SECTION .code]global _start_start:    jmp 0:kernel_start[SECTION .gdt]; 准备GDT 表定义gdt_start:gdt_null:    dd 0x0    dd 0x0; 代码段定义gdt_code:    dw 0xffff    dw 0x0    db 0x0    db 10011010b    db 11001111b    db 0x0; 数据段定义gdt_data:    dw 0xffff    dw 0x0    db 0x0    db 10010010b    db 11001111b    db 0x0gdt_end:; 描述符gdt_descriptor:    dw gdt_end - gdt_start    dd gdt_start; 代码段基址CODE_SEG equ gdt_code - gdt_start; 数据段基址DATA_SEG equ gdt_data - gdt_start; 通过中段打印字符串子程序print:    pusha    mov ah, 14    mov bh, 0.loop:    lodsb    cmp al, 0    je .done    int 0x10    jmp .loop.done:    popa    ret; 定义要打印的两个字符串mode_16 db 'mode_16', 0mode_32 db 'mode_32', 0[SECTION .s16][bits 16]; 程序入口kernel_start:    ; 堆栈初始化    mov ax, 0    mov ss, ax    mov sp, 0xFFFC    ; 段寄存器初始化    mov ax, 0    mov ds, ax    mov es, ax    mov fs, ax    mov gs, ax    ; 打印现在处于16位模式下    mov si, mode_16    call print    cli    ; 用lgdt加载GDTR    lgdt[gdt_descriptor]    ; 打开A20    mov eax, cr0    or eax, 0x1    mov cr0, eax    ; 跳到保护模式    jmp CODE_SEG:b32[bits 32]VIDEO_MEMORY equ 0xb8000WHITE_ON_BLACK equ 0x0f; 32位模式下 向显存写数据print32:    pusha    mov edx, VIDEO_MEMORY.loop:    mov al, [ebx]    mov ah, WHITE_ON_BLACK    cmp al, 0    je .done    mov [edx], ax    add ebx, 1    add edx, 2    jmp .loop.done:    popa    ret; 进入32位模式b32:    mov ax, DATA_SEG    mov ds, ax    mov es, ax    mov fs, ax    mov gs, ax    mov ss, ax    mov ebp, 0x2000    mov esp, ebp    mov ebx, mode_32    call print32    jmp $; 填充引导区times  510-($-$$)-0x45 db 0     ; 填充剩下的空间,不知道为什么不能直接引用_start的地址,这里0x45是我临时凑出来的dw 0xAA55

运行效果:

356350b45465fe05b9f3032de8cadd35.png

四、定义GDT数据结构

pm.inc

; 描述符图示; 图示一;;  ------ ┏━━┳━━┓高地址;         ┃ 7  ┃ 段 ┃;         ┣━━┫    ┃;                  基;  字节 7 ┆    ┆    ┆;                  址;         ┣━━┫ ② ┃;         ┃ 0  ┃    ┃;  ------ ┣━━╋━━┫;         ┃ 7  ┃ G  ┃;         ┣━━╉──┨;         ┃ 6  ┃ D  ┃;         ┣━━╉──┨;         ┃ 5  ┃ 0  ┃;         ┣━━╉──┨;         ┃ 4  ┃ AVL┃;  字节 6 ┣━━╉──┨;         ┃ 3  ┃    ┃;         ┣━━┫ 段 ┃;         ┃ 2  ┃ 界 ┃;         ┣━━┫ 限 ┃;         ┃ 1  ┃    ┃;         ┣━━┫ ② ┃;         ┃ 0  ┃    ┃;  ------ ┣━━╋━━┫;         ┃ 7  ┃ P  ┃;         ┣━━╉──┨;         ┃ 6  ┃    ┃;         ┣━━┫ DPL┃;         ┃ 5  ┃    ┃;         ┣━━╉──┨;         ┃ 4  ┃ S  ┃;  字节 5 ┣━━╉──┨;         ┃ 3  ┃    ┃;         ┣━━┫ T  ┃;         ┃ 2  ┃ Y  ┃;         ┣━━┫ P  ┃;         ┃ 1  ┃ E  ┃;         ┣━━┫    ┃;         ┃ 0  ┃    ┃;  ------ ┣━━╋━━┫;         ┃ 23 ┃    ┃;         ┣━━┫    ┃;         ┃ 22 ┃    ┃;         ┣━━┫ 段 ┃;;   字节  ┆    ┆ 基 ┆; 2, 3, 4;         ┣━━┫ 址 ┃;         ┃ 1  ┃ ① ┃;         ┣━━┫    ┃;         ┃ 0  ┃    ┃;  ------ ┣━━╋━━┫;         ┃ 15 ┃    ┃;         ┣━━┫    ┃;         ┃ 14 ┃    ┃;         ┣━━┫ 段 ┃;; 字节 0,1┆    ┆ 界 ┆;;         ┣━━┫ 限 ┃;         ┃ 1  ┃ ① ┃;         ┣━━┫    ┃;         ┃ 0  ┃    ┃;  ------ ┗━━┻━━┛低地址;; 图示二; 高地址………………………………………………………………………低地址; |   7   |   6   |   5   |   4   |   3   |   2   |   1   |   0    |; |7654321076543210765432107654321076543210765432107654321076543210|;1可用286TSS;2LDT;3忙的286TSS;4286调用门;5任务门;6286中断门;7286陷阱门;8未定义;9可用386TSS;A;B忙的386TSS;C386调用门;D;E386中断门;F386陷阱门;; (5) G:    段界限粒度(Granularity)位。;G=0 表示界限粒度为字节;;G=1 表示界限粒度为4K 字节。;           注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。 ;; (6) D:    D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。 ;           ⑴ 在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。;① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段;;② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。 ;           ⑵ 在向下扩展数据段的描述符中,D位决定段的上部边界。;① D=1表示段的上部界限为4G;;② D=0表示段的上部界限为64K,这是为了与80286兼容。 ;           ⑶ 在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。;① D=1表示使用32位堆栈指针寄存器ESP;;② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。 ;; (7) AVL:  软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。 ;;----------------------------------------------------------------------------; 描述符类型值说明; 其中:;       DA_  : Descriptor Attribute;       D    : 数据段;       C    : 代码段;       S    : 系统段;       R    : 只读;       RW   : 读写;       A    : 已访问;       其它 : 可按照字面意思理解;----------------------------------------------------------------------------DA_32EQU4000h; 32 位段DA_DPL0EQU  00h; DPL = 0DA_DPL1EQU  20h; DPL = 1DA_DPL2EQU  40h; DPL = 2DA_DPL3EQU  60h; DPL = 3;----------------------------------------------------------------------------; 存储段描述符类型值说明;----------------------------------------------------------------------------DA_DREQU90h; 存在的只读数据段类型值DA_DRWEQU92h; 存在的可读写数据段属性值DA_DRWAEQU93h; 存在的已访问可读写数据段类型值DA_CEQU98h; 存在的只执行代码段属性值DA_CREQU9Ah; 存在的可执行可读代码段属性值DA_CCOEQU9Ch; 存在的只执行一致代码段属性值DA_CCOREQU9Eh; 存在的可执行可读一致代码段属性值;----------------------------------------------------------------------------; 系统段描述符类型值说明;----------------------------------------------------------------------------DA_LDTEQU  82h; 局部描述符表段类型值DA_TaskGateEQU  85h; 任务门类型值DA_386TSSEQU  89h; 可用 386 任务状态段类型值DA_386CGateEQU  8Ch; 386 调用门类型值DA_386IGateEQU  8Eh; 386 中断门类型值DA_386TGateEQU  8Fh; 386 陷阱门类型值;----------------------------------------------------------------------------; 选择子图示:;         ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓;         ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9  ┃ 8  ┃ 7  ┃ 6  ┃ 5  ┃ 4  ┃ 3  ┃ 2  ┃ 1  ┃ 0  ┃;         ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫;         ┃                                 描述符索引                                 ┃ TI ┃   RPL    ┃;         ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛;; RPL(Requested Privilege Level): 请求特权级,用于特权检查。;; TI(Table Indicator): 引用描述符表指示位;TI=0 指示从全局描述符表GDT中读取描述符;;TI=1 指示从局部描述符表LDT中读取描述符。;;----------------------------------------------------------------------------; 选择子类型值说明; 其中:;       SA_  : Selector AttributeSA_RPL0EQU0; ┓SA_RPL1EQU1; ┣ RPLSA_RPL2EQU2; ┃SA_RPL3EQU3; ┛SA_TIGEQU0; ┓TISA_TILEQU4; ┛;----------------------------------------------------------------------------; 宏 ------------------------------------------------------------------------------------------------------;; 描述符; usage: Descriptor Base, Limit, Attr;        Base:  dd;        Limit: dd (low 20 bits available);        Attr:  dw (lower 4 bits of higher byte are always 0)%macro Descriptor 3dw%2 & 0FFFFh; 段界限 1(2 字节)dw%1 & 0FFFFh; 段基址 1(2 字节)db(%1 >> 16) & 0FFh; 段基址 2(1 字节)dw((%2 >> 8) & 0F00h) | (%3 & 0F0FFh); 属性 1 + 段界限 2 + 属性 2(2 字节)db(%1 >> 24) & 0FFh; 段基址 3(1 字节)%endmacro ; 共 8 字节;; 门; usage: Gate Selector, Offset, DCount, Attr;        Selector:  dw;        Offset:    dd;        DCount:    db;        Attr:      db%macro Gate 4dw(%2 & 0FFFFh); 偏移 1(2 字节)dw%1; 选择子(2 字节)dw(%3 & 1Fh) | ((%4 << 8) & 0FF00h); 属性(2 字节)dw((%2 >> 16) & 0FFFFh); 偏移 2(2 字节)%endmacro ; 共 8 字节; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

cpu32.s

%include"src/boot/pm.inc"global _start_start:    jmp 0:LABEL_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 结束 GdtLen      equ $ - LABEL_GDT   ; GDT长度GdtPtr      dw  GdtLen - 1      ; GDT界限            dd  0               ; GDT基地址 ; GDT 选择子SelectorCode32      equ LABEL_DESC_CODE32   - LABEL_GDTSelectorVideo       equ LABEL_DESC_VIDEO    - LABEL_GDT; END of [SECTION .gdt][SECTION .s16][bits 16]; 程序入口LABEL_BEGIN:movax, csmovds, axmoves, axmovss, axmovsp, 0xFFFC; 初始化 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 

Makefile 编译脚本:

# 进入32位保护模式,加载到0x10000hcpu32.o : $(SRC)boot/cpu32.s$(NASM) $(ASFLAGS) -f elf32 -o $(TARGET)$@ $^cpu32.bin : cpu32.o$(LD) $(LDFLAGS) -Ttext 0x7c00 --oformat binary -o $(TARGET)$@ $(TARGET)$^install_cpu32 : cpu32.bin$(DD) if=$(TARGET)cpu32.bin of=$(TARGET)os.img conv=notruncrun_cpu32 : install_cpu32cd $(TOOLPATH) && bochs.bat && cd ..

目录结构:

ec6c90d432a8c26b66b2a797d49968cc.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值