内存是计算机系统重要资源之一,为了让多个同时运行的进程可以安全、高效的使用内存。现代操作系统的普遍做法是在进程和物理内存之间抽象了一个概念虚拟内存

编写应用程序是面向虚拟内存的,程序运行的时候只能使用虚拟内存,cpu负责将虚拟内存地址翻译成物理地址,而操作系统则负责将虚拟内存和物理内存的地址做映射。这样每个进程只能看到自己的虚拟地址,从而保证了应用的虚拟地址空间是统一的、连续的,同时通过内存管理做到每个进程的内存隔离,这样降低了编程的复杂度。

所以设计虚拟内存的目标包含以下三个方面:

  • 高效性 虚拟内存抽象不能使应用在运行的过程中造成明显的性能开销,且虚拟内存抽象不应该过多占用实际的物理内存资源
  • 安全性 虚拟内存抽象需要使不同应用程序的内存相互有隔离性,一个进程只能访问自己的内存区域
  • 透明性 虚拟内存抽象需要考虑应用程序透明性,使得应用程序开发者在编程时无需考虑虚拟内存的抽象

物理地址和虚拟地址

逻辑上将物理内存看做一个大的数组,其中每个字节都可以通过与之唯一对应的地址进行访问,这个地址就是物理地址。在引入虚拟内存抽象后,应用程序使用的内存地址就是虚拟地址

地址翻译

在程序运行过程中,CPU将应用程序访问的虚拟地址转换成物理地址的过程就是地址翻译

MMU(内存管理单元 Memeory Management Unit)

CPU中负责将虚拟地址转换成物理地址的组件,操作系统在运行一个程序的时候会将程序从磁盘加载到物理内存中,然后CPU会去执行第一条指令,此刻指令存放在物理内存中。在使用虚拟内存的前提下,CPU取指令时使用的是虚拟地址,虚拟地址通过MMU转换成物理地址,再通过总线将将请求发送给物理内存设备,物理内存设备将程序指令返回给CPU。

地址翻译的主要机制有哪些

  • 分段机制 分段机制下虚拟地址由两部分组成,段选择子和段内偏移量。简单来说讲就是段号+段内偏移量。段表中保存的是这个段的基地址、段的界限、特权等级等。虚拟地址中段内偏移应该位于0和段界限之间。分段机制下由于虚拟空间和物理空间都被划分成不同的段,在虚拟空间中,对应的物理空间中的段可以不相邻,因此操作系统能实现物理内存资源的离散分配。但是这种机制容易导致物理内存出现外部碎片(在段与段之间留下碎片),这样可能导致不足以映射给虚拟内存空间中的段,从而造成物理内存资源利用率低。

    分段原理机制

  • 分页机制 分页机制的思想是将虚拟地址空间划分成等长、连续的虚拟页,同时物理内存也被划分成连续、等长的物理页(两者页长固定相等)。这样使得操作系统方便为每个进程构造页表(虚拟页到物理页的映射关系)。在该机制下任何的虚拟页都可以映射到任意的物理页上,这样的好处可以有效地避免外部碎片的产生,同样使得虚拟内存、物理内存的管理更加便捷。

    分页原理机制

为什么需要多级页表

32 位环境下,虚拟地址空间共 4GB。如果分成 4KB 一个页,那就是 1M 个页。每个页表项需要 4 个字节来存储,那么整个 4GB 空间的映射就需要 4MB 的内存来存储映射表。如果每个进程都有自己的映射表,100 个进程就需要 400MB 的内存。64位虚拟地址空间这个页表大小为 33554432GB!!为了压缩页表大小,操作系统引入了多级结构的页表,用来满足虚拟内存在高效方面的要求。当使用简单的页表时,一个虚拟地址划分为两部分:虚拟页号+页内偏移。使用多级页表时,一个虚拟地址中依然是虚拟页号+页内偏移,其中虚拟页号将被进一步划分为K个部分(0<=i<=K)。多级页表允许整个表结构中出现空洞,而单级页表则需要没一项都实际存在,在实际过程中,绝对大多数虚拟空间都是未分配实际的物理内存,所以多级页表可以部分创建,从而极大的节约所占空间。

多级页表查找原理


单级页表可以看成虚拟地址的虚拟页号作为索引的数组,整个数组起始地址(物理地址)存储在页表的基地址寄存器中。翻译某个虚拟地址即根据虚拟页号找到对应的数组项,因此整个页表必须在物理内存中连续,其中没有被利用到的数组项页必须预留着

为什么需要TLB&TLB的工作原理是什么

