首页 > WinDriver > WD-内存管理概述(WRK)
2015四月10

WD-内存管理概述(WRK)

[隐藏]

1.内存地址类型

1.物理地址:即内存存储器的索引,处理器操纵内存芯片时,通过地址线管脚加上电信号来读或写相应的内存单元。在Intel x86体系结构上,物理地址是一个32位或36位的无符号整数。 

2.虚拟地址(有时候称为线性地址):在32位系统上,虚拟地址空间可以达到4GB大小,也就是说,整个空间可以有2~32=4294967296个字节单元。Intel x86芯片内有专门的电路负责把一个虚拟地址转译成物理地址。 

3.逻辑地址:逻辑地址包含两部分:段(segment)和偏移(offset)。段部分指定了在整个地址空间中的一个基地址以及段空间的大小,当然还有段的一些其他属性。与寻址相关的是段的基址和大小。偏移部分指定了一个逻辑地址相对于段基址的偏移量此偏移量不能超过段的边界。因此,逻辑地址的实际地址是段基址加上偏移量。Intex86芯片也有专门的电路把逻辑地址转译成一个虚拟地址或物理地址。 


2.页式内存管理 

在页式内存管理中,虚拟地址空间是按页(page)来管理的,对应于物理内存也按页来管理,物理内存中的页面有时候称为页帧(page frame).其大小与虚拟空间中的页面相同.

在虚拟地址空间中连续的页面对应于在物理内存中的页面可以不必连续,并且,通过小心地维护好虚拟地址空间的页面与物理内存页面之间的映射关系,物理页面可以被动态地分配给特定的虚拟页面,从而只有当真正有必要的时候才把物理页面分配给虚拟页面,毕竟物理页面相对来说是稀缺资源.

1.png


注意,在一个系统中,物理地址空间只有一个,但虚拟地址空间可以有多个。每个虚拟地址空间都必须有一个映射关系,而且,虚拟地址空间中有相当一部分页面并没有对应的物理页面(在上图中标记为“不使用”的页面)。实际上,每个虚拟地址空间往往只能映射到很少一部分物理页面。反过来,每个物理页面往往只被映射到一个虚拟地址空间中。如果有一个物理页面被映射至两个或两个以上的虚拟地址空间,那么,这些地址空间将共享此页面,若在一个虚拟地址空间中改写了此页面中的数据,则在其他的虚拟地址空间中将可以看到这样的变化。 

有了页面划分的机制以后,我们可以想象,每个虚拟地址32位信息中,其中一部分位信息指定了一个物理页面,其余的位信息则指定了页内的偏移量。也就是说,虚拟地址分成了两部分:页索引+页内偏移,其结构如图所示。页索引是指该虚拟地址在映射关系中的索引编号,页内偏移则指定了该地址在页面内部的具体位置。

1.png

以Intel x86为例来介绍从虚拟内存页面到物理内存页面的映射:

首先,寻址系统必须确定页面的大小,标准的大小为4KB,即 2~12字节,所以,32 位地址值的最后12位是页内偏移,而前20位则是页索引部分,用于找到一个实际的物理页面。

因此,在这样的系统上,页面映射表是一个2~20=1048576(即1M)大小的表(表项的数目是1M)。

如果直接用一个简单的线性表来表达这一映射表的话,因为表中每一项都要表达一个物理页面地址(32位4字节),需要4MB内存(4*1048576),即使考虑到页面起始地址的后面12位总是0,那也需要2.5MB(20位即2.5字节)。

这里我们假设映射表中的每一项都指定了一个32位物理内存地址。

另一方面,在此1M大小的映射表中,有相当数量的表项其实并没有用,所以,为这部分表项分配存储空间是浪费。Intel x86 采用了分级页表的方式来管理这一映射关系。

32位虚拟地址中的页索引部分又被分成页目录索引(10位)和页表索引(10位)两部分,所以,一个32 位虚拟地址的实际构成如下图最上边的虚拟地址结构所示。

1.png

个人觉得前10位应该叫页表目录索引才对。

1.每个虚拟地址空间对应有一个页目录,其中包含2~10=1024个目录项(PDE,Page Directory Entry).

2.每一个目录项指向一张包含1024项的页表.

所以,Intel x86 处理器在解析一个虚拟地址时,首先根据最高10位在页目录中定位到一个页目录项,它指向一个页表(PDE)。然后根据接下来的10位,在页表中定位到一个页表项(PTE,Page Table Entry),此页表项指定了目标页面的物理地址。最后在此物理地址的基础上加上页内偏移,即得到最终的物理地址。

在以上二级页表结构中, CR3 寄存器包含了页目录的物理地址。

kd> rm 0x80
kd> r
cr0=80000011 cr2=00000000 cr3=00039000

