内存管理
80386将逻辑地址(logical address,即对程序员可见的地址)转化为物理地址(physical address,即物理内存中的实际地址)分两步走:
1、段翻译(segment translation):将一个逻辑地址(包括一个段选择器和一个段内偏移)转化为线性地址
2、页翻译(page translation):这是将一个线性地址转化为物理地址,这一步是可选的,由系统软件设计者决定
翻译的过程对程序员是透明的,图5-1高度抽象地描绘了这两步翻译:
一、段翻译(segment translation)
图5-2展示了将逻辑地址转化为线性地址更多的细节
为了实现这个翻译过程,处理器使用了如下数据结构:
描述符(Descriptors)
描述表(Descriptor tables)
选择器(Selectors )
段寄存器(Segment Registers )
1、描述符
描述符为处理器提供将逻辑地址转化为线性地址所需要的数据。描述符由编译器(compilers)、链接器(linkers)、加载器(loaders)或者是操作系统创建,而非由程序员负责。图5-3给出了两种类别描述符的格式,所有类型的段描述符都使用这两种格式之一。
描述符包括如下域:
基地址(BASE):定义在4GB段线性地址空间中的位置,处理器将三个基地址片段连接成为一个32位数值。
段长度(LIMIT):定义段的大小。当处理器连接两个段长度片段就得到一个20位数值。根据粒度位(granularity bit)的不同,处理器以两种方式解释段的长度:
a、以字节为单位定义一个最大为1MB的段长度(注:段长度字段共有20位,)
b、以4KB为单位定义一个最大为4GB的段长度。载入时将段长度左移12位并加入低12比特位
粒度位(Granularity bit):确定段长度(LIMIT)的解释方式。复位的时候以字节为单位解释段长度,置位的时候以4KB为单位解释段长度。
类型(TYPE):区分不同种类的描述符。
描述符特权级(DPL,Descriptor Privilege Level):在保护模式中使用。
段表示位(Segment-Present bit):当这个位为0时,描述项在地址翻译中无定义;当描述符对应的选择器载入段寄存器时处理器会产生一次异常(注:即表示该描述符所指向的那一段内容不在内存中,也就是说,在磁盘的某个地方,此时针对异常响应的服务程序便可从磁盘交换区将这一段内容读入内存的某个地方,并据此设置描述符中的基地址)。图5-4给出了当表示位被置0时描述符的格式。操作系统此时可以使用任意一个标志为可用(AVAILABLE)的区域。操作系统根据下面任一种情况将表示位进行复位以执行基于段的虚拟内存:
a、通过段指向的线性空间未被分页机制映射
b、段不在内存中
访问位(Accessed bit):当段已被访问处理器就将这个位进行置位;即描述符对应的选择器被载入一段寄存器中,或者被一个选择器测试指令使用。操作系统实现虚拟内存的过程中,可以通过周期性地检测与复位在段级别对段使用频率进行监听。
创建与维护描述符是系统软件的责任,可视常常需要编译器、程序加载器、系统创建器(system builders)与等级系统(rating system)的协作。
2、描述表
段描述符存储在两种描述表中:
a、全局描述表(GDT,global descriptor table)
b、局部描述表(LDT,local descriptor table)
如图5-5所示,一个描述表实际上只是一个存储描述符的8字节单位的内存数组。一个描述表的长度不定并且最多可容纳8192()个描述符。要注意的是,全局描述表(GDT)的第一个单元(下标为0)并没有被处理器使用。
处理器通过全局描述表寄存器(GDTR)与局部描述表寄存器(LDTR)来确定全局描述表(GDT)与当前局部描述表(LDT)在内存中的位置。这些寄存器在线性地址空间中存储表的基地址并且存储段长度。通过指令LGDT与SGDT来访问全局描述表寄存器(GDTR),通过指令LLDT与SLDT来访问局部描述表寄存器。
3、选择器
逻辑地址的选择器部分通过指定一个描述表并通过其定位一个描述符来识别当前描述符。选择器对于程序员是可见的,其存在与一个指针变量的一个域中,但是选择器的值常常由链接器或连接加载器指定(确定)。图5-6为选择器的格式。
索引(Index):在描述表的8192个描述符中指定一个位置。处理器通过简单地将这个索引值乘以8(描述符的长度),并将其与描述表基地址相加获得的结果来访问表中相应的段描述符。
表指示器(Table Indicator):指定描述符相关的描述表。0表明是全局描述表(GDT),1表明是当前的局部描述表(LDT)。
要求特权级(Requested Privilege Level):在保护模式中使用。
由于全局描述表(GDT)的第一个单元是不被处理器使用的, 一个拥有索引与指示器值均为0的情况(即指向GDT第一个单元的选择器)可以视为一个空选择器。当一个段寄存器(除了CS和SS以外)通过一个空选择器加载时,处理器不会产生异常。只有当段寄存器用来访问主存的时候才会产生异常。这个性质在初始化未被使用的段寄存器来使偶然引用陷入时很有用。
4、段寄存器
80386在段寄存器中存储描述符中的信息,借此来防止每次访存的时候都查找描述表。
如图5-7所示,每个段寄存器都有一个“可见”部分和一个“不可见”部分。通过程序操纵这些段地址寄存使得它们像简单的16位寄存器一样。不可见的部分是由处理器操纵的。
通过普通程序指令就能载入这些寄存器。这些指令分为两类:
a、直接载入指令;如MOV、POP、LDS、LSS、LGS和LFS。这些指令显式地指定段寄存器。
b、隐式载入指令;如CALL和JMP。这些指令隐式地指定CS寄存器,并且将新值载入其中。
使用这些指令,程序就能以一个16位选择器的方式载入段寄存器的可见部分。处理器从描述表中自动获得基地址(base address)、长度(limit)、类型(type)和其他信息,并且将它们装入段寄存器中的可见部分。
由于大部分涉及段中数据的指令的选择器已经被载入段寄存器,处理器就能通过将由指令提供的相关偏移和段的基地址相加而不添加额外开销。
二、页面翻译(Page Translation)
在地址转换的第二阶段,80386将一个线性地址转为物理地址。这个地址转换的过程实现了面向页面的虚拟存储系统(page-oriented virtual-memory system)和页面级保护(page-level protection)的基本特性。
页面翻译这一步是可选的。 只有在PG位被置位的时候页面翻译才有效。这个位一般是软件初始化的时候被操作系统置位的。PG位在操作系统要实现多种虚拟8086任务(multiple virtual 8086 tasks)、面向页面的保护(page-oriented protection)或者面向页面虚拟内存(page-oriented virtual memory)的时候可能被置位。
1、页框(Page Frame)
一个页框是物理内存中地址连续的4比特单元。页面始于字节边界并按照大小分配。
2、线性地址(Linear Address)
线性地址通过指定一个页表、表中的页和页中偏移间接地映射一个物理地址。图5-8是一个线性地址的格式。
图5-9展示了处理器如何通过查询二级页表将一个线性地址的DIR、PAGE和OFFSET域转化为物理地址。地址机制将DIR域作为页目录(page directory)的索引,将PAGE域作为页目录中确定的页表(page table)的索引,而OFFSET得出由页表确定的页内字节的地址。
3、页表(Page Tables)
页表只是一个32位页说明符数组。页表本身也是一个页,因此它包含了4K内存或者1K的32位项。
通过两级的表来确定内存页的地址。高一级的是一个页目录。一个页目录定位第二级中最多1K的页表。一个第二级中的页表定位最多1K的页。所有的页表由一个页目录定位,故而可以定位1M的页()。因为每个页的大小为4K()字节,一个页目录就可以覆盖整个80386地址空间()。
4、页表项(Page-Table Entries)
每级页表中的项都有同样的格式,如图5-10所示。
a、页框地址(Page Frame Address)
页框地址指定一个页的物理起始地址。由于页是以4K为边界分配,所以低12位总是置0。在一个页目录中,页框地址是一个页表的地址。在一个二级页表中,页框地址是包含期望的内存操作对象的页框地址。
b、表示位(Present Bit)
表示位指定了一个页表项是否可以用于地址翻译。P=1表明这个项可用。
在任一级中P=0表示页表项不能用于地址翻译,而这个项的其他部分可以供软件使用;项中其余的位都不会被硬件检测。图5-11展示了P=0时的页表项格式。
当任一级页表项中的P=0,此时尝试使用页表项进行地址翻译,处理器都会发出一个页面异常信号。在支持页式虚拟内存的软件系统中,面对“页不存在”(page-not-present)异常,会将需要的页从物理内存中载入。导致异常的指令将会被重新执行。第九章将会涉及更多异常处理的信息。
注意到对于页目录来说没有表示位。页目录将会在相关任务被挂起的时候转为“不表示”(not-present)状态,但是操作系统必须保证由TSS(注:TSS,Task State Segment,任务状态段)中的CR3(注:CS3,Control Register number 3,控制寄存器3)像指向的页目录在任务被调度(dispatch)前在物理内存中。关于TSS与任务调度请见第七章中相关说明。
c、访问位(Access Bit)与脏位(Dirty Bit)
这些位提供了各级页表中的页使用的数据。通过页目录项中脏位产生的异常,这些位被硬件设置;同时处理器不对这些位中的任何一个进行复位。
处理器在一个页面的读或写操作之前对各级相关的访问位进行置1操作。
处理器会在一个二级页表项指定的地址写操作之前对脏位进行置1操作。在目录项中脏位是未定义的。
在一个支持页式虚拟存储的操作系统中利用这些位在物理内存请求超过可用内存量之前确定哪些页要从物理内存中移除。操作系统负责测试与清除这些位。
第十一章中将说明在一个多处理器系统中80386如何协调访问位与脏位的更新。
d、读/写(Read/Write)与用户/管理者(User/Supervisor)位
这些位不是用来进行地址翻译的,而是在处理器同时进行地址翻译时用来进行页级别保护的。第六章中将对保护进行详细论述。
5、页面翻译缓存(Page Translation Cache)
为了最高效地进行地址翻译,处理器将最常使用的页表数据存储在一个片上缓存(on-chip cache)中。只有当必要的页信息不在缓存中的时候才需要引用各级的页表。
页翻译缓存的存在对于应用程序编写者来说是透明的,可是对于系统程序员是可见的;操作系统程序员要在任一页表改变的时刻对缓存进行刷新。页翻译缓存可以用下面任一方法进行刷新。
a、通过MOV指令重新载入CR3;例如:
MOV CR3 EAX
b、将一个任务转到一个与当前CR3像不同的TSS中(更多关于任务转换的内容见第七章)。
三、段与页翻译结合
图5-12结合了图5-2与图5-9总结了在启动分页的情况下将逻辑地址转为物理地址的两个过程。通过各自阶段中适当地选择选项与参数,内存管理软件能实现几种不同的内存管理风格。
1、“平坦(flat)”结构
当80386被设计成为无段结构执行软件时,关闭80386的段特性被视为行之有效的。80386并没有禁用段的模式,但是一开始的时候为描述符载入包含整个32位线性地址空间且带有选择器的段寄存器可以实现同样的效果。一旦载入,段寄存器就不需要改变了。80386指令中使用的32位偏移足以定位整个线性地址空间。
2、跨越几个页的段(Segments Spanning Several Pages)
80386的体系结构允许段比一个页的大小(4KB)更大或者更小。例如考虑一个用来定位与保护跨越132KB的大数据结构。在一个支持分页虚拟内存的软件系统中,并不需要整个结构同时存在于物理地址。这个结构被分为33个页面,而并不需要任何数量的页。应用程序编写者并不需要知道虚拟地址子系统如何将结构体分页。
3、跨越几个段的页(Pages Spanning Several Segments)
同时,段也可能比页小。例如考虑像一个信号量 这样的小数据结构。由于段的保护与共享,为每个信号量单独建立一个段进行存储会很有用,但是为每个分配一个页是低效的。因此将许多相关的段放在一页中会很有用。
4、非对齐页与段边界(Non-Aligned Page and Segment Boundaries)
80386体系结构并不强制段与页边界有任何的相关性。一个页同时包含一个段的结尾与另一个段的开头是完全允许的。而且,一个段也允许包含一个页的结尾与另一个页的开头。
5、对齐页与段边界(Aligned Page and Segment Boundaries)
如果强制产生一些页与段边界的相关性,内存管理软件就能变得更简单。例如,如果段只以页为单位进行分配,段与页的分配逻辑就能组合起来。没有特意为使用页设计逻辑。
6、每个段的页表(Page-Table per Segment)
如图5-13所示,空间管理软件的一个更简单的空间管理方法实在段描述符与页目录项中建立一对一的映射。每个段描述符低22位为0时是一个基地址;换句话说,基地址是由页表的第一项定位的。一个段的大小可以是1到4兆中的任意值。根据大小值,段被存储于1到1K个页框中。一个任务因此被限制使用1K个段(对很多程序都有效的数字),每个段最多包含4MB空间。描述符、相应的页目录项和相应的页表可以同时分配与释放。
PS:本文翻译至http://pdos.csail.mit.edu/6.828/2005/readings/i386/toc.htm第五章节,如有表述不清处请参照原文,本文仅供参考。
2010年6月03日 04:04
你这矬男,不开全文RSS输出
2010年6月17日 03:27
那啥,你翻译一下 What Every Programmer Should Know about Memory...
2010年7月07日 08:38
囧,那可是一百多页的量,你还不如直接让我去翻Intel的Manual…………
2023年4月23日 18:52
crediblebh