虚拟内存细节简介
虚拟内存总览
虚拟内存是一种计算机系统内存管理技术,看到很多博客或者网站将虚拟内存单纯的理解为是在物理内存不够用时,用磁盘对物理内存进行扩充,甚至我之前也是这样理解的,这样理解是很不全面,甚至可以说是本末倒置的。所以虚拟内存到底在做什么,怎么做?虚拟内存的机制是比较复杂的,总体来说,操作系统和CPU一起,通过CPU的内存管理单元(MMU),共同维护虚拟地址这个概念,其中MMU通过页表等工具,维持虚拟地址空间与物理地址空间的映射,将虚拟内存地址转换为实际的物理地址。
虚拟内存历史
虚拟内存概念要追溯于上世纪60年代至70年代,那时候内存价格十分昂贵,虚拟内存机制使得操作系统可以减少对内存的使用。这种巨大的节约使得虚拟内存机制逐渐推广到几乎所有的操作系统中,目前仍有一些操作系统出于安全或者其他业务考量没有引入虚拟内存机制,比如有些嵌入式系统需要极低的内存延迟,或者有些嵌入式系统由于空间太小,没法安装一些硬件进行地址翻译。虚拟内存机制的引入不光节约了内存成本,同样增强了进程的安全性和可靠性,(因为每个进程由自己独立的地址空间,依靠MMU翻译成真实物理内存,减少了各个进程之间对内存资源抢夺的无序性,也就减少了很多内存地址错误,比如非法访问等)
虚拟内存的软硬支持
首先要明确一个概念,虚拟内存的全部数量是收到磁盘交换空间大小限制的。从概念上讲,虚拟内存是存放在磁盘上N个连续字节大小单元的数组,虚拟内存管理系统将其分割为一块一块的虚拟页,同样的事情发生在物理内存上,被分割后的块状内存叫物理页也叫页帧。
在七层存储模型里面,处于上层的可以看做是下一级的缓存,所以也可以把物理内存理解为虚拟内存在磁盘上数据的缓存。那计算机怎么判断虚拟内存的内容已经被缓存到物理内存了呢?答案是通过页表这种数据结构,操作系统每次想要访问一个虚拟地址,都要通过页表映射到一个真实的物理空间上,如果不存在这种映射关系,那就惨了,要从磁盘swap区中取出来,更改页表状态,再放进物理内存中去,cpu访问磁盘的速度要比访问内存的速度慢十万倍,需要很多个时钟周期。
上图是CSAPP中物理内存和虚拟内存以及页表的一种关系描述,页表中有效位为1代表缓存命中,0代表不命中,即虚拟内存中的数据没有被缓存到物理内存中,这种不命中也叫作缺页。
使用虚拟内存的几点优势:
-
简化链接,在链接程序时,需要对某些变量和程序进行预先分配内存地址,对于64位地址空间,代码段总是从虚拟地址0x400000开始,数据段跟在代码段之后,最终链接生成的可执行文件是完全独立于物理内存中代码和数据的最终位置的。
-
简化加载,要把目标文件.text和.data节加载到一个新创建的进程中,加载器从不从磁盘中复制任何数据到内存,虚拟内存系统可以根据页表找到所需数据在物理内存中的地址。
-
简化共享,虽然每个进程有自己的私有代码,数据,此部分内容不共享,当其他进程访问到这部分内存时,就极有可能会触发段错误(segmentation fault),我在当时做硕士毕业论文时,用了GNU的gsl库,就经常出现这种错误,最后也没找出来他们的bug,用了一种巧妙的方式解决了。回归主题,简化共享说的是,共享库内容所在的物理地址,所有进程都有权访问,所以,在使用共享库时,共享库所在的物理空间会通过页表被映射到过个虚拟内存上面。
-
简化内存分配,在c中使用malloc函数时,会在虚拟地址空间上创建你想要的大小N个字节的空间,但是在实际的物理空间中,他们会被映射到N个任意位置的物理页面。所以在使用malloc函数时,实际上需要管理两套内存,一套是虚拟内存,一套是物理内存,还好现在的计算机有足够健全的机制完成这一复杂的事情。
地址翻译
以上面这一套机制为基础,cpu可以将地址翻译做的更快,如何做的更快呢?一般来说,想要更快,就需要更快的存储介质,比如将页表条目放到cpu的高速缓存中,在进行查表翻译地址前,先去高速缓存中看看有没有这个地址的信息,相当于你要查一个电话号码,你想打114查询,但是打114查之前,你先在自己的小本子上面找一下有没有。这一部分内容还是很复杂的,只是讲了一下最简单的原理,有兴趣的朋友可以自己去CSAPP或者其他计算机组成原理教材中找到相应答案。
多级页表
假设我们有一个32位的地址空间,想象一下这得有多少地址,维护这么多地址需要多么大的页表?为了方便读取,页表是放在内存中的,内存空间还是很宝贵的,虚拟地址太多,用一张大页表去维护很不划算,所以可以采用多级页表,用页表指向一张页表,被指向的页表再指向虚拟地址。
这样的话,如果第一个页表中的第一个页表条目是空的,那么也就不用检查二级页表了;其次,内存空间很宝贵,cpu只把一级页表放入内存中,由于虚拟内存中大部分空间都是闲置的,只有少部分虚拟地址是总被使用的,指向这部分地址的二级页表才会被缓存到物理内存中,其他的都在磁盘上。
其他内容
其他内容还应该包括,malloc,free原理,内存碎片如何处理,C如何进行保守形式的垃圾回收,以及其他语言的垃圾回收机制等,但是这是一篇定位不太深入的博客,就不讲了。
有一些细心的读者会想,在32位linux系统中,一个进程有着4G的虚拟地址空间,假如多运行几个进程,那磁盘空间不就被占满了吗?
答:虚拟内存可用大小是磁盘的可交换区大小决定的,如果这部分空间满了,是创建不了虚拟内存的,实际上你可以把虚拟内存理解为一套完整的内存体系,其大小等于物理内存与磁盘swap区空间之和。
32位架构上,进程空间是4G,64位架构上,进程空间大到磁盘都装不下,所以虽然说是有4G的虚拟内存,但是实际并不会占用很多磁盘空间。在stackoverflow上有人说,
In *nix systems I have seen that it is recommended to keep the size of swap partition to be double of the size of the RAM in the system
交换区建议设置为物理内存的两倍大小。