本节对应代码:进入保护模式-代码详解
参考:《操作系统真相还原》《x86汇编:从实模式到保护模式》

五部曲概览

  • 打开 A20 地址线
  • 加载 GDT
  • 将 CR0 寄存器的 PE 位设置为 1
  • 关闭中断
  • 刷新流水线

以上五个步骤没有顺序,但为了保险起见,最好将操作 CR0 放在最后一步。

打开 A20 地址线

8086/8088 CPU 的地址总线为 20 位,寻址空间为 1MB 。当地址超过 1MB 时,地址就会发生回绕,即重新从 0 算起,相当于对 1MB 求模。这种缺陷被当时很多程序员利用,成为了一个编程技巧,并写入了程序(一旦用得多,就必须考虑兼容)。后来的 80286 CPU的地址总线扩展到 24 位,寻址空间达到 16MB 。考虑到 80286 需要兼容 8086 实模式,即在实模式下必须只使用 20 位地址线,以确保能够发生回绕。为实现这一目的,CPU 在 A20 地址线(第21根地址线)处设置了一个开关(A20Gate),在实模式下关闭,则最大地址空间为 1MB,能够发生回绕;在保护模式下打开,寻址空间扩展到 24 位。

32位 CPU 类似,只有打开 A20Gate 后,地址空间才能由 1MB 扩展到 4GB,从而进入更大的空间。

打开 A20Gate 的方法很简单,将 0x92 端口的第 1 位(起始位是第0位)置 1 即可

in  a1,0x92       #先读进来
or  al,0000_0010B #第1位置1
out 0x92,al       #再写出去

加载GDT

使用指令 lgdt (load gdt)将 GDT 的信息加载进 GDTR

lgdt  gdt_ptr

gdt_ptr 是标号,代表 GDT 所在的地址,指向一个包含了 48 位的内存区域。该区域的高 32 位必须为 GDT 的基地址,低 16 位为边界 。该指令在实模式和保护模式下都能够使用。注意,必须先加载 GDT 才能够进入保护模式 !由于实模式下的内存空间为 1MB,所以只有先将 GDT 放在 1MB 内存以内,进入保护模式后可以转移 GDT 的位置。

置 PE 位为 1

CR0 寄存器的 PE 位(第 0 位)是进入 32 位保护模式的开关,开启后,对指令的解释将改变,参见32位保护模式概览。CR0 结构如下:

各位的解释如下:

置 1 方式和打开 A20Gate 差不多:

mov eax,cr0
or  eax,1
mov cr0,eax

此后,CPU 就进入到保护模式啦!

关闭中断

cli

准确来说是禁止可屏蔽中断。从实模式进入保护模式时,不关中断也是可以的,如果中断发生在实模式进入保护模式之前和之后都没有问题,但若在模式切换进行时发生中断就会发生异常了,这个情况概率比较低。具体原因笔者也不太明白,可参考为什么保护模式需要使用cli指令禁止硬件中断?)

另外,保护模式下不能再使用 BIOS 中断。

刷新流水线

其实本来只有前三步曲,但进入保护模式后还有一个相当重要的步骤必须马上进行:刷新流水线。未进入保护模式时,CPU 使用 16 位指令格式译码,进入保护模式后,使用 32 位指令格式译码(两者区别参见32位保护模式概览)。而进入保护模式这一瞬间,有很多实模式下的指令之前已经进入了流水线,而保护模式对这些指令的解释有所不同,可能引发错误,所以必须清空流水线。关于流水线,参见处理器微架构使用 jmp 指令即可刷新流水线

jmp dword SELECTOR_CODE:p_mode_start

关于这条指令的详细解释,参见进入保护模式-代码详解

另外,其实在置 PE 位为 1 和刷新流水线的这段时间内,系统实际上是处于 16 位保护模式下。这是因为置 PE 为 1 后,即刚进入保护模式时,CS 的描述符高速缓存器中仍然是实模式下的内容(此时还没来得及更新),其 D 位是 0,因此在刷新流水线前,处理器运行在 16 位保护模式下。

文章作者: 极简
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 后端技术分享
自制操作系统
喜欢就支持一下吧