2. x86架构:实模式和保护模式

518 阅读6分钟

X86 架构诞生之初,是没有虚拟内存的概念。1978 年发行的 8086 芯片 是 X86 架构的首款芯片,它的内存管理是直接访问物理内存,这种工作方式有一个名称:实模式。后来CPU上出现虚拟内存的概念,将每个进程的地址空间隔离,同时由于页表项中的多种的权限保护标志,极大的提高了程序的数据安全,人们把 CPU 的这种工作模式称为保护模式

从实模式演进到保护模式,X86 体系架构的内存管理发生了重大的变化,最大的不同就体现在段式管理和中断的管理上

8086的实模式

8086 的寄存器只有 16 位。而后面的 CPU 为了保持兼容,在芯片上电以后,还必须运行在 16 位模式之下,这种模式叫做实模式。在实模式下,程序员不能通过内存管理单元(MMU)访问地址,程序必须直接访问物理内存。

那实模式下,我们是怎么访问存储的物理地址的呢?

8086 的寄存器位宽是 16 位,但地址总线却有 20 位,8086 的寻址空间是 1M。但在写程序时没办法把一个地址完整地放到一个寄存器里,因为寄存器相比地址少了 4 位。

为了解决这个问题,8086 就引入了段寄存器,例如 cs、ds、es、gs、ss 等。段寄存器中记录段基地址,通过计算可以得到物理地址。

物理地址 = 段寄存器 << 4 + 段内偏移

i386 中的保护模式

经过十年的发展,X86 CPU 迎来了历史上使用最广泛、影响力最大的 32 位 CPU。i386 与 8086 的一个很大的不同,就是它采用了全新的保护模式。体现在 i386 的段式管理机制相比 8086 发生了重大变化;同时 i386 还引入了页式管理。

i386在完成各项初始化工作后就开启页表,由于段和页都能提供对内存的保护,安全性得到提升,这种模式被称为保护模式。

相比8086,i386的段式管理发生哪些变化?

段选择子和全局描述符表

i386 地址总线是 32 位,寄存器也变成 32 位,意味着因寄存器位数不够而产生的段基址寄存器失去了作用。 但 i386 没放弃段寄存器,将它进化成了新的段式内存管理。段寄存器仍是 16 位寄存器,但其中存的不再是段基址,而是被称为段选择子的东西。

相比 8086,i386 多了一个叫全局描述符表(GDT)的结构。它本质上是一个数组,每一项都是一个全局描述符,32 位的段基址就存储在这个描述符里。段选择子本质上就是这个数组的下标。

image.png

GDT的地址保存在寄存器里,即GDTR,和上节课的CR3类似。

CPU 在处理一个逻辑地址“cs:offset”时,将 GDTR 中的基址加上 cs 中的下标值来得到一个段描述符,再从这个段描述符中取出段基址,最后将段基址与偏移值相加,这样就可以得到线性地址(虚拟地址)。

段式管理与页式管理

段式管理按功能把内存空间分割成不同段,有代码段、数据段、只读数据段、堆栈段等等,为不同的段赋予了不同的读写权限和特权级。通过段式管理,os可以进一步区分内核数据段、内核代码段、用户态数据段、用户态代码段等,为系统提供了更好的安全性。

但是段的长度往往不能固定,例如不同的程序代码段的长度各不相同。以段为单位进行内存的分配和回收,数据结构非常难设计,而且难免会造成各种内存空间的浪费。页式管理则不按照功能区分,而是按照固定大小将内存分割成很多大小相同的页面,不管存放什么,都先分配一个页,再将内容存进页里。

相比页式管理,段式管理的优点是提供更好的安全性,按照内存的用途进行划分更符合人的直观思维。缺点就是由于不定长,难于进行分配、回收调度。

而页式管理的优点是大小固定,分配回收都比较容易。段式管理提供的安全性,在现代 CPU 上也可以被页表项中的属性替代,所以段式管理变得越来越不重要。

但是,如果以 X86 的历史演进来看,段式管理是最早出现的,然后才出现了页式管理。而且现代 X86 架构的 CPU 同时兼容段式管理和页式管理。

总的来说,现代的os都是采用段式管理来做基本的权限管理,而对于内存的分配、回收、调度则依赖页式管理。

中断描述表

中断描述符表(IDT),是 i386 中一个非常重要的描述符表,它也是保护模式的另一大不同。

CPU与外设间的协同工作是以中断机制来进行的。例如敲键盘时键盘的控制器向 CPU 发起一个中断请求。CPU 接到请求后停下正在做的工作, 把当前的寄存器状态保存好,然后去调用中断服务程序。

中断根据中断来源的不同,又可以细分为 Fault、Trap、Abort 以及普通中断,我们统一称为中断。 硬件负责产生中断,CPU 会响应中断,但中断后要做什么是由os定义的。os通过设置某个中断号的中断描述符,指定中断到达以后要调用的函数。 中断描述符表的作用就体现在这了,本质是中断描述符的数组。

IDT 的基地址存储在 idtr 寄存器,这和 GDTR 的设计如出一辙。每个中断都有一个编号与其对应,称之为中断向量号。中断向量号是 CPU 提前分配好的

image.png

小结

相比实模式,i386 中的保护模式, 采用了页式管理,但它没有彻底放弃 8086 的段式管理,而是将段寄存器中的值由段基址变成了段选择子。段选择子本质是 GDT 表的下标值,段基址转移到 GDT 。

段式管理负责将逻辑地址转换为虚拟地址,页式管理负责将虚拟地址映射到物理地址。i386 的保护模式采用了段页式混合管理的模式,兼具了段式管理和页式管理的优点。

除了段页式内存管理这个不同之外,保护模式和实模式的区别还体现在中断描述符表上。IDT 是保护模式的一个重要组成部分,它保存着 i386 中断服务程序的入口地址