开门见山
虚拟内存的初衷是采用页表管理机制使得程序即使在物理内存不足时也可以运行。在内存大幅度增加的当下,你可能会问虚拟内存还有用吗?如果你只是普通用户,买根内存条完事!但作为一名软件工程师,虚拟内存管理将直接影响你开发程序的性能!
灵魂拷问
作为一名程序员,小编认为以下关于虚拟内存的知识需要搞懂:
1)什么是虚拟内存? 核心观点是什么?
2)页表机制和页面置换算法的基本原理?为什么需要多级页表?
3)哪些虚拟内存的关键点可能影响程序性能?
4)应用程序开发应该从哪些方面减少缺页异常和 TLB 不命中带来的性能影响?
5)动态内存申请和释放和虚拟内存的关系?
6)操作系统如何管理每个进程的虚拟内存?切换进程有什么影响?
一鸣惊人
虚拟内存是大多数操作系统(例如 MAC OS,Windows 和 Linux)的组成部分。要了解虚拟内存的工作方式,我们必须追溯到虚拟内存出道之前。在Windows版本1或2的日子里,如果没有安装足够的物理RAM,我们实际上无法运行许多应用程序。众所周知,系统本身正在使用一部分RAM。如果我们运行更多的应用程序,则每个应用程序还将获得其自己的RAM部分。如果我们运行太多的应用程序,那么某一时刻将耗尽RAM。届时,我们将无法打开任何其他应用程序。那时,我们不得不忍受这一点。如果没有足够的内存,则无法运行应用程序。虚拟内存的出现打破了这一僵局。它利用程序的局部性原理使得物理内存充当缓存的角色,如果出现物理内存不够时,它会将一部分不常用的内容写回磁盘上的交换区或文件,进而让当前运行的程序可以继续使用。
页表机制
在虚拟内存管理下,每个进程直接访问的是一块相互独立的连续虚拟内存,而实际的数据和程序存储在物理内存之中,这也意味着它需要建立一种从虚拟内存到物理内存的映射关系。虚拟内存和物理内存都采用页机制管理。虚拟内存的逻辑页和物理内存的页帧具有相同的大小。Linux 用户可以采用如下命令查看页面大小,页面大小是一个可配参数,当前大小为4K.
getconf PAGE_SIZE
4096
当进程需要访问物理页帧中的具体内容时,它会利用页表机制将虚拟地址映射到物理地址。这也意味着页表记录的实际是从逻辑地址到物理地址的映射。
页表和缺页
虚拟地址和物理地址的格式如下:
如果页大小为4K,则0-11表示页内偏移,12-31位表示页号。在实际访问RAM时我们需要访问页表将逻辑地址映射到物理地址。页表存储的是物理页号和标志位。其中物理页号表示物理页的偏移地址。如下图所示:
物理地址的查找逻辑如下:
1)根据逻辑页号找到页表的偏移地址。
2)如果标志位为1,则表示找到物理页号对应的物理偏移地址,转入第 3)步。否则产生缺页,进入缺页处理程序:如果此时页表项存在磁盘地址,则需要分配一个物理页并将内容从磁盘重新拷贝进物理内存,建立起映射关系(此时成为major fault,耗时较高);如果页表项为空,则直接分配物理页并建立映射关系(此时成为 minor fault)。此外,如果分配物理内存时,没有空闲页也将启动页面置换算法。待缺页处理完毕,继续转入第 3)步。
3)物理地址=页偏移地址+页内偏移地址。
至此,我们已经借助一级页表完整讲述了地址映射和缺页。值得一提的是,虚拟内存管理中,每一个进程都有一个独立的虚拟内存空间,每一个进程的页表项都常驻内存。在上面的一级页表中,每一个逻辑页号都需要在页表中有一个映射条目。对于一个32位地址空间,4KB的页面,每个页表项占用4个字节来说,每一个进程的应用程序将占用 4MB 的存储空间。如果是64位系统,这个问题将更加严重。现在有两个问题需要解决:
1)页表常驻内存,每次访问页表时都需要访问内存,这意味着当应用程序需要访问内存的实际数据时,即使是一级页表也产生了两次内存访问,这引入的性能副作用无疑是不可接受的!(需要多级页表)
2)每个进程一级页表占用了很多额外的存储空间,浪费了内存,这无疑也是不可接受的!(需要MMU,引入TLB)要解决以上两个问题,请继续关注"程序员的大厂之路"微信公众号下一篇关于多级页表和 MMU (TLB)的分享。