多级页表结构能够显著的压缩页表的大小,但会导致地址翻译时间过长。为了加快虚拟内存到物理内存之间的翻译数据,CPU引入了TLB转址旁路缓存,TLB属于MMU的内部单位。TLB缓存了虚拟页号和物理页号之间的映射关系,简单看成TLB存储着键值对的哈希表,其中键是虚拟页号,值是物理页号,MMU会首先查询TLB,如果命中直接返回物理页号,反之TLB未命中查询页表。

什么是缺页/换页,为什么需要

  • 换页思想:当物理内存容量不够时或者一块内存不常使用的时候,操作系统会通过一定的策略算法选取若干物理内存页的内容写入类似磁盘这种更大容量的存储设备中,然后回收该物理内存并提供给其他程序使用

  • 缺页异常:它是和换页机制密不可分的,也是换页能够工作的前提,当程序访问已分配但未映射至物理内存的虚拟页时,就会发生缺页异常。操作系统利用换页机制把物理内存中放不下的数据临时存放到磁盘上,等到需要的时候在将其放回物理内存中,从而使得为程序提供超过物理内存容量的内存空间

  • 按需页分配:当程序申请分配内存时,操作系统可以选择将新分配的虚拟页标为已分配但未映射至物理内存状态,而不必为这个虚拟页分配对应的物理内存页,当应用程序访问该虚拟内存页的时候,触发缺页异常,此时操作系统才真正为这个虚拟内存页分配对应的物理内存

多级页表查找原理

如何选择页进行替换:页替换的策略

  • MIN 策略(最优策略) 优选选择未来不会再访问的页、长时间内不再访问的页
  • FIFO 策略(先进先出)维护一个队列用来记录换入内存的物理页号,每换入一个物理页就把其页号加入队尾,最先换入的内存页号处在对头。当触发换页时优选选取头部物理页号
  • Second Chance 策略 FIFO改进版本,在队列中的页号加上一个访问标志位。在换出该页号时查询访问标志位是否被访问标志,如果被标志则将该页号放入队尾且清除访问标志位,然后重新从队头查找页号进行比对。Second Chance最差情况会退化为FIFO策略。
  • LRU 策略 优选选择最近最久未被访问的页
  • MRU 策略 与LRU相反的策略,优先换出最近访问的内存页
  • 时钟算法策略 将物理内存的页号排成时钟形状,和Second Chance 策略类似,也是增加访问标志位来进行选择,不同点只不过无需将被标志过得页号放入队尾,效率略高与Second Chance

利用虚拟内存实现内存共享

虚拟内存使得应用程序能有拥有一个独立而连续的虚拟地址空间,通过页表和硬件之间的配合能够队应用程序透明的前提下自动进行虚拟地址到物理地址的翻译。共享内存允许同一个物理页面在不同的进程之间共享,如A、B两个进程,分别映射A1、B1虚拟内存,同时A1和B1指向同一片物理页,这样就实现了共享内存。共享内存的一个基本用途就是让不同的进程之间相互通信、传递数据。基于共享内存的思想有衍生出了写时拷贝、内存去重等功能。

  • 写时拷贝 两个进程拥有相同的内存数据,如果把这些数据相同的内存页在物理内存仅存一份,然后以只读的方式映射给两个进程,那么能显著的节约物理内存空间。在Linux中,一个进程fork了一个子进程,父子进程的全部内存数据和地址空间完全一样,此时父子进程的虚拟页指向的物理页为同一片物理内存,同时页表的权限会标志该进程对该空间的写权限,当子进程需要对内存进行写操作时,会触发缺页异常,从而操作系统会进行对只读的内存页进行拷贝。 多级页表查找原理

  • 内存去重 基于写时拷贝操作系统设计了内存去重功能。即操作系统定期扫描内存中具有相同内容的物理页,并且找到该物理页映射的虚拟页,然后只保存其中一个物理页,并且将具有相同内容的其他虚拟页使用写时拷贝的方式映射到该保留的物理页上,进而释放其他物理页来节约物理内存的开销。值得一提的是该功能对应用程序是完全透明的。

  • 内存压缩 为了节约内存资源,现代操作系统引入了压缩算法对内存数据进项压缩。当内存资源不充足的时候,操作系统选择一些最近不常用的内存页压缩其中的数据,从而释放更多的空闲内存。相对于换出内存数据到磁盘,该方法能够更加迅速的腾出空闲空间。

  • 大页 为了提高TLB命中率,将程序中连续的4KB内存页合并成相对较大的内存页,从而减少TLB的缓存项的使用,提高TLB的命中率。不过过度的大 页可能造成物理内存的浪费,也可能增加系统管理内存的复杂度。

物理内存的分配与管理