2011年(17)
分类: Delphi
2011-09-28 14:36:46
之前在《寄存器与分页分段的解决方案》里我其实是在想总结分段和分页的目的、为什么要进行分段和分页,以及和它们相关的寄存器。尽管两者都能够划分进程的物理地址空间,我们在实模式或者8086微处理器中也可以之间将段寄存器内容左移四位加上偏移地址得到物理地址,但是大家还是倾向于找到它们各自侧重的应用背景。从上篇到现在,终于感觉又有了点值得总结的东西,记录在此吧。
这里我重点讨论保护模式下的分段吧,毕竟实模式下段寄存器cs/ecs、ds/eds、ss/ess直接存贮基地址,ip或eip存储16位或者32位偏移地址,这一句话就说完了,实在不是重点。
段选择符在保护模式下,段寄存器并不存储基地址而是存储了一种叫做段选择符的结构。
图1 段选择符结构
RPL 段选择符的最低两位标示当前的权限,这个权限对cs寄存器有用。
T1 决定段描述符是在gdt中还是在ldt中。
Index 决定描述符在gdt或者ldt中的相对位置。
段描述符和描述符表真正描述段的特征的是这样一个八个字符(64位)大小的段描述符。
图2 段描述符结构
在Linux里会使用一个结构来定义描述符,其中最重要的是标示段首字节线性地址的base字段和标示最后一个内存单元偏移量的limit字段。
而全局描述符表gdt和局部描述符表ldt则是由这样一个描述符结构组成的列表。前者每cpu一个放在gdtr寄存器中,后者一般每进程一个,当前使用的ldt放在ldtr中。段选择符的index字段记录了其描述符在描述符表中的位置,这样我们就可以获取到真正的“基地址”也就是段选择符中指定的段描述符的base字段(这句话有点绕)。
段描述符和非编程寄存器如果每次生成线性地址的时候都从段选择符找到描述符表,定位到描述符在获取描述符基地址,那么效率显然会打些折扣,为了提高快速生成的速度,微处理器使用了非编程寄存器,这些非编程寄存器将在我们指定段选择符给段寄存器的时候自动将相关联的段描述符填充进来,这样我们只要不改变段寄存器,就可以快速的得到相应的段描述符。
段描述符地址的计算这里还是举一个《深入理解Linux内核》第一章中出现的例子,可怜我为了理解这个例子颇费了一些周章,因此单独拿来说说吧。
段描述符的大小是8个字节,因此描述符表的地址加上段选择符中的索引值乘以8就是最终的段描述符地址。
比如如果gdt在0x00020000且段选择符所指定的索引号为2,那么相应的段描述符地址是0x00020000+(2*8)。
我当时之所以理解的痛苦,主要是没想通为什么一个描述符占用的空间是8个字符64位,为什么将index乘以8而不是64就可以表示在描述符表里的相对位置了? 原因其实非常简单,一个十六进制的数正好比二进制数是8倍,而每个字节也正好是八位,这里正好可以将段描述符地址通过十六进制的形式进行换算。
参考书籍
《深入理解Linux内核》第三版