Loading... # 认识保护模式 ## 为什么需要保护模式 - Intel 8086是16位CPU,它有着16位的寄存器,16位的数据总线以及20位的地址总线和1MB的寻址能力。 - 从80386开始CPU进入32位时代,寻址能力达到4GB,无法使用16位寄存器完成寻址 ## GDT(global descriptor table) 而保护模式下,虽然段值仍然由原来16位的cs、ds等寄存器表示,但此时它仅仅变成了一个索引,这个索引指向一个数据结构的一个表项,表项中详细定义了段的起始地址、界限、属性等内容。这个数据结构,就是GDT. ### 描述符(Descriptor) GDT的作用是用来提供段式存储机制,这种机制是通过段寄存器和GDT中的描述符共同提供的。 #### 描述符的结构 ![image-20240910135918638](https://y0k1n0-1323330522.cos.ap-beijing.myqcloud.com/image-20240910135918638.png) - 段基址:规定线性地址空间中段的开始地址。在保护模式下,段基地址长32位。因为基地址长度与寻址地址的长度相同,所以段基地址可以是 0~4GB 范围内的任意地址,而不像实方式下规定的边界必须被16整除。 - 段界限:段界限规定段的大小。在保护模式下,段界限决定了偏移量的最大值。对于向下扩展的段,如堆栈段来说, 段界限决定了偏移量的最小值。 ```asm [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_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt] ``` 在`pmtest1.asm`中定义了如上的GDT,包括: - 三个描述符:`空描述符` `非一致代码段` `显存首地址`,并赋予`段基址` `段界限` `属性` 的初值 - GDT长度 - GDT指针:包括GDT界限和GDT基地址 - GDT选择子:包括`非一致代码段选择子` `显存首地址选择子` ### 选择子(selector) ![image-20240910141437914](https://y0k1n0-1323330522.cos.ap-beijing.myqcloud.com/image-20240910141437914.png) ```asm ; GDT 选择子 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ``` 对于上面的代码片段,**在`Ti`和`RPL`都是0的时候,选择子可以看做是描述符相对于GDT基地址的偏移** ## 段式寻址 理解`[SECTION .s16]`程序的关键是要明白此时CPU仍然处于实模式,此时最大的寻址范围仍然是1M,因此可以用`cs`寄存器存储 ```asm [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs ;cs为0 mov ds, ax mov es, ax mov ss, ax mov sp, 0100h ``` 这一节程序是用cs中存储的段基址(即`LABEL_BEGIN`标签在内存中对应的地址)初始化`ds` `es` `ss`寄存器,并初始化`sp`寄存器 ```asm ; 初始化 32 位代码段描述符 xor eax, eax mov ax, cs shl eax, 4 ;段基址(CS)左移四位 add eax, LABEL_SEG_CODE32 ;加上偏移地址(LABEL_SEG_CODE32) ``` 这段程序利用如下公式算出`LABEL_SEG_CODE32`真实的逻辑地址并存储在eax中(此时仍然处于实模式,计算机的最大寻址空间仍然是1M) $$ 逻辑地址=段基址*16+偏移地址 $$ ```asm ;对照图3.4阅读 mov word [LABEL_DESC_CODE32 + 2], ax;BYTE2,3 shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al;BYTE4 mov byte [LABEL_DESC_CODE32 + 7], ah;BYTE7 ``` 这段程序则是将`eax`(`LABEL_SEG_CODE32`的真实逻辑地址)赋值给`BYTE2` `BYTE3` `BYTE4` `BYTE7`作为保护模式下描述符`LABEL_DESC_CODE32`的基地址 ```asm ; 为加载 GDTR 作准备 xor eax, eax mov ax, ds ;ds = cs shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; GDT基地址([GdtPtr + 2]) <- gdt 基地址 ``` 这段程序是将`GDT`的信息写入`GDTPtr`指针 首先计算出`LABEL_GDT`对应的内存地址,然后将`GdtPtr`对应的基地址赋值为`LABEL_GDT`的真实的内存地址 ```asm lgdt [GdtPtr] ``` 这段程序是将`GdtPtr`加载到`gdtr`寄存器当中,`gdtr`寄存器的格式如下图所示 ![image-20240910151254302](https://y0k1n0-1323330522.cos.ap-beijing.myqcloud.com/image-20240910151254302.png) ```asm ; 在保护模式下,如果不关终端程序会发生错误 cli ; 打开地址线A20,才能访问所有的内存空间 in al, 92h or al, 00000010b out 92h, al ``` - 问:什么是A20? - 答:这是一个历史问题。 - 8086中,“段:偏移”这样的模式能表示的最大内存是FFFF:FFFF,即10FFEFh。可是8086只有20位的地址总线,只能寻址到1MB,那么如果试图访问超过1MB的地址时会怎样呢?实际上系统并不会发生异常,而是回卷(wrap)回去,重新从地址零开始寻址。可是, - 到了80286时,真的可以访问到1MB以上的内存了,如果遇到同样的情况,系统不会再回卷寻址,这就造成了向上不兼容. - 为了保证百分之百兼容,IBM想出一个办法,使用8042键盘控制器来控制第20个(从零开始数)地址位,这就是A20地址线,如果不被打开,第20个地址位将会总是零。 ```asm ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正进入保护模式 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, ; 并跳转到 Code32Selector:0 处 ``` 这段程序首先通过控制cr0寄存器的第0位(PE标志位:1为实模式,0为保护模式)切换到保护模式 然后使用`jmp dword SelectorCode32:0`跳转到实模式,将`SelectorCode32`即`LABEL_SEG_CODE32`标志的真实物理地址当做保护模式的段基址 > 还是没弄明白 `Code32Selector:0`这里是怎么跳转到`LABEL_SEG_CODE32`对应的标签,`Code32Selector`明明对应的是`LABEL_SEG_CODE32`和`LABEL_GDT`的差值,怎么会能够转移到`LABEL_SEG_CODE32`对应的标签 最后修改:2024 年 09 月 12 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