Loading... # 前言 在操作系统启动时,会先进入`实模式(Real Mode)`,然后再转换为`保护模式(Protected Mode)`,为什么需要这么做?两种模式的寻址方式有何不同?本文通过`Intel 8086`和`Intel 80286`分段寻址的不同来解释这两种模式的区别。 ## `Intel 8086` `8086`被设计为完全的16位处理器,所有的内部寄存器、内部及外部数据总线都是16位宽,不过它具有20位宽的外部地址总线,这意味着它能够寻址`2^20=1,048,576`个地址,也就是最大能够支持1MB的寻址空间。这里可能会有疑问,刚才不是说`8086`是16位处理器吗?怎么能够进行20位地址总线寻址呢?这是由于`8086`处理器支持`分段机制(Segmentation)`,它能够通过`16 * Segment + Offset`的方式进行寻址,例如`Segment`为`0xf000`,`Offset`为`0xfff0`,则进行如下转换: ``` 16 * 0xf000 + 0xfff0 = 0xf0000 + 0xfff0 = 0xffff0 ``` `Segment`为16位的基地址被存储在`段寄存器(Segment Registers)`,`Offset`也同样为16位的偏移地址,通过左移位的方式组合得到的结果`0xffff0`称之为`线性地址(Linear Address)`,可以理解为`Segment`为一个内存区域,`Offset`为相对于这个内存区域起始地址的偏移量。这时有的人可能会问了,如果`Segment`为`0xffff`且`Offset`为`0xffff`,通过上述公式计算: ``` 16 * 0xffff + 0xffff = 0xffff0 + 0xffff = 0x10ffef ``` 最终结果为`0x10ffef`,这已经超过20位地址总线的寻址范围(至少得24位地址总线才能够寻址此地址),这时`CPU`会丢弃最高位,实际访问的地址为`0xffef`(这也是在使用更大的内存地址空间前启用A20总线的原因,既启用高位地址)。这种直接能够访问内存地址的方式称之为`实模式(Real Mode)`。 ## `Intel 80286` 当`80286`问世时,外部地址总线被拓宽到24位,使得它的寻址能力进一步提升,但是它所带来的不仅仅是寻址能力的提升,还有一个新东西:`保护模式(Protected Mode)`。`保护模式`下的`分段机制`与`实模式`下的分段机制有些许不同,它通过以下数据结构进行`段翻译(Segment Translation)`: * `描述符(Descriptor)` 用来存储分段信息 * `描述符表(Descriptor Table)` 有描述符所组成的数组结构 * `段选择器(Selector)` 从描述符表中找到具体的某个描述符 虽然`80286`的`段寄存器(Segment Registers)`依然是16位,但是它保存的不再是`段基地址`,而是称之为`Selector`的`选择器`,这与`实模式`下的`分段机制`有所不同。`保护模式`下每个分段信息是存储在对应的`Descriptor`中,那么我们要进行地址转换则需要先在`Descriptor Table`中找到这个`Descriptor`,这就需要`Selector`,通过`Selector`定位到`Descriptor`,然后翻译`Segment`成为`线性地址(Linear Address)`: ![linear address](/usr/uploads/2020/11/1021179124.png) ### `Descriptor` 先看下`Descriptor`,它是由8-byte组成的数据结构: ![segment descriptor](/usr/uploads/2020/11/2536320800.jpg) * `段基地址(Base Address)` * 由三个片段组成占用32位,用来表示地址的开始位置(后续配合`Offset`来得到一个具体的地址) * `Segment Limit` * 由两个片段组成占用20位,这两用来表示地址范围 * `G=Granularity` * 如果清除,`Segment Limit`单位为`bytes`最大能够表示 `2^20` bytes * 如果设置,表示以`4KB(页大小)`为单位,最大可表示 `2^32` bytes * `D=Default operand size` * 如果清除,则为16位代码段 * 如果设置,则为32位 * `B=Big` * 如果设置,则数据段的最大偏移量增加到32位`0xffffffff` * 否则为最大16位`0x0000ffff`,与 `D` 基本相同 * `L=Long` * 如果设置,则为64位段(`D` 必须为零),并且该段中的代码使用64位指令编码 * 不能将 `L` 与 `D` 或 `B` 同时设置 * `AVL=Available` * 仅供软件使用,硬件不做处理 * `P=Present` * 如果清除,则对此段的任何引用都会生成“段不存在”异常 * `DPL=Descriptor privilege level` * 访问此描述符所需的特权级别 * `Type` * 如果设置,则为代码段描述符。如果清除,则为数据/堆栈段描述符 * `C=Conforming` * 可以从特权级别较低的级别调用此段中的代码 * `E=Expand-Down` * 如果清除,则该段将从基地址扩展到基数+限制 * 如果设置,它将从最大偏移向下扩展到限制,这是通常用于堆栈的行为 * `R=Readable` * 如果清除,则该段可以执行但不能读取 * `W=Writable` * 如果清除,则可以读取但不能写入数据段 * `A=Accessed` * 当访问该段时,该位由硬件设置为1,并由软件清除 由`描述符`的数据结构可以看出,它包含了段信息以及权限等信息,在程序将`Selector`加载到段寄存器中时,`CPU`不仅加载段的基地址,而且加载保护信息,根据这些信息对段内存进行保护,所以称之为`保护模式(Protected Mode)`。 未完待续... 最后修改:2020 年 11 月 24 日 © 允许规范转载 赞 0 如果觉得我的文章对你有用,请随意赞赏