2.2 IA-32 Memory Management Mechanism

 

[Home]  [Top]  [Previous]  [Next]


1. Segment & Page

 
在IA-32体系上,内存地址概念分为3种:逻辑地址,线性地址和物理地址。

其中逻辑地址指的是我们熟悉的Segment:Offset模式的地址,在IA-32上,Segment通过Segment Selector来引用一个Segment Descriptor,在Segment Descriptor中定义了Segment Base Address,Segment Limit,以及用作段保护目的的Segment Attribute。在IA-32上任意给出的地址都是一个逻辑地址,即任意一个地址都是通过Segment:Offset的方式给出的。这是段内存访问模式的基本特点。所以我们在IA-32上,我们无法避免使用段模式,因为我们必须按照Segment:Offset的方式给出一个地址。

最终一个逻辑地址会通过Segment Base Address+Offset的方式转化为一个线性地址,线性地址空间是指一段连续的,不分段的,范围为0到4GB的地址空间,一个线性地址就是线性地址空间的一个绝对地址。如果没有页模式,则一个线性地址就被直接映射为物理地址,二者是相同的。

如果使用分页,则可以将一个大的线性地址空间映射到相对小的物理地址空间。其原理就是我们上一节所讨论的页表映射方式。页模式再IA-32上是可选的。

我们在上一节内容中讨论了在内存管理模式中,如果我们想实现有效的虚拟内存管理,以让多个应用程序可以有效的使用有限的物理内存,我们需要使用Paging模式。而Paging模式实现的是线性空间到物理地址空间的映射。而在IA-32上,你无法直接操作线性空间,而只能给出逻辑地址,由IA-32的段机制将其转化为线性地址。但由于绝大多数硬件平台都不支持段模式,而支持Paging模式,所以为了让我们的OS有更好的可移植性,我们需要去掉段模式而只适用Paging模式。

但令人讨厌的是,IA-32规定段模式是不可禁止的,你不可能绕过它直接给出线性空间地址,我们必须使用它。万般无奈之中,我们意识到,如果我们通过设置段,可以访问到全部4 GB线性空间,同时给出的逻辑地址中的Offset恰好等于线性空间地址,那么我们一旦装载好Segment Selector之后,任意时刻给出的地址总是等于被转化后的线性地址的话,那么其效果和直接操作线性空间没有什么不同。

办法就是创建一个基地址为0,Segment Limit为4 GB的段,这时任意给出一个offset,则等式为0+offset=linear Address,也就是说offset=linear Address,另外由于段机制规定offset < 4GB,所以offset的范围为[0h,FFFFFFFFh],恰好是线形空间范围,这一切正是我们想要的。

由于IA-32段机制规定,必须为代码段和数据段创建不同的段,所以我们必须分别为代码段和数据段分别创建一个Base Address为0,Segment Limit为4GB的段描述符,另外,由于我们的OS要让内核运行在特权级0,而用户程序运行在特权级别3,根据IA-32的段保护机制,特权级别3的程序是无法引用特权级别为0的段描述符的(因为CPL>DPL),所以我们必须分别为内核和用户程序分别创建其代码段和数据段。这就意味着我们必须创建4个段描述符——特权级别0的代码段,特权级别0的数据段,特权级别3的代码段,特权级别3的数据段。

如果我们这么定义段,段保护则失去了作用,因为这些段使用完全相同的线形空间,它们互相覆盖。你可以设想,如果不使用Paging的话,线形空间直接被影射到物理空间,则你修改任何一个段的数据,都会同时修改其它段的数据,段机制所提供的通过Base Address:Limit方式将线形空间分割,以让段与段之间完全隔离,来实现段保护的方式在这种情况下根本就不存在了。那么这是不是意味着用户可以随意修改内核数据了?不要慌,我们之所以这么定义段,正是为了实现一个纯的Paging,此时我们已经不需要任何来自于段的保护(如果它提供了对我们来讲反而可能是个负担,比如我们仍然不得不使用4个段描述符。对一个纯粹的Paging而言,段机制的任何痕迹都是多余的),而Paging机制会提供给你所有你所需要的保护。


2. Page Table

IA-32模式对应两种页大小,提供了2种页表模式。对于4 KB页,使用2级页表;而4 MB则使用1级页表。我们不讨论4 MB的页,只讨论4 KB的页表模式。

4KB页的2级页表模式与我们在上一节讨论的相同,这里就不再赘述。我们下面来看一看Page-Directory Entry和Page-Table Entry的格式。

  • Present-用来说明当前Page-Directory Entry所指向的Page Table,或当前Page-Table Entry所指向的Page是否在物理内存中,如果在,则设置为1,否则设为0。在一个线性地址到物理地址的转化过程中,如果对应的Page-Directory或Page Table Entry的Present为0,则产生一个Page-Fault异常,相应的异常处理程序将对此异常进行处理,应该将相应的Page Table或Page装入物理内存。CPU不会对这个标志进行任何操作,完全靠OS来设置或清除。

如果CPU产生一个Page-Fault异常,OS Kernel中的Page-Fault异常处理程序必须按顺序做如下处理:

  1. 如果需要的话,将引起异常的Page从磁盘中装入物理内存。
  2. 将此Page基地址的高20-bit设置到相应的Page-Table Entry或Page-Directory Entry的高20-bit。然后将此Entry的Present标志设为1。至于其它标志,比如Dirty和Access等,或许也需要被设置。
  3. Invalidate TLB中当前的Page-Table Entry。
  4. 从Page-Fault异常处理程序中返回,重新执行引起异常的地址访问指令。

  Present位是实现请求页虚拟内存算法的关键位,所有支持Paging的硬件平台都肯定支持这个位。

  • Read/Write(R/W)-用来指定一个页(PTE) 或一组页(PDE)是Read-Only的还是Read-Write的,一般将代码页设为Read-Only的,将数据页设为Read-Write的,但在进程fork的时候,也会将数据页设为Read-Only的,以实现Copy-On-Write技术。如果这个位被设置,则表示是Read-Write的,如果被清零,则表示是Read-Only的。

   Read/Write域是页保护机制的关键位之一,所有支持Paging的硬件平台都肯定支持这个位。

  • User/Supervisor(U/S)-用来指定一个页(PTE) 或一组页(PDE)的访问权限。一般将OS Kernel的数据和代码设置为Supervisor权限,将用户进程的数据和代码设置为User权限。如果这个位被设置,则表示此页(PDE),或此组页(PTE)是User权限,CPL位任何值都可以访问这些页;如果被清零,则表示此页(PDE),或此组页(PTE)是Supervisor权限,只有CPL=0,1,2时才能访问这些页,CPL=3时,访问这些页会引起Page-Fault异常。

   User/Supervisor位是页保护机制的关键位之一,所有支持Paging的硬件平台都肯定支持这个位。

  • Page-level Write Through(PWT)-用于控制对单个Page,或者单个Page Table的Write-through或write-back缓冲策略。如果PWT位被设置,则Write-through缓冲策略被使用。否则,Write-back缓冲策略被使用。如果CR0寄存器的CD(Cache-Disable)位被设置,则PWT的值被忽略,因为此时整个分页系统缓冲已经被禁止,已经谈不上什么缓冲策略了。


 


3. Enable Paging