预计阅读本页时间:-
3.7.3 分段和分页结合:Intel Pentium
Pentium处理器的虚拟内存在许多方面都与MULTICS类似,其中包括既有分段机制又有分页机制。MULTICS有256K个独立的段,每个段最长可以有64K个36位字。Pentium处理器有16K个独立的段,每个段最多可以容纳10亿个32位字。这里虽然段的数目较少,但是相比之下Pentium较大的段大小特征比更多的段个数要重要得多,因为几乎没有程序需要1000个以上的段,但是有很多程序需要大段。
Pentium处理器中虚拟内存的核心是两张表,即LDT(Local Descriptor Table,局部描述符表)和GDT(Global Descriptor Table,全局描述符表)。每个程序都有自己的LDT,但是同一台计算机上的所有程序共享一个GDT。LDT描述局部于每个程序的段,包括其代码、数据、堆栈等;GDT描述系统段,包括操作系统本身。
为了访问一个段,一个Pentium程序必须把这个段的选择子(selector)装入机器的6个段寄存器的某一个中。在运行过程中,CS寄存器保存代码段的选择子,DS寄存器保存数据段的选择子,其他的段寄存器不太重要。每个选择子是一个16位数,如图3-39所示。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

选择子中的一位指出这个段是局部的还是全局的(即它是在LDT中还是在GDT中),其他的13位是LDT或GDT的表项编号。因此,这些表的长度被限制在最多容纳8K个段描述符。还有两位和保护有关,我们将在后面讨论。描述符0是禁止使用的,它可以被安全地装入一个段寄存器中用来表示这个段寄存器目前不可用,如果使用会引起一次陷阱。
在选择子被装入段寄存器时,对应的描述符被从LDT或GDT中取出装入微程序寄存器中,以便快速地访问。一个描述符由8个字节构成,包括段的基地址、大小和其他信息,如图3-40所示。

选择子的格式经过合理设计,使得根据选择子定位描述符十分方便。首先根据第2位选择LDT或GDT;随后选择子被复制进一个内部擦除寄存器中并且它的低3位被清0;最后,LDT或GDT表的地址被加到它上面,得出一个直接指向描述符的指针。例如,选择子72指向GDT的第9个表项,它位于地址GDT+72。
现在让我们跟踪一个描述地址的(选择子,偏移量)二元组被转换为物理地址的过程。微程序知道我们具体要使用哪个段寄存器后,它就能从内部寄存器中找到对应于这个选择子的完整的描述符。如果段不存在(选择子为0)或已被换出,则会发生一次陷阱。
硬件随后根据Limit(段长度)域检查偏移量是否超出了段的结尾,如果是,也发生一次陷阱。从逻辑上来说,在描述符中应该简单地有一个32位的域给出段的大小,但实际上剩余20位可以使用,因此采用了一种不同的方案。如果Gbit(Granularity)域是0,则是精确到字节的段长度,最大1MB;如果是1,Limit域以页面替代字节作为单元给出段的大小。Pentium处理器的页面大小是固定的4KB,因此20位足以描述最大232 字节的段。
假设段在内存中并且偏移量也在范围内,Pentium处理器接着把描述符中32位的基址和偏移量相加形成线性地址(linear address),如图3-41所示。为了和只有24位基址的286兼容,基址被分为3片分布在描述符的各个位置。实际上,基址允许每个段的起始地址位于32位线性地址空间内的任何位置。

如果禁止分页(通过全局控制寄存器中的一位),线性地址就被解释为物理地址并被送往存储器用于读写操作。因此在禁止分页时,我们就得到了一个纯的分段方案。各个段的基址在它的描述符中。另外,段之间允许互相覆盖,这可能是因为验证所有的段都互不重叠太麻烦太费时间的缘故。
另一方面,如果允许分页,线性地址将通过页表映射到物理地址,很像我们前面讲过的例子。这里惟一真正复杂的是在32位虚拟地址和4KB页的情况下,一个段可能包含多达100万个页面,因此使用了一种两级映射,以便在段较小时减小页表大小。
每个运行程序都有一个由1024个32位表项组成的页目录(page directory)。它通过一个全局寄存器来定位。这个目录中的每个目录项都指向一个也包含1024个32位表项的页表,页表项指向页框,这个方案如图3-42所示。

