千里之行始于足下

CSAPP阅读笔记(2)-虚存管理

Posted on By Peter Yang

进程的执行需要进程空间,如果直接使用内存的空间,那么对于多任务的操作系统而言,维护进程所分配到的内存空间就比较困难,而且对于应用程序的编写者来说,也不够方便。引入虚存的概念,相当于在这两者之间加入一层,向上层应用屏蔽其复杂性。

虚存,全称虚拟内存,顾名思义,它对应的地址是虚拟的。其与物理的内存地址存在一个映射关系。物理内存的地址空间与实际内存大小对应,虚拟内存的地址空间在32位系统中,一般是2^32=4GB。有了虚拟内存机制,使得系统可以方便地支持内存管理、缓存以及内存保护机制。

先来看一下虚拟内存地址如何转换到最终的物理地址。

虚拟内存按页组织,页小大一般是4KB。所以,对于32位的虚拟内存地址而言,低12位用作页内寻址,高20位用作页表寻址。低12位(我们称之为VPO,即Virtual Page Offset)用作页内寻址没什么问题,且无需转换,直接对应到物理内存的页内寻址。但高20位(我们称之为VPN,全称Virtual Page Number)用作页表寻址就存在问题了。20位可以表示的页表空间有2^20,每个页表地址项需要4Byte,所以如果把全部的页表加载到内存中,需要

4*2^20=4MB 的空间。考虑到实际上有很多地址空间实际上不是一直要使用到的,所以把全部页表放在内存中,有点浪费。因此,采用多级页表可以解决这一问题。多级页表把20位的页表寻址对半分,分为10位的页目录寻址和10位的页表寻址。所谓页目录,其实就是页表的页表,多了一级索引而已。其最近结构如下图所示。

如图所示,页目录表(对应图中1级页表)只需4KB的空间即可存储,其中大部分页目录项为空。每个页目录对应着一个2级页表,页表中包括2^10个页表地址。一个页表地址对应了一个页,即4KB的虚存空间。图的最右边是整个虚拟内存的空间。如图所示,进程对应的堆栈空间以及进程代码和全局数据所处的空间对应的页表一般是常驻内存的。其余的只在需要时调入。

以上是解决单级页表容量太大的问题,采用多级页表,按需调入的方法大大减少了页表的容量。在实际应用中,光这样还不行。因为查询的效率不够高。在计算机系统结构中,cache的使用随处可见。此处也采用cache技术来提高查询效率。这里的cache有个专有名词,叫做TLB,全称Translation Lookaside Buffer。TLB中存储了一些VPN到PPN的映射,即虚拟页到物理页的映射。TLB将20位的VPN分为两部分,分别是tag和index,同CPU中普通Cache采用的技术相同。这样一来,如果在TLB中查到,则可以直接构造物理地址,完成转换工作。

转换工作完成后,得到实际的物理地址,再通过多级Cache来访问实际的内容。

前面说过,虚拟内存可以提供内存保护措施。那么如何提供呢?注意图中的两级页表,其实际内容只需要20位即可表示,剩余的低12位,则可以用作标志位的设置。包括读写权限、用户特权级等,都可以设置。值得注意的是,在页级的保护上并没有执行权限的设置,这就使用程序可以运行任意地址空间上的代码,从而使得缓冲区溢出攻击可以成功。

再来看下动态内存分配。动态内存分配是在堆空间上分配内存,为程序动态性提供了良好支持。堆内的内存空间按块组织,并且要求块8Byte对齐,或者4Byte对齐。这部分主要讲述到各种各样的块分配算法,以及块回收算法,涉及到块的分裂合并以及内存碎片的问题。具体不赘述了,有需要时再查书吧。