页目录的大小是4096个字节,每个目录项为4个字节;同样,每个页表的大小也是4096个字节,其中每个页表项为4个字节。目录项和页表项均指向一个32位地址,但只有前20位真正指向一个物理地址,后12位用于各种标志信息,比如页面是否已被访问过、是否允许缓存等。

对于一个满的虚拟地址空间,为维护映射关系共需要1个页目录和1024个页表,所占空间为4096+1024×4096个字节,即4 KB+4 MB大小(一个页目录表有1024项,每项4字节,所以页目录表为4KB,每个页表大小也为4KB)。这比简单的线性页表多了4 KB开销,但它带来的好处是,当虚拟地址空间中实际使用的内存的比例较小时,很多页表不必在内存中构建出来,从而可以极大地节省这些页表的开销。不过,二级页表结构也要付出性能的代价,因为在解析一个虚拟地址时,需要两次查表操作。

为了避免在多级页表解析过程中多次查表而导致性能下降的问题,Intel x86处理器采用了TLB来缓存从虚拟地址到物理地址的映射关系,TLB是完全由硬件来维护的,换句话说,软件无法操纵TLB以便加入、保留或移除其中的映射项。

然而,有一种情况可以使TLB中的映射项失效,那就是当处理器切换CR3寄存器的时候,原因很简单,一旦CR3寄存器切换了,就意味着从一个虚拟地址空间切换到了另一个虚拟地址空间,因此原来那些项没有理由再保留。但有一个例外,如果映射项的 PTE的全局标志位(32 位 PTE 的低 12 位都是标志位,其中第 8 位指明了这是一个全局项还是局部项)已置上,则在CR3寄存器切换虚拟地址空间的过程中,此映射项仍然留在TLB中。此外,在Intel x86 Pentium Pro以后的处理器中,通过invlpg指令可以使单个TLB项失效。  

 

2.1.PAE

另外,Intel x86 Pentium Pro处理器还引入了一种称为物理地址扩展(PAE,Physical Address Extension)的内存映射模式,它支持36位物理地址,但虚拟地址仍然是32位。因此,在PAE模式下,系统支持64GB物理内存,其地址映射方式和页表结构也有所不同。虚拟地址的转译采用了三级页表机制,如下图所示。在页目录之前增加了一个页目录指针索引,而页目录和页表中的每一项都64位,所以,4KB(它的大小肯定要和未PAE前保持一致)大小的页目录和页表只能存放512项,正好对应于虚拟地址中的9位索引值。页目录指针表包含4项,分别指向4个页目录之一。由于页目录和页表中的每一项都变成了64位,因此它们可以描述更长的物理地址。若简单地增加4位物理地址,即从原来的20位(注意,还有12个标志位)扩展到24位,则处理器将允许系统使用64GB物理内存。Windows用26位来表达物理地址,因此可以支持2~26+12=256GB物理内存。 

1.png

这里很容易让人混淆,支持64G物理内存和虚拟地址最多4G是两个不同的概念,注意是物理内存!物理内存!物理内存!

分页机制中有个重要的概念就是物理页号,页表能够层层递进正是在页表项中记录了物理页号。物理页号可是全局的(全局唯一)。在普通模式下,只有20位用于物理页号,那么最高也就是支持2^20=1M个页,即4GB物理内存。其他的有再多的内存寻址不到啊!

PAE模式下,由于页表项中有64-12=52位(普通模式是32-12=20位),所以其可以定位更多的物理页(当然PAE只取了24位来定位物理页)。即使碍于虚拟地址宽度所限,一个进程只能访问4GB空间,但是放大了范围

比如在普通模式下,现在有8GB的物理内存,而当前系统运行8个进程,如果每个进程当前都需要1GB的物理内存,则在此模式下使行不通的,这8个进程只能平分8GB中的4GB(页表项只能记录1M的物理页(4G)),其他的只能通过不停的换页机制来实现。

而在支持PAE模式下,上面8个进程均可分到1GB物理内存(页表项是64位啊,后12位用于各种标志信息,那还有52位可以用于记录呢呢),这就是PAE带来的好处。

windbg查询参看:http://blog.csdn.net/hgy413/article/details/20778245

3.段式内存管理

3.1.段选择符

在Intel x86中,逻辑地址的段部分称为段选择符(segment selector),指定了段的索引以及要访问的特权级别。段寄存器cs、ss、ds、es、fs和gs专门用于指定一个地址的段选择符。虽然只有这六个段寄存器,但是软件可以灵活地使用它们来完成各种功能。其中有三个段寄存器有特殊的用途: 

1.cs:代码段寄存器,指向一个包含指令的段,即代码段。 

2.ss:栈段寄存器,指向一个包含当前调用栈的段,即栈段。 

