前言
如果你想学习或者自己尝试写一个操作系统内核,保护模式是你绕不开的一个话题。
这篇文章的主要目的是让你了解保护模式个东西,以及它的作用。
不会涉及到如何进入保护模式,以及保护模式的一些细节我也会有意略过,
希望你在读这篇文章的时候,能了解计算机的组成, 对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位的数据结构。
数据结构看着有点怪是不是?这其实是历史原因
全局描述符包含可访问的内存的基址, 最大长度, 以及其他的一些权限位。
这个全局描述符的地址被放在了一个称之为 GDTR
的寄存器中
现在的段寄存器存储的就是全局描述符表的索引, 我们一般称之为段选择子
实际上段选择子的高13位存储的是索引, 低3位用来存储和权限相关的bit, 可以参考下图
这样的话我们的寻址方式就变成了以下流程了
- 从GDTR取得全局描述符表的地址
- 从段选择子取得索引 idx, 然后用
idx * 64 + 全局描述符的地址
得到 一个全局描述符的地址(前面我们讲过一个描述符是64bit的数据结构) - 从全局描述符我们可以拿到内存的
BaseAddress
, 用BaseAddress + 16位通用寄存器的偏移
就可以得到最终的物理内存地址
注意, 我有意跳过了相关的权限检查的流程, 参考下图(目前得到的线性地址-Linear address 就是物理内存地址)
总结
保护模式不仅仅是寻址的改变, 其实它还有一个核心就是保护, 这里的保护值得就是权限的校验等等。
还有一点就是保护模式是实现页式内存管理的基础, 而内存分页又是虚拟内存的基础, 所以保护模式在操作系统层次还是有举足轻重的地位的。