2.1 内存管理概述
Intel体系结构的内存管理可分为两部分:分段和分页。
在保护模式下,分段是必须的,分页是可选的。
分段:可以为每个程序或者任务提供单独的代码、数据和栈模块,亦或装载系统的数据结构(TSS、LDT等)——保证多个进程或者任务能够在同一个处理器上运行而不互相干扰。
分段将处理器可寻址空间(线性地址)分为较小的受保护的地址空间段,当处理器上运行多个进程时,可以为每个进程分配属于它自己的段(集合)。处理器会强制规定段的边界,以确保进程间的误写和干扰。这种分段机制对段进行分类,限制对特定类型段的操作。
所有的段都在处理器的线性空间内。只有逻辑地址(远指针)才能确定一个字节在一个特定段中的位置。
逻辑地址:由段选择符(16位)和偏移量(32位)组成,段选择符是一个段的唯一标识。
线性地址:处理器线性地址空间内的32位地址,与物理地址一样是平坦的(不分段的),空间大小为4GB,线性地址空间包含了所有的段以及为系统而定义的各种系统表。
段描述符中包含了描述符表中的偏移(like GDT),该偏移指向叫做段描述符的一种数据结构。
每一个段有一个段描述符,用来描述段的大小、访问权限、优先权、类型以及段基址(第一个字节在线性地址空间的位置)等。
逻辑地址中的偏移量+段基址=这个地址在段中的字节位置
段基址+偏移量=处理器线性地址空间的线性地址
分页:实现传统的请求调页虚拟内存系统——程序的执行代码按需要映射到物理内存中;隔离多个任务。
若无分页,则线性地址空间直接映射到物理地址空间(处理器能够在地址总线上产生的地址范围)
多任务计算系统中,定义的线性地址空间>>实际物理内存空间——要虚化线性地址空间——分页机制
虚拟内存通过较小的物理内存以及一些磁盘存储空间来模拟一个很大的线性地址空间。
分页机制:每个段被分为很多页(通常为4KB),这些页在物理内存或磁盘上,操作系统会维护一个页目录and一组页表来跟踪这些页。
当一个进程试图访问线性地址空间的一个地址时,处理器通过页目录and页表将线性地址转换为物理地址。若被访问的页不在物理内存中,处理器终端这个进程(产生一个缺页异常),然后操作系统从磁盘上读取这个页到内存中。
2.2 分段机制
分段可以用来保护程序,也可以实现健壮的操作系统,用来改进内存管理的性能和可靠性。
2.2.1 Basic Flat Model(基本平坦模型——最简单的内存模型)
在this model中,操作系统和应用程序can访问一个连续的、没有分段的地址空间,最大程度地隐藏了Intel架构的分段机制。
要实现BFM,至少要建立两个段描述符:一个指向代码段,一个指向数据段。这两个段都被映射到整个线性地址空间,that means这两个段描述符都是以地址0危急值,段限长4GB(即使所访问的地址没有物理内存,处理器也不会产生“超出内存范围”异常)。
ROM(EPROM)的地址通常位于物理地址空间的高端,因为处理器从0xfffffff0处开始执行;RAM(DRAM)位于地址空间的低端,因为复位初始化后,数据段DS的初始基地址被置为0;
2.2.2 Protected Flat Model(受保护的平坦模型)
PFM与BFM类似,只是段限长被设定为实际物理内存内。如果试图访问实际物理内存范围以外的地址,会产生一个通用保护异常(#GP)——利用一点硬件的保护机制来防止一些程序的错误。
当然,也可以使这个受保护的平坦模型更加复杂以提供更多的保护。比如,为了能在分页机制中分离普通用户和超级用户的代码和数据,需要定义4个段:优先权为3(普通用户)的代码段和数据段,还有优先权为0(超级用户)的代码段和数据段。
一般来说,这些段都是互相重叠的,并且都从线性地址空间地址 0x00000000开始。
这个平坦分段模型加上一个简单的分页结构就可以在操作系统和应用程序之间起到保护作用。而且,如果为每一个进程都分配一个页结构,这样就可以在应用程序之间起到保护作用。
2.2.3 Multi-Segment Model(多段模型)
MSM充分利用分段机制,提供硬件级强制保护。每个进程(或任务)都被分配了自己的段描述符表以及自己的段。进程可以完全独自拥有这些分配到的段,也可以与其他进程共享这些段。单独的程序对段和执行环境的访问由硬件控制。
2.3 逻辑地址和线性地址的转换
2.3.1 物理地址空间
保护模式下,Intel架构提供最大4GB(2的32次方)的物理地址空间,这是处理器可以在地址总线上寻址的范围(0x00000000-0xFFFFFFFF)
2.3.2逻辑地址和线性地址转换
保护模式下,得到物理地址需要两步:逻辑地址转换机制and线性地址空间的分页机制。
逻辑地址转换为线性地址:
-
通过段选择符中的偏移量,在GDT或者LDT中定位该段的段描述符。(仅当一个新的段选择符被读入段寄存器时才执行这一步)
-
检查段描述符中的访问权限和段的地址范围以确保该段是可访问的,偏移量是在段限长范围内的。
-
将段描述符中的段基址与偏移量相加以构成线性地址。
若无分页,线性地址直接映射到物理地址;如有分页,则还需要一次地址转换。
Segment Selectors(段选择符):16位的段标识符,并不直接指向段,而是指向定义该段的段描述符。一个段选择符包含以下项目:
Index (位3~15)。选中GDT或LDT中8192个描述符中的某个描述符。处理器将索引值乘以8(段描述符的字节数),然后加上GDT或LDT的基地址(基地址在GDTR 或者LDTR寄存器中)。
TI标记 (位2)。确定使用哪一个描述符表:将这个标记置0,表示用GDT。将这个标记置1,表示用LDT。
RPL(特权级) (位0和1)。确定该选择符的特权级。特权级从0-3,0为最高特权级。
对应用程序而言,段选择符作为指针变量的一部分是可见的;但是其值有连接程序赋予或者更改,而非应用程序。
Segment Registers(段寄存器):为减少地址转换时间和代码复杂度,处理器提供6个段寄存器来保存段选择符。每个段寄存器都支持某个特定类型的内存寻址(代码、堆栈、数据等)。
6个段寄存器:代码段寄存器(CS)、数据段寄存器(DS)、堆栈段寄存器(SS)还有另外三个附加数据段寄存器(ES、FS、GS)
当某一进程要访问某个段时,这个段的段选择符必须被赋值到某一个段寄存器中。
Visible Part(可见):当段选择符被加载到VP时,处理器也通过段选择符所指向的段描述符获取这个段寄存器的不可见信息:段基址、段限长and访问权限。
Hidden Part(描述符缓存/影子寄存器)
有两种载入段寄存器的指令:1.直接载入指令:指定了相应的寄存器
2.隐含的载入指令:改变CS寄存器的内容,有时也会改变其他段寄存器的内容。
Segment Descriptors(段描述符)
Segment Descriptors段描述符有64位,是GDT或LCT中的一个数据结构,为处理器提供段基址、段大小、访问权限及状态等信息。段描述符主要由编译器、连接器、装载器或者操作系统来构造的,而非应用程序产生的。
Segment limit field(段限长字段): 指定段的大小。处理器将这两个段限长域组合成一个20位的段限长值。根据标志位G(粒度)的不同,按两种不同的方式处理段限长:
G标志位为0:该段大小可以从1字节到1M字节,段长增量单位为字节;
G标志位为1:该段大小可以从4K字节到4G字节,段长增量单位为4K字节。
Base address fields(基地址域):确定该段的第0字节在4GB线性地址空间中的位置。处理器将则3个基地址域组合在一起构成了一个32位地址值。段基址应当是16字节边界对齐的。16字节边界对齐不是必须的,但这种段边界的对齐能够使程序的性能最大化。
Type field(类型域):指明段或门的类型,确定段的范围权限和增长方向。如何解释取决于S标记。
S标志(描述符类型):确定段描述符是系统描述符(S为0)or应用描述符(代码or数据,S为1)
DPL(描述符特权级)域:指明特权级(0-3,0为最高特权级)。DPL用来控制对该段的访问。
P(段存在)标志:指明该段当前是否在内存中(1表示在,0表示不在)。当指向该段描述符的段选择符装载入段寄存器中时,若P=0,会产生一个段不存在异常(NP)
D/B(默认操作数大小/默认栈指针大小或上限)标志:根据这个段描述符指向的是一个可执行代码段,一个向下扩展的数据段还是一个堆栈段,这个标志完成不同的功能。(对32位的代码和数据段,这个标志总是被置1,16位的代码和数据段,这个标志总是被置0),包括:
D标志(可执行代码段):指明该段中的指令所涉及的有效地址值的缺省位位数和操作数的缺省位位数。若为1则缺省为32位的地址、32位or8位的操作符;若为0,缺省为16位的地址、16位or8位的操作符。
B标志(堆栈段(由SS寄存器所指向的数据段)):为隐含的栈操作(like push、pop and call)确定栈指针值的位数。若为1则使用的是32位的栈指针,该指针放在32位的ESP寄存器中;若为0,则使用的是放在16位SP寄存器中的16位的栈指针。
向下扩展的数据段:B标志确定了该段的地址上界,若为1则段地址上界为FFFFFFFFH(4GB);若为0,段地址上界为FFFFH(64KB)
2.4 段描述符的分类
当段描述符中的S标志(描述符类型)为1时,该描述符为代码段描述符或者数据段描述符。类型域的最高位(段描述符的第二个双字的第11位)将决定该描述符为数据段描述符(为0)或者代码段描述符(为1)。
对于数据段而言,描述符的类型域的低3位(位8,9,10)被解释为访问控制(A),是否可写(W),扩展方向(E)。参考表3-1对代码和数据段描述符类型域的解码描述。数据段可以是只读或者可读写的段,这取决与“是否可写”标志。
所有的数据段都是非一致的——数据段不能被更低特权级的进程访问,却可以被更高优先级的程序或进程访问,不需要使用特别的访问门。
堆栈段必须是可读写的数据段,否则将一个不可写的数据段选择符放入SS寄存器会导致通用保护异常(GP)
对于代码段:类型域的低3位为访问位(A)、可读位(R)、一般位(C)
访问位(access位):表示访问位自最后一次被操作系统清零后,该段是否被访问过。当处理器将该段的段选择符置入某个段寄存器时,就将访问位置1,且一直保持到被显式清零。该位可以用来虚拟内存管理和debug
可读位:根据该位的设置,代码段可以为“只执行”or“可执行可读”。当有常量或其他静态数据与指令代码一起在ROM时,必须使用“只执行可读”的段。要从代码段读取数据,可以通过带有CS前缀的指令或者将代码段选择符置入数据段寄存器(DS,ES,FS或GS)。保护模式下,代码段是不可写的。
进程的执行转入一个具有更高特权级的一致段可以使代码在当前特权级继续运行。除非调用了调用门或或者任务门,进程将被转入一个不同特权级的非一致段僵尸处理器产生一个“一般保护异常(#GP)”
不访问受保护的程序和某类异常处理程序的系统程序可以被载入一致的代码段。
不能被更低特权级的进程访问的程序应该被载入非一致的代码段。
注意:无论目标段是否为一致代码段,进程都不能因为call或jump而转入一个低特权级(特权值较大)的代码段执行,否则会导致一个通用保护异常(GP)
当S标志为0时,该描述符为系统描述符。
分类:一种是系统段描述符,指向系统段(LDT和TSS段)——用于描述内存段(数据段、代码段、堆栈段等);另一种是门描述符,他们自身就是“门”,或者持有指向代码段的过程的入口点的指针,或者持有TSS(任务门)的段描述符——用于描述可执行代码(如一段程序、一个过程或一个任务)。编码如下:
GDT:系统必须定义,以备所有的进程或者任务使用 LDT:可以定义一个或多个,如:可以为每个正在运行的任务定义一个LDT,也可以所有任务共享一个LDT。
GDT:不是一个段,而是线性地址空间的一个数据结构。 LDT:位于类型为LDT的系统段内。GDT必须包含一个指向LDT段的段描述符。若有多个LDT,那么每个LDT都要有一个段选择符,都要在GDT中有一个段描述符。