3.ds:数据段寄存器,指向一个包含全局和静态数据的段,即数据段。 

kd> rm 0x8
kd> r
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000086

虽然地址寄存器和数据寄存器都是32位,但是段选择符只有16位,其格式如图所示

1.png

1.段索引指定了一个段描述符段描述符表中的编号,它有13位,这也说明了一个段描述符表只包含 2~13=8192个段.

2.表指示位说明了此段位于全局段描述符表(GDT,Global Descriptor Table)还是局部段描述符表(LDT,Local Descriptor Table)中.

3.当前特权级(CPL, Current Privilege Level),也称为请求者特权级,是一个两位的值(0~3),代表了请求者的当前特权级别。特权级是CPU的运行模式,0表示最高特权级,3表示最低特权级.

  

3.2.段描述符

每个段描述符用来定义一个段,其中包括段的起始地址、有效范围和一些属性,段描述符指定了32位基地址,以及20位段长度(即段内最大偏移)。当G位为0时,

此长度单位为字节;当G位为1时,此长度单位为4096字节。所以,段长度可达2~20×4096=4GB,即整个32位线性地址空间。描述符特权级(DPL,Descriptor Privilege Level)是允许访问此段的最低特权级,比如,DPL为0的段只有当CPL=0时才可以访问,而DPL为3的段,可由任何CPL的代码访问。类型域(共4位)指定了段的类型,包括代码段、数据段、TSS段和LDT段。 (以下三张图都表示段描述符结构,)

1.png

 

1456639943844686.gif1.jpg

全局描述符表GDT是系统全局范围内有效的一张表,它包含最多8192个段描述符,所以,一张完全的GDT表需要8192×8=64KB内存空间。CPU有一个寄存器gdtr包含了GDT 的地址。

除了GDT外,处理器另有一个LDT,它也同样最多包含8192个段描述符,对应于LDT的寄存器为ldtr,它包含了LDT的地址。现在我们可以理解Intel x86转译一个逻辑地址的过程,如图所示

1.png

  处理器在解析一个“段+偏移”的逻辑地址时,首先根据段寄存器中的表指示位确定应该使用GDT(若表指示位为0)还是LDT(表指示位为1),然后从gdtr或ldtr中得到描述符表的地址,再加上段索引部分乘以8,即得到段描述符的地址,然后根据段描述符的格式,拼出32位段基地址,最后加上CPU指令中的偏移值,得到最终的线性地址。

  现在我们可以想象一下,如何利用以上介绍的段式内存访问机制来实现操作系统中的多进程地址空间。显然,一种自然的设计思想是,系统全局共享的空间可以通过 GDT来安排和访问,比如,操作系统本身的代码和数据是系统全局可见的,各个进程的地址空间中不可避免地要包含这部分内存。此外,各个进程私有的数据和代码存放在LDT中,因而进程切换时,只需改变LDT表,即可实现进程私有地址的切换。在现代的应用程序中,每进程往往包含多个二进制模块,以及一些动态数据区;各个二进制模块中既有代码,也有数据(比如全局变量和静态变量等)。所以,如果用段机制来管理内存的话,每个模块都需要一个段,动态数据区往往需要一个或多个段(比如全局堆和局部堆,以及栈等)。对于绝大多数应用程序,8 192个段(指LDT中的段)足够使用了。对于系统全局空间,只要小心地安排好段的使用,8192个段(指GDT中的段)也能满足正常的内存分配。利用这种方法,既可以做到进程之间的空间隔离性,也可以很方便地在进程之间共享数据。 

  最后需要说明的是,段式内存管理和页式内存管理并不是对立的,它们可以组合起来在同一个系统中使用。事实上, Intel x86处理器的内存管理单元 (MMU, Memory Management Unit)结合了这两种寻址方法。例如,下图显示了一个逻辑地址被解析成虚拟地址,再进一步被解析成物理地址的全过程。

1.png

Windows采用了页式内存管理方案,在Intel x86 处理器上,Windows不使用段来管理虚拟内存,但是,Intel x86处理器在访问内存时必须要通过段描述符,这意味着Windows 将所有的段描述符都构造成了从基地址0 开始,且段的大小设置为 0x80000000、

0xc0000000 或0xffffffff ,具体取决于段的用途和系统设置。所以,Windows 系统中的代码,包括操作系统本身的代码和应用程序代码,所面对的地址空间都是线性地址空间。这种做法相当于屏蔽了处理器中的逻辑地址概念,段只被用于访问控制和内存保护。 

文章作者:hgy413
本文地址:http://hgy413.com/2948.html
版权所有 © 转载时必须以链接形式注明作者和原始出处!

本文目前尚无任何评论.

发表评论