一、几个基本的概念
1.存储器的金字塔结构
存储器从下之上依次是磁盘/flash、DRAM(内存)、L2-cache、L1-cache、寄存器,越在上面的存储器访问速度越快,同时价格也越昂贵,每一级都可以看做是下一级的缓存,内存是磁盘的缓存,cache是内存的缓存。
2.地址空间
地址空间就是一个非负正数的有序集合,如果是连续的即线性地址空间,从硬件的角度看就是处理器所能访问的存储器空间,与地址线的位数相关,物理地址空间就是物理存储器的访问空间(按字节访问)
3.页
将物理内存和虚拟内存按页来划分,页的大小也有所不同,ARM中支持1M的大页,4k或64k的细页,x86下支持4M的大页,利用页表来表征整个地址空间,当采用二级页表时,存在一个页目录,用来索引对应页表所在的物理地址,PTE页表项占4个字节,当采用4k大小的页时,一个虚拟地址可以划分为:
31
22 21 12 11
0
| 页目录索引 |
页表索引 | 页内偏移 |
硬件页表项PTE
31
22
0
| 物理页帧号 |
物理页属性和访问控制 |
二、地址转译
地址的转译是有虚拟地址至物理地址,处理器内使用的是虚拟地址,经过MMU转译为物理地址,例如DMA使用的就是物理地址,因为它在MMU之后,转译的过程是MMU与OS配合完成的,OS负责维护页表,MMU负责翻译,要明确一点是MMU使用的是物理地址。首先根据页目录的物理地址(x86在CR3中)和页目录索引查找到页目录表项PDE,其中记录了对应页表的物理地址,如果该页表在物理内存中(页表也可以被换出到磁盘中),PDE表项的格式与硬件PTE基本一致,然后根据页表索引找到对应的页表项PTE,最后根据其中的物理页帧号和页内偏移计算得到物理地址。
具体的翻译过程用一个例子来说明:
例如:一个虚拟地址为0x50001,页目录索引为0x0,页表索引为0x50,页内偏移为0x1,首先找到PDE(windows的虚拟地址空间PDE_0的VA
= 0xc0300000),PDE物理地址 = CR3 + 0x0(页目录索引)*4,PDE表项内容为0x00700 067,查找PTE,PTE物理地址 =
0x700 + 0x50(页表索引)*4,(PTE的虚拟地址为0xc0000140),PTE表项的内容为0x00e63 047,则最终转译的物理地址 =
0xe63 000 + 0x1 = 0xe63001
三、虚拟内存的原理
如何在物理内存有限的情况下例如只有32MByte,可以使系统寻址4G的地址空间,物理内存资源是一种共享的稀缺资源,因此虚拟地址空间的页并不是都有对应的物理内存页面,将某一时刻处理器不访问的虚拟页面以页的方式保存在磁盘上,当需要读写时再将其换入至物理内存中。
四、进程虚拟地址空间
x86体系结构下,一个进程的虚拟地址空间为4G,主要包括了进程的私有地址空间和系统地址空间两部分,windows中0x0--0x7fff
ffff作为进程私有,0x8000 0000 -- 0xffff ffff为系统地址空间。
1.大页和小页
采用大页的好处是在TLB中可以缓存更大的地址空间,增加了缓存命中的概率,不好的一点是页面粗粒度造成的不安全访问,例如.data(读写)和.text(只读)都放在同一个具有读写权限的页面内,则有可能造成.text的内容误写,可能导致系统崩溃。
采用小页的好处是细粒度管理内存,更加灵活,更少的内部碎片,但同时PFN更多,管理的开销要更大一些。
因此为了安全可靠,选用小页,4K page
2.延迟计算
(1)先保留再提交内存
当一个线程申请大块的虚拟内存时,现在虚拟地址空间中保留一段范围,为一块虚拟内存建立虚拟地址描述符(VAD),当真正访问这段地址空间时,再建立PTE,也就是在物理内存中分配有效的页面。一个应用例子就是每个线程的用户栈在创建时首先会保留1MB的空间,只有在虚拟页面被访问时才会提交。在VAD中记录了地址范围的大小,以及读写权限,当建立PTE时就根据VAD中记录的信息(读写权限)来填充PTE。
(2)写时复制 copy-on-wirte
i.当两个进程共享一个写时复制的物理页面时,如果其中一个进程中的线程要对共享页面进行写操作,则产生一个内存管理错误(一个异常),内存管理器为其分配一个新的读写物理页面,并且将原始共享页面的内容复制到新的物理页面中,并将虚拟地址映射更新为新的物理页面,控制权返回线程,线程执行写操作,此时新的物理页面属于该进程私有,其他进程是看不到的,也就是说写时复制为执行写操作的进程创建了一份私有的拷贝。
ii.POSIX子系统利用写时拷贝来创建子进程的地址空间,首先父进程创建一个子进程,子进程的地址空间是共享父进程的地址空间,将地址空间标记为写时拷贝,只有子进程执行写操作时,才会拷贝相应的页面给子进程私有使用。
3.内存区对象
section
object,每一个打开的文件都有一个内存区对象指针,根据文件类型的不同(数据文件或可执行文件)指向不同的控制区域(虚拟地址空间内),控制区域包括子内存区,例如一个可执行文件的子内存区就是.data,.text,.bss等,同时这个控制区域又指向了一些原型PTE,通过这些原型PTE可映射到该内存区对象所映射的实际物理页面。
当内存区对象指向一个很大的数据文件时,线程只访问其中的一部分,就只映射该内存区中的一部分,这部分被称为内存区视图。
内存区对象的应用,映像加载器利用它加载可执行文件,缓存管理器利用它来访问缓存文件中的数据,这两者都是针对打开的文件,共享内存的应用是内存区对象与物理内存关联。
虚拟页有三种状态,已提交的,保留的和未分配的。
4.windows的四种内存保护机制
(1)系统空间的全局数据结构和内存池只有在内核模式下才可以访问,用户线程无法访问这些页面
(2)每个用户进程都有自己的私有地址空间,这部分空间是其他进程无法访问的
(3)地址转译过程中的隐式保护,一个标记为只读的页在对其进行写操作时会产生页面访问错误
(4)共享内存区对象具有标准的windows访问控制列表,对于没有权限的进程,是无法访问共享内存区的
5.进程页表
一个进程有自己的页目录,这个页目录就指向了对应的页表,进程的系统空间都是共享的,因此页表也是共享系统空间页表项,一个进程在创建时会初始化地址空间,也就是初始化页表,第一阶段就是创建系统空间的页表项,就是将页目录的PDE指向系统空间的页表,然后是打开一个可执行文件建立一个内存区对象,再映射至进程的私有空间,实质上是先保留地址空间,当进程执行时根据读写页面的需要创建PTE,进程的会话空间页表也是共享的,即指向已有的会话页表。进程的页目录和页表所在的物理页被映射至0xc0300
000和0xc000 0000
6.TLB
TLB就是缓存了虚拟页至物理页的映射,本质上就是一个cache,进程切换时会刷新TLB,但是系统空间的TLB项不会刷新,因为进程的系统空间是共享的
7.页面错误处理
页面处理的错误有两种,(1)访问了没有物理页映射的虚拟地址 (2)以错误的方式访问
8.原型PTE
首先原型PTE是一种无效PTE,目的是让所有可能需要共享页面的进程的PTE都指向原型PTE来解决页面错误,原型PTE是伴随这共享页面的内存区一起创建的,当共享内存页面是有效时,进程都直接用自己的PTE来访问。
当共享内存页面无效时,如果没有原型PTE,那如果一个进程访问该共享页面将其换入内存中,那内存管理器要将所有共享该内存页的进程的页表进行更新,告诉他们共享内存页有效了,这样做比较费力。
现在使用原型PTE,当进程访问的共享页无效时,则将该PTE指向共享页的原型PTE,如果进程A再次访问该页,共享页又被换入内存中,此时要更新原型PTE指向新的物理页,当进程B再次访问共享页时,此时因为原型PTE已经得到更新,则内存管理器更新进程B的页表,使其PTE指向新的物理页。
原型PTE的精髓还是延迟计算,就是当一个共享页从新被换入内存时,不必去更新所有曾经访问过该共享页的进程页表,而是等到进程真正访问时再去更新。
9.换页策略
(1)最近最少使用
(2)先进先出 将驻留在内存中最久的页面换出
(3)时钟算法