虚拟内存

操作系统之虚拟内存

Posted by 袁平 on November 2, 2019

前言

虚拟内存算是操作系统中比较重要的一部分了,内容也很多,早在看操作系统相关书籍的时候就有想要总结这一部分,但是功力不够,总感觉串不起来;经过秋招这几个月对基础知识的回顾,感觉对虚拟内存这一部分有了自己的一个认识和体系,遂趁这个机会述以成文。


正文


一. 背景

在计算机硬件体系结构中,总有这样一种规律:性能越好,价格越高;为了中和性价比,在计算机硬件体系中多采用逐级缓存的结构,通过软件算法实现访问性能与价格成本的平衡,这在计算机的存储体系中体现的尤为明显。

1.1 DRAM vs SRAM

  • DRAM:动态随机访问存储器;价格低,访问速度较慢,耗电量较大,一般用于做主存。

  • SRAM:静态随机访问存储器;价格高,访问速度快,耗电量低,一般用于做高速缓存。

1.2 存储器层次结构

如上图,存储器的层次结构实际上是一个金字塔形状,从上到下,表示价格越来越低,容量越来越大;从上到下依次是:寄存器,L1,L2,L3三级高速缓存,主存,磁盘缓存,网络缓存。存储器的层次结构实际上也是一个逐级缓存的结构,其中,虚拟内存主要作用于主存与磁盘缓存之间,负责主存与磁盘之间的页面交换。


二. 虚拟内存的目的

虚拟内存有三个目的:

  • 将主存视为磁盘的高速缓存,在主存中只保留活动区域,提高主存利用率
  • 为进程提供统一的地址空间,简化内存管理
  • 保护进程地址空间不被其他进程破坏

虚拟内存如何达到这三个目的,将在后文讲解。


三. 虚拟内存的原理

3.1 基本概念

  • 页表:可以理解为位于主存中的一个大数组,由页表项组成
  • 页表项:页表的基本组成单元,页表项分为两个部分:标志位和地址位
  • 虚拟页:主存与磁之间的基本传送单元,磁盘地址空间被视为一个个的虚拟页
  • 物理页:缓存在主存中的实际单元

3.2 寻址的基本过程

因为页表其实也是很占内存空间的,所以实际中会采用多级页表和动态创建页表的方式来减小页表大小,这里为了方便了解寻址的整体过程,就以单级页表为例;另外,页表项的高速缓存这里也没有考虑,高速缓存将在下一节介绍。

主存中有一个页表,页表由页表项组成,将虚拟页映射到物理页;处理器(CPU)进行一次寻址的过程是:处理器生成一个虚拟地址,交给MMU(地址翻译器)进行地址翻译,MMU将虚拟地址转换为页表索引,然后去主存中查找对应的页表项,如果主存中缓存该虚拟页,则主存将虚拟页对应的页表项传送给MMU,之后MMU从页表项计算出物理地址送给主存,主存将物理地址对应的数据通过数据总线送给处理器;如果主存中没有缓存虚拟页对应的物理页,则触发一次缺页异常,由主存从磁盘换取所需虚拟页之后,再次执行导致缺页的指令。

如下图所示:摘自《深入理解计算机系统》

3.3 页表缓存

上文中提过,相比于 L1,L2,L3 三级高速缓存,主存的访问速度还是很慢的( DRAM 比 SRAM 要慢约十多倍,磁盘比 DRAM 要慢约十万多倍),所以如果每次进行寻址的时候,都按照上述过程,先去主存查找页表项,然后再去主存取对应的物理页,那么,即使每次页面都命中的情况下,每次寻址也需要两次的 DRAM 访问(主存访问),如果算上缺页异常与主存与磁盘的页面替换,耗费的时间将更多。

为了加速 MMU 地址翻译的过程,会将页表项缓存到 TLB(TLB 见后补充解释) 和 L1 级高速缓存中,因此上述 MMU 进行地址翻译的过程就变成了:MMU 根据虚拟地址从 TLB 中查找页表项,如果找到,则直接进行后续物理地址生成;如果 TLB 未命中,则 MMU 从 L1 级高速缓存中查找对应的页表项,如果命中,则将该页表项缓存在 TLB 中并进行下一步,否则,继续从主存中查找页表项。

