虚拟内存系统——了解内存的工作原理
1、地址空间
在现代操作系统中,当CPU需要访问主存的时候,会先生成一个虚拟地址,这个虚拟地址经过地址翻译单元,将其翻译成物理地址,然后使用这个物理地址来访问主存。
一个带虚拟内存的系统中,CPU可以生成n位的虚拟地址空间。现代操作系统一般支持32位或64位虚拟地址空间,也就是包含最多2 ^ 32或 2 ^ 64个地址。
除了虚拟地址空间,还有一个概念是物理地址空间,对应了物理内存的M个字节。
主存中的每个字节都有一个位于虚拟地址空间的虚拟地址和一个位于物理地址空间的物理地址。
2、虚拟内存的概念
虚拟内存是一个存放在磁盘上的大型数组。数组的每个字节都有一个唯一的虚拟地址,映射为数组的索引。主存是磁盘的高速缓存。磁盘上的数据被分割成块,作为磁盘和主存之间传输数据的单元。虚拟内存分割的块被称为虚拟页。
虚拟页的状态共分三种:
1)虚拟内存系统还未分配的页,不占用磁盘空间
2)已分配的页,并且已经缓存到内存中
3)已分配的页,但还没缓存在内存中
3、地址翻译
CPU通常只生成一个虚拟地址,如果要把这个虚拟地址翻译成物理地址,就需要用到MMU(内存管理单元)中的地址翻译硬件和一个存放在内存中、称为页表的数据结构。
翻译过程:
CPU生成一个虚拟地址,0 ~ p-1位为页偏移,页的大小即2 ^ (p-1),p ~ n-1位为虚拟页号,与页表索引相对应。
在页表中找到对应的行(页表条目)后,如果有效位为1,表示页面的内存中,可以使用页表条目的物理页号,物理页的偏移量与虚拟页的偏移量相对应。物理页号和物理页偏移量拼接成物理地址。
如果有效位为0,则表示页面不在内存中,也就是缺页了。此时会调用内核的缺页异常程序,该程序需要先淘汰内存中的一个页,如果这个页被修改过,属于脏页,那么内核会将其写回磁盘,然后再淘汰它。接下来,内核从磁盘复制所需的页面到内存中,更新页表中的对应条目,随后返回。
当异常处理程序返回时,会重新启动导致缺页的指令,然后将原先那个导致缺页的虚拟地址重新进行翻译。此时,将会按照有效位为1进行后续处理。
TLB:很多系统,在MMU中加入了一个缓存页表条目的翻译后备缓冲器(TLB),这样可以快速地获取页表条目,整个地址翻译步骤都是在硬件上完成,非常快。
4、虚拟内存的内存管理
1)简化链接。操作系统为每个进程提供一个独立的页表,因此,每个进程都有一个独立的虚拟地址空间。独立的地址空间允许每个进程的内存映像都使用相同的基本格式,不用管代码和数据实际存放在哪个物理位置。这样的操作极大地简化链接器的设计和实现,允许链接器生成完全链接的可执行文件,这些可执行文件独立于最终的物理位置。
比如,linux系统上每个进程的内存格式基本类似,64位地址空间中,代码段都被放置在0x400000开始的位置,然后是数据段,高地址是栈空间,向下生长。
2)简化加载。要把可执行目标文件的代码和数据加载到一个新创建的进程中时,加载器无需从磁盘复制任何数据到内存中。实际上,linux加载器只为代码和数据段分配虚拟页,将页表条目的有效位设置为0(即未缓存到内存),再指向磁盘目标文件中对应的.text节(存储代码)和.data节(存储数据)。虚拟操作系统在CPU取指令时会自动调入代码页,在某一指令要引用某个内存位置时自动调入数据页。
下图是一个可重定位的目标文件的基本格式。其中,.text包含编译完成的机器代码,.rodata包含只读数据,.data包含已初始化的全局和静态C变量(局部变量运行时保存在栈中),.bss包含未初始化的全局和静态C变量以及所有仅被初始化为0的全局或静态变量(在目标文件中未初始化变量不占用任何磁盘空间,运行时刻,这些变量的初始值都为0),.symtab包含一张符号表(存放了程序中定义和引用的函数和全局变量的信息),.rel.text包含一个.text节中位置的列表(在链接时,如果本模块调用了外部函数,需要修改这些位置),.rel.data包含了本模块引用或定义的所有全局变量的重定位信息(如果已初始化的全局变量,它的初始化值是一个全局变量地址或者外部定义函数的地址,都需要修改)。
3)简化共享。虽然进程之间大部分地址空间都是私有的,但还是有一些代码和数据是可以共享的,比如操作系统的内核代码,标准库中的函数等。操作系统让不同进程的虚拟地址都映射到这些需要被共享的相同的物理页面上,这样可以使得这部分代码和数据在内存中只保留一份。
4)简化动态内存分配。当程序需要在堆上动态分配内存时,操作系统只需要分配所需的k个连续的虚拟内存页面,并将其映射到内存中任意位置的k个任意的物理页面,这k个页面可以分散在物理内存中。
5、小结
虚拟内存提供了三个重要能力:
1)将主存作为磁盘的缓存,主存中只保存程序活动区域。根据需要,在磁盘和主存之间来回传送数据。这种方式高效地使用了主存。
2)为每个进程提供了一致性的地址空间,简化了内存管理。
3)保护每个进程的地址空间私有化,不被其他进程破坏。
参考资料:《深入理解计算机系统》