8086 中的实模式

在实模式下,程序员是不能通过内存管理单元(Memory Management Unit, MMU)访问地址的,程序必须直接访问物理内存。

  • 寄存器位宽16位, 地址总线20位, 所以使用双寄存器表示地址

物理地址 = 段寄存器 << 4 (16bit) + 段内偏移 (4bit)

cs: 代码段基址寄存器
ds: 数据段基址寄存器
ss: 栈基址寄存器

这种按功能分段的管理内存方式就是段式管理

i386 中的保护模式

i386 与 8086 的一个很大的不同,就是它采用了全新的保护模式。这个体现在,i386 中的段式管理机制,相比 8086 发生了重大变化;同时,i386 芯片在段式管理的基础上,还引入了页式管理。

  • i386 在完成各种初始化动作以后,就会开启页表
  • i386 的保护模式是一种段式管理和页式管理混合使用的模式

变化一:段选择子和全局描述符表

  • 在 i386 上,地址总线是 32 位的,通用寄存器也变成 32 位的,这就意味着因为寄存器位数不够而产生的段基址寄存器已经失去了作用。
  • 但是 i386 没有直接放弃掉段寄存器,而是将它进化成了新的段式内存管理。段寄存器仍然是 16 位寄存器,但是其中存的不再是段基址,而是被称为段选择子的东西。
  • i386 中多了一个叫全局描述符表(Global Descriptor Table, GDT)的结构。它本质上是一个数组,其中的每一项都是一个全局描述符,32 位的段基址就存储在这个描述符里。段选择子本质上就是这个数组的下标

image.png

  • GDT 的地址也要保存在寄存器里,这个寄存器就是 GDTR
  • CPU 在处理一个逻辑地址“cs:offset”的时候,就会将 GDTR 中的基址加上 cs 中的下标值来得到一个段描述符,再从这个段描述符中取出段基址,最后将段基址与偏移值相加,这样就可以得到线性地址了。
  • 由 CPU 的 MMU 将线性地址映射为物理地址,然后就可以交给地址总线去进行读写了。

变化二:段寄存器对段的保护能力增强

i386 中段描述符(也就是 GDT 中的每一项)的结构。

image.png

其中比较重要的属性有 P 位、DPL、S 位、G 位和 Type

  • P 位是一个比特,指示了段在内存中是否存在,1 表示段在内存中存在,0 则表示不存在。
  • DPL,占据了两个比特,指的是描述符特权级,英文是 Descriptor Privilege Level。Intel 规定了 CPU 工作的 4 个特权级,分别是 0、1、2、3,数字越小,权限越高。
  • S 位,S 为 1 代表该描述符是数据段 / 代码段描述符,为 0 则代表系统段 / 门描述符。门是 i386 提供的用于切换特权级的机制,有调用门、陷阱门、中断门、任务门等。在 Linux 系统中,只使用了中断门描述符。
  • G 位,它指的是定义段颗粒度(Granularity),它的值为 0 时,段界限的单位是字节,为 1 时段界限以 4KB 为单位,也就是一页。
    • “段界限”字段并不是连续的,它一共有 20 位
    • 当 G=1 时,段界限的最大值是 2^20 * 4K = 4G
  • Type 属性,它定义了描述符类型

image.png

段式管理对比页式管理

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

页式管理则不按照功能区分,而是按照固定大小将内存分割成很多大小相同的页面,不管是存放数据,还是存放代码,都要先分配一个页,再将内容存进页里。

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

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

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

中断描述符表

中断描述符表(Interruption Description Table, IDT)

CPU 与外设之间的协同工作是以中断机制来进行的

硬件负责产生中断,CPU 会响应中断,但是中断来了以后要做什么事情是由操作系统定义的。操作系统要通过设置某个中断号的中断描述符,来指定中断到达以后要调用的函数。中断描述符表(IDT)的作用就体现在这了,它的本质就是中断描述符的数组。

  • IDT 的基地址存储在 idtr 寄存器中
  • 每个中断都有一个编号与其对应,我们称之为中断向量号。中断向量号是 CPU 提前分配好的

image.png

Linux 系统把中断向量表的 32 号中断(用户自定义中断的第一位)设置成 8259A 的 0 号中断,也就是说 IDT 的 32 号至 47 号都分配给了 8259A 所管理的中断。键盘、软盘、硬盘、鼠标的中断服务程序就设置在这里。