TLB:TLB 是一个在 MMU 中的高速缓存,用于缓存页表项;如果 TLB 命中,那么所有的地址翻译都是在芯片上的 MMU 中完成的,因此非常快。

如下图所示:摘自《深入理解计算机系统》

3.4 地址翻译的细节

3.4.1 从 TLB 查找页表项

处理器生成的虚拟地址如下:

TLB 中每一行都保存着一个由单个页表项组成的块,用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号(VPN)中提取出来的,如果 TLB 有 T = 2^t个组,那么 TLB 索引(TLBI)是由 VPN 的 t 个最低位组成的,而 TLB 标记(TLBT)是由 VPN 中剩余的位组成的。

更多关于 TLB 查找页表项的细节可参见: https://www.cnblogs.com/alantu2018/p/9000777.html

3.4.2 从主存中查找页表项

如下图:

虚拟地址由两部分组成:虚拟页号和虚拟页偏移量;虚拟页号可以简单的视为页表索引,如:根据VPN 0 选择页表项0,根据 VPN 1 选择页表项1,依次类推。

3.4.3 从页表项生成物理地址

仍如上图:

可以看出,物理地址最终由两个部分组成:低位是虚拟地址的另一部分,即虚拟页偏移量,高位是页表项的另一部分(页表项还有一部分是标志位,上文介绍过)。

另外,根据上图,还可以看出MMU是如何根据页表项判断虚拟页是否在主存中有对应的物理页的:上文中说过,页表项分为两部分,其中一部分是一个标志位(即途中的有效位),上图也说的很清楚了,如果有效位为 0 ,说明虚拟页对应的物理页还为被缓存在主存中(缺页),否则,说明命中。

3.5 页表优化

上文提到过,页表占据的内存空间也是可观的;对于一个 32 位的地址空间,4KB 的页面和一个 4 字节的页表项,即使应用所引用的只是虚拟地址空间中很小的一部分,也总是需要一个 4MB 的页表常驻内存。为了优化页表占用的内存空间,实际中常采用多级页表。

多级页表的基本思路就是,增大单个页表项所能映射的虚拟页的大小,即进行粒度更大的映射(对于常驻内存的页表来说),如:之前一个页表项只能映射 4KB 的虚拟页,如果一个页表项能映射 4MB 的虚拟页的话,页表的大小也会相应的减小很多。

另外,多级页表还支持动态创建子级页表,即常驻内存的只有粒度最大的一级页表,二级页表等子级页表不需要常驻内存,在进行地址翻译时动态创建即可。


四. 局部性原理与内存抖动

MMU 进行地址翻译以及缺页时进行页面替换的算法在很大程度上都依赖于局部性原理。程序的局部性原理意思是,程序上次用到的数据和代码,在下次访问时很可能也会用到;因此将下次很可能会用到的数据缓存在访问速度更快的高速缓存中,将比较可观的提升程序性能。

比较常见的页面替换算法是:LRU 算法,即最近最少使用算法,以及其他更多页面置换算法可参见: https://www.cnblogs.com/dolphin0520/p/3749259.html

内存抖动的意思是,处理器进行数据访问时,不断产生缺页中断,不断进行页面置换,造成程序运行缓慢。出现内存抖动一般有两个原因:一个是页面置换算法的缺陷,差的页面置换算法通常会导致页面命中率的降低,造成频繁的缺页中断与页面置换;另一个也可能与我们自己写的程序有关。


五. 虚拟内存与进程

本文开始讲虚拟内存的目的时,提到了虚拟内存对于进程的两个目的:

  • 为进程提供统一的地址空间,简化内存管理:

操作系统为进程分配地址空间时,由于是通过虚拟内存进行虚拟页与物理页的映射,所以,对于进程而言,物理内存在地址空间上可以是不连续的(另外,这里还想提一点的是,这种不连续的映射,可以减少内存碎片与不同进程数据的共享,即通过虚拟内存将多个进程映射到同一虚拟页即可实现进程间数据共享);同时,对于每一个进程而言,其地址空间都是由 0 开始往高地址增长(因为有 MMU 进行地址翻译)

  • 保护进程地址空间不被其他进程破坏:

进程在通过虚拟内存进行数据访问时,虚拟内存可以添加一些权限校验,防止进程间数据相互访问。


六. 参考

  • 《深入理解计算机系统》