操作系统之保护模式简谈
2018/12/30 · vran

前言

如果你想学习或者自己尝试写一个操作系统内核保护模式是你绕不开的一个话题。

这篇文章的主要目的是让你了解保护模式个东西,以及它的作用。

不会涉及到如何进入保护模式,以及保护模式的一些细节我也会有意略过,

希望你在读这篇文章的时候,能了解计算机的组成, 对CPU的寄存器体系有一定的了解。

历史

保护模式是CPU的一种运行模式,出现在80286及之后的X86架构的CPU下。

这里说的CPU运行模式主要体现在CPU在内存寻址的区别。

80286之前只有一种运行模式, 由于保护模式和这种模式有非常大的区别, 为了便于区分,便将以前的运行模式称之为实模式, CPU默认是工作在实模式下的。

实模式

最开始的8086最大寻址空间为1MB(即2^20), 但是8086的内部寄存器只有16位,那么如何能够做到1MB的内存寻址呢?Intel采用了以下的方式

内存地址 = 16位段寄存器 « 4 + 16位通用寄存器保存的地址偏移

这样也会出现一个问题:即最终计算的内存地址会大于2^20 , 此时回产生内存回卷

回卷就是把溢出的最高位1和低16位做加法运算。例如:原本是(1)0100101011000001,回卷就是0100101011000001+1=0100101011000010

这个就是CPU默认的运行模式–实模式的最大特点了。

保护模式

80286之后出现的保护模式,不仅仅改变了内存的寻址方式,还增加了存储器保护标签页系统以及硬件支持的虚拟内存等特性。

为了兼容, 该模式默认是关闭的, 要由程序(通常是系统内核)主动切换。

为什么要有保护模式呢?因为80286的内存寻址空间达到16MB, 而80386更是达到了4GB, 实模式没法满足寻址的要求了。

下面我们就来讲一下保护模式是如何实现以上特性的。

在我们切换到保护模式之前我们要准备一个数据结构**全局描述符表, 它的本质是一个数组, 数组中的每一个元素称为全局描述符**, 这是一个64位的数据结构。

数据结构看着有点怪是不是?这其实是历史原因

global-descriptor

全局描述符包含可访问的内存的基址, 最大长度, 以及其他的一些权限位。

gdt-01

这个全局描述符的地址被放在了一个称之为 GDTR的寄存器中

现在的段寄存器存储的就是全局描述符表的索引, 我们一般称之为段选择子

实际上段选择子的高13位存储的是索引, 低3位用来存储和权限相关的bit, 可以参考下图

selector

这样的话我们的寻址方式就变成了以下流程了

  1. 从GDTR取得全局描述符表的地址
  2. 从段选择子取得索引 idx, 然后用 idx * 64 + 全局描述符的地址 得到 一个全局描述符的地址(前面我们讲过一个描述符是64bit的数据结构)
  3. 从全局描述符我们可以拿到内存的BaseAddress, 用BaseAddress + 16位通用寄存器的偏移 就可以得到最终的物理内存地址

注意, 我有意跳过了相关的权限检查的流程, 参考下图(目前得到的线性地址-Linear address 就是物理内存地址)

segment-translation

总结

保护模式不仅仅是寻址的改变, 其实它还有一个核心就是保护, 这里的保护值得就是权限的校验等等。

还有一点就是保护模式是实现页式内存管理的基础, 而内存分页又是虚拟内存的基础, 所以保护模式在操作系统层次还是有举足轻重的地位的。

over