在图3-42a中我们看到线性地址被分为三个域:目录、页面和偏移量。目录域被作为索引在页目录中找到指向正确的页表的指针,随后页面域被用作索引在页表中找到页框的物理地址,最后,偏移量被加到页框的地址上得到需要的字节或字的物理地址。
每个页表项是32位,其中20位是页框号。其余的位包含了由硬件设置供操作系统使用的访问位和“脏”位、保护位和一些其他有用的位。
每个页表有描述1024个4KB页框的表项,因此一个页表可以处理4MB的内存。一个小于4MB的段的页目录中将只有一个表项,这个表项指向一个惟一的页表。通过这种方法,长度短的段的开销只是两个页面,而不是一级页表时的100万个页面。
为了避免重复的内存访问,Pentium处理器和MULTICS一样,也有一个小的TLB把最近使用过的“目录-页面”二元组映射为页框的物理地址。只有在当前组合不在TLB中时,图3-42所示的机制才被真正执行并更新TLB。只要TLB的缺失率很低,则性能就不错。
还有一点值得注意,如果某些应用程序不需要分段,而是需要一个单独的、分页的32位地址空间,这样的模式是可以做到的。这时,所有的段寄存器可以用同一个选择子设置,其描述符中基址设为0,段长度被设置为最大。指令偏移量会是线性地址,只使用了一个地址空间——效果上就是正常的分页。事实上,所有当前的Pentium操作系统都是这样工作的。OS/2是惟一一个使用Intel MMU体系结构所有功能的操作系统。
不管怎么说,我们不得不称赞Pentium处理器的设计者,因为他们面对的是互相冲突的目标,实现纯的分页、纯的分段和段页式管理,同时还要与286兼容,而他们高效地实现了所有的目标,最终的设计非常简洁。
尽管我们已经简单地讨论了Pentium处理器虚拟内存的全部体系机制,关于保护我们还是值得再说几句的,因为它和虚拟内存联系很紧密。和虚拟内存一样,Pentium处理器的保护系统与MULTICS很类似。它支持4个保护级,0级权限最高,3级最低,如图3-43所示。在任何时刻,运行程序都处在由PSW中的两位域所指出的某个保护级上,系统中的每个段也有一个级别。

当程序只使用与它同级的段时,一切都会很正常。对更高级别数据的存取是允许的,但是对更低级别的数据的存取是非法的并会引起陷阱。调用不同级别(更高或更低)的过程是允许的,但是要通过一种被严格控制的方式来进行。为执行越级调用,CALL指令必须包含一个选择子而不单单是一个地址。选择子指向一个称为调用门(call gate)的描述符,由它给出被调用过程的地址。因此,要跳转到任何一个不同级别的代码段的中间都是不可能的,只有正式指定的入口点可以使用。保护级和调用门的概念来自MULTICS,在那里它们被称为保护环(protection ring)。
这个机制的一种典型的应用如图3-43所示。在0级是操作系统内核,处理I/O、存储管理和其他关键的操作。在1级是系统调用处理程序,用户程序可以通过调用这里的过程执行系统调用,但是只有一些特定的和受保护的过程可以被调用。在2级是库过程,它可能是由很多正在运行的程序共享的。用户程序可以调用这些过程,读取它们的数据,但是不能修改它们。最后,运行在3级上的用户程序受到的保护最少。
陷阱和中断使用了一种和调用门类似的机制。它们访问的也是描述符而不是绝对地址,而且这些描述符指向将被执行的特定的过程。图3-40中的Type域用于区别代码段、数据段和各种类型的门。