内存,大家都很熟悉,没了内存,CPU跑不起来。
内存空间在使用时被分成两部分,一部分是内核空间,一部分是用户空间。之所以要这样划分,是为了避免用户应用程序直接关闭内核空间中的关键进程,从而导致系统崩溃。
但是对内存的使用存在这样一个问题:假设用户空间有700M,现在有3个应用程序需要运行,大小分别为300M、300M和400M。那么程序1会被分配300M,程序2也会被分配300M,而程序3因为没有足够的内存空间使用,暂时等待。当程序1执行完,释放掉其所占用的内存之后,用户空间有两块可用内存,一块300M,一块100M,但是这两块内存不连续。虽然此时总的可用内存是400M,但是由于不连续,无法分配给程序3。这两块内存被称为外碎片。
为解决外碎片问题,出现了分段的概念。将程序分段,也将用户空间分段。然后以段为单位将程序加载到内存中执行。用分段方式处理时,不要求用户程序占用连续的内存空间,可以出现用户程序占用多个不连续段的情况。
由于分段方式中每一段的大小比较大,应用程序通常不会刚好占用若干个段,一般最后一个段都只会用到一部分。段中未用到的部分被称为内碎片。为了解决内碎片问题,出现了分页的概念。分页是更小尺度上的分段,可以更充分地利用内存空间。
以上的策略可以解决一些问题,但是如果应用程序比整个用户空间还要大,这时要怎么运行程序呢?于是就出现了虚拟内存的概念。
虚拟内存是虚拟出来的,是逻辑上的,其物理组织形式是磁盘上的一块连续的存储空间。对这块磁盘空间来说,物理内存是它的高速缓存。虚拟内存被划分成若干个虚拟页;物理内存被划分成若干个物理页。虚拟页和物理页大小相等。我们将磁盘上的一页也称为虚拟页。当说的是磁盘上的虚拟页时,我会显式地加上限定词“磁盘上的”。
虚拟页有两种状态:
-
未分配:逻辑上该页中没有数据
-
已分配:逻辑上该页中已有数据
已分配页又可分为两种状态:
-
未缓存:该页实际上还在磁盘上,还未被缓存到物理页中
-
已缓存:该页已经缓存到了物理页中
虚拟内存系统(专门管理虚拟内存的系统)需要判断一个虚拟页是否已经缓存到了物理页中,如果是,那么还需要确认虚拟页缓存到了哪个物理页中;如果否,那么还需要确认虚拟页对应磁盘上的哪个虚拟页,并需要在物理内存中选择一个物理页,将磁盘上对应的虚拟页缓存到该物理页中。
这里先介绍一下页表的概念。
页表是存放在物理内存上的,它负责将虚拟页映射到物理页。页表结构如下图所示。
可以将页表当作一个数组,每个数组元素被称为页表条目(PTE,Page Tabe Entry)。一个PTE包含一个有效位和一个地址。有效位为1表示该虚拟页已经已经缓存到了物理页中,对应的地址中存储的是物理页的起始地址;有效位为0时,如果地址为null,则表示该虚拟页还未被分配,如果地址不为null,则该地址是磁盘上的虚拟页的起始地址。
每当CPU需要取一个虚拟页时,就将虚拟地址(即该虚拟页的起始地址)发给MMU(Memory Management Unit,内存管理单元),MMU将该虚拟地址作为索引定位到PTE,然后根据该PTE中的有效位和地址进行具体判断。如果该PTE中的有效位为1,表明虚拟页命中,直接去物理页中取指令(或者是数据)。如果有效位为0,且地址为null,则表明缺页,此时MMU会触发一个缺页异常,转由内核中的缺页异常处理程序进行处理。缺页异常处理程序首先在物理页中选择一个作为牺牲页,如果该物理页已被修改,则需要先将该物理页回写到磁盘上,然后用磁盘上的虚拟页覆盖该物理页,并更新PTE。
其实从上面的描述中可以想到,虚拟内存为什么叫“虚拟”内存呢?因为从物理上看,并没有这样一块内存,每当要取虚拟内存中的一页数据时,是通过页表将虚拟页映射到物理内存中的物理页或磁盘上的虚拟页来实现的。
欢迎关注博主个人微信公众号~~