什么是虚拟内存、什么是物理内存?
Linux 虚拟内存是操作系统中一个重要概念,它允许程序在更大的地址空间中运行,并提供了高效的内存管理机制。
什么是虚拟内存?
虚拟内存是操作系统的一种内存管理技术,它将系统中的物理内存与逻辑地址空间进行了解耦。通过使用虚拟内存,每个程序可以拥有自己的地址空间,从而使得程序可以在较小的物理内存上运行,同时提供了更高的安全性和隔离性。
虚拟地址和物理地址
虚拟地址:由程序产生的由段选择符和段内偏移地址组成的地址
逻辑地址:由程序产生的段内偏移地址,逻辑地址与虚拟地址二者之间没有明确的界限
线性地址:指虚拟地址到物理地址变换的中间层,是处理器可寻址的内存空间(称为线性地址空间)中的地址。程序代码会产生逻辑地址,加上相应段基址就成了一个线性地址。如果启用了分页机制,那么线性地址再经过变换产生物理地址。若是没有采用分页机制,那么线性地址就是物理地址。
物理地址:指内存中物理单元的集合,进程在运行时执行指令和访问数据最后都要通过物理地址来访问主存。
在Linux中,用户代码段,用户数据段,内核代码段,内核数据段的段寄存器的基地址都为0,这就意味着在用户态或内核态下的所有进程可以使用相同的逻辑地址,而且逻辑地址与线性地址是一致的。
使用虚拟内存的系统
CPU通过生成一个虚拟地址(VA)来访问主存,虚拟地址由地址翻译转换为物理内存(PA)。负责地址翻译工作的是内存管理单元(MMU),一个集成在CPU中的专用硬件,利用存放在主存上的查询表来动态翻译虚拟地址
虚拟内存的工作原理
作为缓存的工具
VM系统将虚拟地址分割为大小相同的虚拟页(VP),页内部连续的线性地址被映射到连续的物理地址。这样,内核可以指定一个页的物理地址和其存取权限,而不用指定页内所包含的全部线性地址的存取权限。通常情况下,我们所说的“页”既指一组线性地址,又指包含在这组地址中的数据。
物理内存被分割为物理页(PP),也叫页框,每个页框包含一个页,也就是页框的长度与页的长度一致。
在任意时刻,虚拟页面的集合分为三个不相交的子集:
未分配的:VM系统还未创建的页,没有任何数据关联,不占用磁盘空间
已缓存的:当前已缓存在物理内存中的已分配页
未缓存的:未缓存在物理内存中的已分配页
页表
存放在物理内存中的页表将虚拟页映射到物理页,页表内容由操作系统维护。页表就是一个页表条目(PTE)的数组。虚拟地址空间的每个页在页表中一个固定偏移量处都有一个PTE。
我们假设每个PTE由一个有效位和一个n位地址字段组成,有效位表示虚拟页是否被缓存。
设置了有效位,地址字段表示DRAM中相应物理页的起始地址,物理页中缓存了该虚拟页,对应已缓存的
没有设置有效位,一个空地址表示这个虚拟页还未分配,对应未分配的
没有设置有效位,一个非空地址指向该虚拟页在磁盘上的起始位置,对应未缓存的
地址翻译
MMU利用页表实现地址映射。CPU中的页表基址寄存器(PTBR)指向当前页表。ARM中的TTBR0(Translation table base register)和TTBR1分别用来存放用户空间和内核空间的一级页表基址。
分配页面
操作系统分配一个新的虚拟内存页VP5,并更新PTE5,指向对应的磁盘地址
页命中
考虑访问VP2中的虚拟内存,此时地址翻译单元找到PTE2,并读取它,因为设置了有效位,所以它使用PTE中指向的物理内存地址。
详细步骤
- 处理器产生一个虚拟地址,并将其传送给MMU
- MMU生成PTE的地址,并向主存请求它
- 主存向MMU返回PTE
- MMU构造出物理地址,并传送给主存
- 主存返回请求的数据给处理器
缺页
访问VP3中的地址,因为有效位为0,所以地址翻译单元知道目标页未缓存,触发缺页异常,缺页异常处理程序会选择一个牺牲页,此例中我们选择VP4作为牺牲页,将其从DRAM中换出,并更新页表项,如果VP4中内容已经修改,内核会将其写回磁盘。
内核从磁盘复制VP3到内存的PP3,并更新PTE3,随后返回,异常返回后,它会重新执行导致缺页的指令,地址翻译单元经过翻译,发现页命中,就可以继续执行了。
缺页处理流程
当第3步返回的PTE中有效位为0时,MMU触发一次缺页异常,控制权转到缺页异常处理程序,选择一个牺牲页,调入一个新页,更新PTE,并再次执行导致缺页的指令,此时页命中。
地址翻译的优化
正如上文描述的,每次CPU产生一个虚拟地址,MMU就需要访问主存查询一个PTE来获取对应的物理地址,然后再此访问主存获取实际的数据。为了优化PTE的查询,MMU有一个PTE缓存TLB(Translation Lookaside Buffer)
当TLB命中时,所有的地址翻译由MMU硬件完成,因此执行非常快。
当TLB不命中时,MMU从主存中获取相应的PTE并更新TLB
多级页表
当这里,我们一直讨论的都是使用一个页表来完成地址翻译。但是如果我们有一个32位地址空间,4KB分页,每个PTE占用4字节,则我们需要一个4MB的页表驻留在内存中,对于64位系统来说,问题更复杂。
使用多级页表就可以完美解决这个问题啦
例如一级页表中的PTE映射虚拟地址空间中的一个4MB的片,由1024个连续页表组成。二级页表的每个PTE映射4KB的虚拟地址。这样一级页表和二级页表都只有4KB大小,大大减少了内存需求。
一个k级页表的地址地址翻译
页表基址 + VPN i 获取下一级页表的基址,再加上下一级页表的index 获取下一个页表的基址,以此类推,最后获取到物理页号PPN,加上内页偏移PPO获取到对应的物理地址。
虚拟内存作为内存管理工具
操作系统为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。
按需页面调度和独立的虚拟地址空间结合,对系统中内存的使用和管理造成了深远的影响。
简化链接:独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理内存的何处。
简化加载:加载器为代码和数据段分配虚拟页,并标记为未缓存的,然后在实际引用时,再按需调入
简化共享:独立的地址空间为操作系统管理用户进程和操作系统自身之间共享的一致机制。用户进程需要共享内核代码段和一些标准库中的程序,操作系统会将不同进程中适当的虚拟页面映射到相同的物理页面。
简化分配:虚拟内存为提供了一种简单的向用户进程分配内存的机制。例如通过malloc分配的连续虚拟内存,操作系统将它们映射到物理内存中的多个分散的页面上。
虚拟内存作为内存保护工具
操作系统需要管控进程对于内存系统的访问,例如不应允许一个用户进程修改只读代码段,或修改其他进程的私有内存。虚拟内存带来的独立的地址空间使得区分不同进程的私有内存变得容易。通过再PTE中扩展一些标志来控制对一个虚拟页面内容的访问变得十分简单
如果指令违反条件,CPU就会触发一个保护故障,将控制权传递给一个内核中的异常处理程序,对应常见的就是”段错误“。
虚拟内存、物理内存之间的关系是什么样的?
物理内存: 计算机实际的内存大小,也就是计算机安装 内存条Q的大小
虚拟内存:是创建进程时分配的虚拟内存,是一个地址连续的地址空间
虚拟内存通过MMU进行映射,关联到实际的物理内存。
Linux给每个进程都分配了3G的用户空间,和1G的内核空间。虽然每个进程都“相信”自己拥有 4GB 的空间,但实际上它们运行时真正能用到的空间根本没有那么多。内存管理器只是分给进程了一片“假地址”,或者说是“虚拟地址”,让进程们“认为”这些“虚拟地址”都是可以访问的。如果进程不使用这些“虚拟地址”,它们对进程来说就只是一笔“无形的数字财富”;当需要进行实际的内存操作时,内存管理器才会把“虚拟地址”和“物理地址”联系起来。
为什么需要虚拟内存?
为什么需要虚拟内存?因为虚拟内存提供了三项能力,解决了物理内存系统面临的两个问题。
内存系统面临哪些问题?
内存(memory)资源永远都是稀缺的,当越来越多的进程需要越来越来内存时,某些进程会因为得不到内存而无法运行;
内存容易被破坏,一个进程可能误踩其他进程的内存空间;
虚拟内存提供了哪些能力?
正如软件工程中的其他抽象,虚拟内存是操作系统物理内存和进程之间的中间层。它为进程隐藏了物理内存这一概念,为进程提供了更加简洁和易用的接口。这个中间层提供了三个重要的能力:
高效使用内存:VM将主存看成是存储在磁盘上的地址空间的高速缓存,主存中保存热的数据,根据需要在磁盘和主存之间传送数据;
简化内存管理:VM为每个进程提供了一致的地址空间,从而简化了链接、加载、内存共享等过程;
内存保护:保护每个进程的地址空间不被其他进程破坏。
参考:
Linux 内存管理(一)--虚拟内存 - 知乎
内存管理1:为什么需要虚拟内存? - 知乎 (zhihu.com)