想要了解虚拟内存技术,就必须要知道它的前世今生,我们都知道最底层的计算机程序运行于硬件上的内存中,我们称其为物理内存(主存), CPU 可以直接从物理内存中读取数据和指令。在物理内存中,每个存储单元被分配了一个地址,这个地址就是物理地址。但存在着一个问题,那就是物理地址之间很难互相隔离,当一个程序使用着当前物理地址时,另一个程序占用了这个地址会导致程序崩溃,为了解决这些问题,人们引入了虚拟内存技术。
虚拟内存技术为每一个进程分配了独立的虚拟地址空间,每个进程看来自己仿佛独享了整个内存,再通过CPU 芯片中的内存管理单元提供的映射将虚拟地址转换成物理地址,保证每个进程所使用的的物理地址互不冲突。主要有两种方式,分别是内存分段和内存分页。
内存分段
分段机制将程序根据代码、数据等分段,不同段属性不同,包括段号,段表里面保存这个段的基地址、段的界限和特权等级等,其中段内偏移量应该位于 0 和段界限之间,如果段内偏移量合法,就将段基地址加上段内偏移量得到物理内存地址。但分段机制引入了新的问题,那就是内存碎片与内存交换效率低。
(1)内存碎片:假设有8G物理内存,根据分段机制将4G分给游戏,2G分给微信,1G分给QQ,这时还有1G空闲空间,假如现在有一个2G的程序要执行,那么吧QQ关闭就有足够空间了吗?答案是不一定,因为释放QQ之后剩余的两个1G内存未必是连续的,无法承载一个2G的程序,而这些被分割的空间就称为外部内存碎片。还有一种内部的内存碎片指的是程序有部分的内存并不常使用,这也会导致内存的浪费。
(2)内存交换:其中通过内存交换解决外部内存碎片:比如把微信占用的2G暂时写到硬盘上,再读回来时就是从游戏的4G后分配2G地址了,这样就空出来一片连续的2G地址。但是硬盘的访问速度要比内存慢很多,会显得效率低下。
内存分页
为了解决内存碎片与内存交换效率低的问题,我们可以让内存交换的时需要交换写入或者从磁盘装载的数据更少,所以引入了内存分页的办法。分页是把虚拟和物理内存空间分成一段段固定尺寸的大小,称为叫页。 Linux 中每一页的大小为 4KB
。虚拟地址与物理地址之间通过页表来映射,而页是存储在内存中,由 CPU 的内存管理单元 (MMU) 负责映射转换的工作,CPU 就直接通过 MMU找出要访问的物理地址。而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后返回用户空间恢复进程的运行。
其中由于内存空间都划分为页了,也就不会再产生内存碎片,如果内存空间不够,操作系统会把其他正在运行的进程中的最近没被使用的内存页面暂时换出在磁盘上,需要的时候再换入进来,每次换出换入少数一个或者几个页,不会花太多时间,内存交换的效率就比较高。甚至在加载进程时,不用一次性全部加载到物理内存中。
而在分页中虚拟地址分为页号和页内偏移,页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址。
Linux 主要采用的就是页式内存管理。
段页式
可以将程序先分为段,再将每个段分为多个页,地址由段号、段内页号和页内位移组成。每个程序一张段表,每个段一张页表,段表中的地址是页表的起始地址,页表中的地址则为物理页号。先访问段表得到页表起始地址,再访问页表得到物理页号,最后物理页号与页内位移组合得到物理地址。这样做虽然增加了硬件成本和系统开销,但提高了内存的利用率。