实模式和保护模式

内容来自 https://zhuanlan.zhihu.com/p/42309472

实模式和保护模式

要了解实模式和保护模式,要从Inter的早期芯片历史开始了解。

Inter 8086一个由Intel于1978年所设计发布的第一款16位微处理器芯片,是x86架构的鼻祖。8086所有的内部寄存器、内部及外部数据总线都是16位宽,因此是完全的16位微处理器。20位外部地址总线,因此物理寻址空间为1MiB (即220 = 1,048,576).由于内部寄存器都是16位,对1M地址空间寻址时采取了段寻址方式。

Inter 8086引脚图:
image

数据总线与地址总线复用了前16个引脚(AD15~AD0)。16位的I/O地址,因此独立的I/O寻址空间为64KiB的虚拟寻址空间 (即216 = 65,536).由于8086内部的地址寄存器是16 位宽,因而最大线性寻址空间为64 KiB.使用超过64 KiB内存空间的程序设计时,需要调整段寄存器(segment registers)。

20位地址总线的寻址空间位1Mib,因为2的20次方 = 1048576个内存空间 = 1048576Byte = 1024 KB = 1 MB = 512K(字) (一字 = 2字节 = 16bit)
在内存中的寻址范围是00000-FFFFF;(FFFFF转换为十进制为1048575)

举个例子:在一个游戏中,你有一块1048575米长的木板,游戏要求你要拜托小明在木板上的某一个位置摆放一粒大米,但你只能讲出两个65536以内的数字,在游戏前你可以和小明任意讨论,你要怎么把这个位置准确地告诉他呢?

其实1048575就是2的20次方,代表地址总线;65536就是2的16次方,代表寄存器。只要和小明商量一个计算规则就可以啦,第一个数x16 + 第二个数 就可以表示1048575内的任意一个数字了,这就是csx16+ip,也就是内存分段的由来。问题来了。Inter为什么要设计20位的地址总线呢,直接设计16位的地址总线不行吗,我只能理解为内存越大越好吧。

同时由于段地址是csx16,所以段地址必定能被16整除,所以8086 处理器的逻辑分段,起始地址都是16的倍数,这称为是按16字节对齐的。
即:

 物理地址 = 段基址<<4 + 段内偏移

  所以假设段寄存器中的值是0xff00,段偏移量为0x0110。则这个地址对应的真实物理地址是 0xff00<<4 + 0x0110 = 0xff110。

由上面的介绍可见,实模式的"实"更多地体现在其地址是真实的物理地址。

在段不重叠的情况下,最多可以将1MB的内存分成 65536 个段, 段地址分别是 0000H、 0001H、 0002H、 0003H,……, 一直到 FFFFH。 在这种情况下,每个段正好16个字节,偏移地址从 0000H 到 000FH。

同样在不允许段之间重叠的情况下,每个段的最大长度是 64KB, 因为偏移地址也是 16 位的,从 0000H 到 FFFFH。 在这种情况下,1MB 的 内存,最多只能划分成 16 个 段,每段长 64KB, 段地址分别是 0000H、 1000H、 2000H、 3000H,…, 一 直到 F000H。

李忠. x86汇编语言:从实模式到保护模式 (Kindle 位置 702-704). 电子工业出版社. Kindle 版本

2.保护模式工作原理

随着CPU的发展,CPU的地址线的个数也从原来的20根变为现在的32根,所以可以访问的内存空间也从1MB变为现在4GB,寄存器的位数也变为32位。所以实模式下的内存地址计算方式就已经不再适合了。所以就引入了现在的保护模式,实现更大空间的,更灵活也更安全的内存访问。

在保护模式下,CPU的32条地址线全部有效,可寻址高达4G字节的物理地址空间; 但是我们的内存寻址方式还是得兼容老办法(这也是没办法的,有时候是为了方便,有时候是一种无奈),即(段基址:段偏移量)的表示方式。当然此时CPU中的通用寄存器都要换成32位寄存器(除了段寄存器,原因后面再说)来保证寄存器能访问所有的4GB空间。

我们的偏移值和实模式下是一样的,就是变成了32位而已,而段值仍旧是存放在原来16位的段寄存器中,但是这些段寄存器存放的却不再是段基址了,毕竟之前说过实模式下寻址方式不安全,我们在保护模式下需要加一些限制,而这些限制可不是一个寄存器能够容纳的,于是我们把这些关于内存段的限制信息放在一个叫做全局描述符表(GDT)的结构里。全局描述符表中含有一个个表项,每一个表项称为段描述符。而段寄存器在保护模式下存放的便是相当于一个数组索引的东西,通过这个索引,可以找到对应的表项。段描述符存放了段基址、段界限、内存段类型属性(比如是数据段还是代码段,注意一个段描述符只能用来定义一个内存段)等许多属性,具体信息见下图:
image
其中,段界限表示段边界的扩张最值,即最大扩展多少或最小扩展多少,用20位来表示,它的单位可以是字节,也可以是4KB,这是由G位决定的(G为1时表示单位为4KB)。

实际段界限边界值=(描述符中的段界限+1)*(段界限的单位大小(即字节或4KB))-1,如果偏移地址超过了段界限,CPU会抛出异常。

全局描述符表位于内存中,需要用专门的寄存器指向它后, CPU 才知道它在哪里。这个专门的寄存器便是GDTR(一个48位的寄存器),专门用来存储 GDT 的内存地址及大小。

最后我们再介绍一下一个新的概念:段的选择子。段寄存器 CS、 DS、 ES、 FS、 GS、 SS,在实模式下时,段中存储的是段基地址,即内存段的起始地址。 而在保护模式下时,由于段基址已经存入了段描述符中,所以段寄存器中再存放段基址是没有意义的,在段寄 存器中存入的是一个叫作选择子的东西。选择子“基本上”是个索引值,虽然它还有其他内容,不过作为初学者暂时忽略也没太大关系。由于段寄存器是 16 位,所以选择子也是 16 位,在其低 2 位即第 0~1 位, 用来存储 RPL,即请求特权级(有兴趣的可以了解一下,不想了解的忽略即可,跟用户态和内核态相关的),可以表示 0、 1、 2、 3 四种特权级。在选择子的第 2 位是 TI 位,即 Table Indicator,用来指示选择子是在 GDT 中,还是 LDT 中索引描述符。 TI 为 0 表示在 GDT 中索引描述符, TI 为 1 表示在 LDT 中索引描述符。选择子的高 13 位,即第 3~15 位是 描述符的索引值,用此值在 GDT 中索引描述符。前面说过 GDT 相当于一个描述符数组,所以此选择子中的索引值就是 GDT 中的下标。选择子结构如下:

image
此外, 扩充的存储器分段管理机制和可选的存储器分页管理机制,不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持; 支持多任务,能够快速地进行任务切换(switch)和保护任务环境(context); 4个特权级和完善的特权检查机制,既能实现资源共享又能保证代码和数据的安全和保密及任务的隔离; 支持虚拟8086方式,便于执行8086程序。

posted @ 2021-11-06 11:57  Pril  阅读(227)  评论(0)    收藏  举报