公众号【codeoffer】精选后端面试知识,欢迎关注
目录
一、 虚拟地址空间
二、分段与分页
三、页表
四、总结
虚拟内存是什么?
这是一个面试中经常被提到的问题,大多数人可能只记住了内存地址映射和缺页置换,但你真的搞懂了为什么会有虚拟内存了吗,它存在的作用是什么呢?
要想搞懂虚拟内存,我们先从头来回顾一下虚拟地址和分段分页的知识吧。
在早期的计算机中,程序是直接运行在物理内存上的,那个时候的计算机和程序内存都很小。程序运行时会把其全部加载到内存,只要程序所需的内存不超过计算机剩余内存就不会出现问题。
但由于程序是可以直接访问物理内存的,这也带来了内存数据的不安全性,轻则程序挂掉,重则操作系统崩溃。
所以,我们希望程序间的内存数据是安全的互不影响的。同时计算机程序直接运行在物理内存上也导致了内存使用率较低,程序运行内存地址不确定,不同的运行顺序甚至会出错。此时在程序的执行过程中,已经存在着大量在物理内存和硬盘之间的数据交换过程。
基于以上问题,那我们可以是不是考虑在物理内存之上增加一个中间层,让程序通过虚拟地址去间接的访问物理内存呢。通过虚拟内存,每个进程好像都可以独占内存一样,每个进程看到的内存都是一致的,这称为虚拟地址空间。
(这种思想在现在也用的很广泛,例如很多优秀的中间层:Nginx、Redis等等)
这样只要系统处理好虚拟地址到物理地址的映射关系,就可以保证不同的程序访问不同的内存区域,就可以达到物理内存地址隔离的效果,进而保证数据的安全性。
进程是操作系统资源分配的最小单元。操作系统分配给进程的内存空间中包含五种段:数据段、代码段、BSS、堆、栈。
数据段:存放程序中的静态变量和已初始化且不为零的全局变量。
代码段:存放可执行文件的操作指令,代码段是只读的,不可进行写操作。这部分的区域在运行前已知其大小。
BSS段( Block Started By Symbol):存放未初始化的全局变量,在变量使用前由运行时初始化为零。
堆:存放进程运行中被动态分配的内存,其大小不固定。
栈:存放程序中的临时的局部变量和函数的参数值。
那么分段的技术可以解决什么问题呢?
假设程序A的虚拟地址空间是0x00000000~0x00000099,映射到的物理地址空间是0x00000600~0x00000699,程序B的虚拟地址空间是0x00000100~0x00000199,映射到的物理地址空间是0x00000300~0x00000399。
假设你手残,在程序A中操作了地址0x00000150,但是此时的地址0x00000150是虚拟的,而虚拟化的操作是在操作系统的掌控中的,所以,操作系统有能力判断,这个虚拟地址0x00000150是有问题的,然后阻止后续的操作。所以,这里体现出了隔离性。(另一种体现隔离性的方式就是,操作同一个虚拟地址,实际上可能操作的是不同的物理地址)
所以通过分段机制,我们可以更好的控制不同段的属性,这有利于内存的组织安排,可以对不同的属性代码、数据进行更方便的管理。如果是打乱的放在内存中,那么读写属性就很难控制。
程序运行地址和物理地址的隔离保证了程序内存数据的安全性,也解决了同一个程序运行地址不确定的问题,但是物理内存使用效率低下的问题依然没有得到解决,因为分段机制映射的是一片连续的物理内存。
于是大佬们又提出了分页的办法。分页其实就是把段空间更细分了一下,粒度更小。此时物理内存被划分为一小块一小块,每块被称为帧(Frame)。分配内存时,帧是分配时的最小单位。
在分段方法中,每次程序的运行都会被全部加载到虚拟内存中;而分页方法则不同,单位不是整个程序,而是某个“页”,一段虚拟地址空间组成的某一页映射到一段物理地址空间组成的某一页。它将少部分要运行的代码加载到虚拟内存中,通过映射在物理内存中运行,从而提高了物理内存的使用率。
为了方便CPU高效执行管理物理内存,每一次都需要从虚拟内存中拿一个页的代码放到物理内存。虚拟内存页有三种状态,分别是未分配、已缓存和未缓存状态。
未分配:指的是未被操作系统分配或者创建的,未分配的虚拟页不存在任何数据和代码与它们关联,因此不占用磁盘资源;
已缓存:表示的是物理内存中已经为该部分分配的,存在虚拟内存和物理内存映射关系的;
未缓存:指的是已经加载到虚拟内存中的,但是未在物理内存中建立映射关系的。
虚拟内存中的一些虚拟页是要缓存在物理内存中才能被执行的,因此操作系统存在一种机制用来判断某个虚拟页是否被缓存在物理内存中,还需要知道这个虚拟页存放在磁盘上的哪个位置,从而在物理内存中选择空闲页或者更新缓存页,并将需要的虚拟页从磁盘复制到物理内存中。这些功能是由软硬件结合完成的,其存放在物理内存中一个叫页表的数据结构中。
虚拟内存和物理内存的映射通过页表(page table)来实现。每个页表实际上是一个数组,数组中的每个元素称为页表项(PTE, page table entry),每个页表项负责把虚拟页映射到物理页上。在 物理内存中,每个进程都有自己的页表。
因为有一个表可以查询,就会遇到两种情况,一种是命中(Page Hit),另一种则是未命中(Page Fault)。
命中的时候,即访问到页表中蓝色条目的地址时,因为在 DRAM 中有对应的数据,可以直接访问。
不命中的时候,即访问到 page table 中灰色条目的时候,因为在 DRAM 中并没有对应的数据,所以需要执行缺页置换。
在上图中,四个虚拟页VP1 , VP2, VP4 , VP7 是被缓存在物理内存中。两个虚拟页VP0, VP5还未被分配。但是剩下的虚拟页VP3 ,VP6已经被分配了,但是还没有缓存到物理内存中去执行。
通过虚拟地址空间和页表的回顾,现在大家应该明白为什么要引入虚拟内存了吧。
虚拟内存是计算机系统内存管理的一种技术,虚拟地址空间构成虚拟内存。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片。还有部分暂时存储在外部磁盘存储器上(Swap),在需要时进行数据交换。
虚拟内存不只是用磁盘空间来扩展物理内存 的意思——这只是扩充内存级别以使其包含硬盘驱动器而已。把内存扩展到磁盘只是使用虚拟内存技术的一个结果。除了扩展内存空间,虚拟内存技术还有隔离运行内存和确定运行地址的作用。
使用虚拟内存主要是基于以下三个方面考虑,也就是说虚拟内存主要有三个作用:
作为缓存工具,提高内存利用率:使用 DRAM 当做部分的虚拟地址空间的缓存(虚拟内存就是存储在磁盘上的 N 个连续字节的数组,数组的部分内容会缓存在 DRAM 中)。扩大了内存空间,当发生缺页异常时,将会把内存和磁盘中的数据进行置换。
作为内存管理工具,简化内存管理:每个进程都有统一的线性地址空间(但实际上在物理内存中可能是间隔、支离破碎的),在内存分配中没有太多限制,每个虚拟页都可以被映射到任何的物理页上。这样也带来一个好处,如果两个进程间有共享的数据,那么直接指向同一个物理页即可。
作为内存保护工具,隔离地址空间:进程之间不会相互影响;用户程序不能访问内核信息和代码。页表中的每个条目的高位部分是表示权限的位,MMU 可以通过检查这些位来进行权限控制(读、写、执行)。
所以,现在你应该搞懂虚拟内存是什么了吧!