我们从80386处理器入手。首先,到了80386时代,CPU有了四种运行模式,即实模式、保护模式、虚拟8086模式和SMM模式。
实模式其大致包括实模式1MB的线性地址空间、内存寻址方法、寄存器、端口读写以及中断处理方法等内容。到了80386时代,引进了一种沿用至今的CPU运行机制——保护模式(Protected Mode)。保护模式有一些新的特色,用来增强系统稳定度,比如内存保护,分页系统,以及硬件支持的虚拟内存等。
对CPU来讲,系统中的所有储存器中的储存单元都处于一个统一的逻辑储存器中,它的容量受CPU寻址能力的限制。
这个逻辑储存器就是我们所说的线性地址空间。8086有20位地址线,拥有1MB的线性地址空间。而80386有32位地址线,拥有4GB的线性地
址空间。但是80386依旧保留了8086采用的地址分段的方式,只是增加了一个折中的方案,即只分一个段,段基址0×00000000,段长0xFFFFFFFF(4GB),这样的话整个线性空间可以看作就一个段,这就是所谓的平坦模型(Flat Mode)。
实模式对于内存段并没有访问控制,任意的程序可以修改任意地址的变量,而保护模式需要对内存段的性质和允许的操作给出定义,以实现对特定内存段的访问检测和数据保护。
考虑到各种属性和需要设置的操作,32位保护模式下对一个内存段的描述需要8个字节,其称之为段描述符(Segment Descriptor)。段描述符分为数据段描述符、指令段描述符和系统段描述符三种。
寄存器不足以存放N多个内存段的描述符集合,所以这些描述符的集合(称之为描述符表)被放置在内存里了。在很多描述符表中,最重要的就是所谓的全局描述符表(Global Descriptor Table,GDT),它为整个软硬件系统服务。
问题一:这些描述符表放置在内存哪里?
答案是没有固定的位置,可以任由程序员安排在任意合适的位置。问题二:
既然没有指定固定位置,CPU如何知道全局描述符表在哪?答案是Intel干脆设置了一个48位的专用的全局描述符表寄存器(GDTR)来保存全局描述符表的信息。
80386CPU加电的时候自动进入实模式55既然CPU加电后就一直工作在实模式下了。那怎么进入保护模式呢?说来也简单,80386CPU内部有5个32位的控制寄存器(Control Register,CR),分别是CR0到CR3,以及CR8。用来表示CPU的一些状态,其中的CR0寄存器的PE位(Protection Enable,保护模式允许位),0号位,就表示了CPU的运行状态,0为实模式,1为保护模式。通过修改这个位就可以立即改变CPU的工作模式。
CRO主要是处理器相关,比如实模式,保护模式等。
CR1系统保留没有用到。
CR2用于发生页异常时报告出错信息。当发生页异常时,处理器把引起页异常的线性地址保存在CR2中。操作系统中的页异常处理程序可以检查CR2的内容,从而查出线性地址空间中的哪一页引起本次异常。
CR3是页目录基址寄存器,保存页目录表的物理地址。
不过需要注意的是,一旦CR0寄存器的PE位被修改,CPU就立即按照保护模式去寻址了,所以这就要求我们必须在进入保护模式之前就在内存里放置好GDT,然后设置好GDTR寄存器。我们知道实模式下只有1MB的寻址空间,所以GDT就等于被限制在了这里。即便是再不乐意我们也没有办法,只得委屈就全的先安排在这里。不过进入保护模式之后我们就可以在4G的空间里设置并修改原来的GDTR了。
现在有了描述符的数组了,也有了“数组指针”(全局描述符表寄存器)了,怎么表示我们要访问哪个段呢?还记得8086时代的段寄存器吧?不过此时它们改名字了,叫段选择器。
通常只定义一个GDT,而每个进程除了存放在GDT中的段之外如果还需要创建附加的段,就可以有自己的LDT。GDT在主存中的地址和大小存放在gdtr处理器寄存器中,当前正被使用的LDT地址和大小放在LDTR处理器寄存器中。
====================================
我们之前简单的阐述了分段,事实上现代操作系统几乎不再使用分段而是绕过分段技术直接使用了分页。其实分段和分页没什么必然联系。只不过Intel从8086开始,其制造的CPU就以段地址+偏移地址的方式来访问内存。后来要兼容以前的CPU,Intel不得不一直保留着这个传统。分段可以说是Intel的CPU一直保持着的一种机制,而分页只是保护模式下的一种内存管理策略。不过想开启分页机制,CPU就必须工作在保护模式,而工作在保护模式时候可以不开启分页。所以事实上分段是必须的,而分页是可选的。
那我们如何”绕过”这个分段机制呢?不知道大家是否还记得之前我们所说的平坦模式(Flat Mode)?这就是我们”绕过”的方法。当整个虚拟地址空间是一个起始地址为0,限长为4G的”段”时,我们给出的偏移地址就在数值上等于是段机制处理之后的地址了。不过我们不是简单的对所有的段使用同样的描述符,而是给代码段和数据段分配不同的描述符。下面的示意图描述了这个抽象:
grub在载入内核的时候:
1. CS 指向基地址为0x00000000,限长为4G – 1的代码段描述符。
2. DS,SS,ES,FS 和GS 指向基地址为0x00000000,限长为4G–1的数据段描述符。
在后期实现TSS任务切换的时候还要用到全局描述符表。
最后放一张带有页目录项的图:
其中CR3是页目录基址寄存器,以该寄存器只有高20位是有效的。而低12位保留供更高级处理器使用。