在计算机的奇妙世界里,我们每天使用的操作系统看似流畅自如地运行着各类程序,背后实则有着一位默默耕耘的 "幕后英雄"------ 内存管理单元(MMU)。它虽不常被大众所熟知,却掌控着计算机内存的关键命脉,是保障系统稳定、高效运行的核心力量,宛如一场精彩演出背后的总导演,掌控着舞台上的每一个细节。
当我们打开电脑,同时运行多个程序时,有没有想过它们是如何在有限的内存空间里 "和平共处" 的?又或者,当程序所需内存远超实际物理内存时,为何系统依然能有条不紊地运行?这一切,都离不开 MMU 的神奇魔法。接下来,就让我们一起揭开 MMU 的神秘面纱,探寻它在操作系统中的精彩故事。
一、引言
虚拟内存是现代操作系统中最伟大的发明之一。它为每个进程提供了一个一致的、私有的地址空间,让每个进程产生了一种自己在独享主存的错觉。
为了讲清楚MMU是如何一步一步完成地址翻译,取出数据的,本篇文章在前部分中讲解了虚拟内存中一些重要的概念,比如,虚拟内存的作用,页命中,缺页异常处理,为什么需要TLB等等。最后,通过两个地址翻译的例子,详细解释了MMU地址翻译的过程。
⑴什么是虚拟内存?
-
虚拟内存能够创建一个连续的更大的空间给进程使用,出现的原因是由于主存的空间是有限。
-
当运行多个进程或者一个进程需要更大的空间进行存储运行,主存显然是不够的,这个时候就需要更大更便宜的磁盘进行保存一部分数据。
-
对于进程来说,虚拟内存就是一张连续的内存空间,这个空间有些在主存中,有些在磁盘中。
⑵虚拟内存的作用
-
虚拟内存将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,可以高效地使用主存。
-
虚拟内存为每个进程提供了一致的地址空间,简化了内存管理。
-
虚拟内存保护了每个进程的地址空间不被其他进程破坏。
⑶虚拟内存与物理内存
我们先来看下,CPU是如何根据地址取得数据的。CPU 在这里生成的物理地址为 4,把地址发送给内存,然后内存从该地址获取其中保存的字,最后将其发送回 CPU;MMU(Memory Management Unit)叫做内存管理单元,主要用来管理虚拟内存与物理内存的映射,由硬件自动完成。
⑷页命中/缺页
-
处理器产生一个虚拟地址。
-
MMU生成PTE地址,并从高速缓存/主存请求得到它。
-
高速缓存/主存向MMU返回PTE。
-
MMU构造物理地址,并把它传送给高速缓存/主存。
-
高速缓存/主存返回所请求的数据字给处理器。
-
处理器产生一个虚拟地址。
-
MMU生成PTE地址,并从高速缓存/主存请求得到它。
-
高速缓存/主存向MMU返回PTE。
-
PTE中的有效位是零,所以MMU触发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
-
缺页处理程序确定出物理内存中的牺牲页,如果这个页面已经被修改了,则把它换出到磁盘。
-
缺页处理程序页面调入新的页面,并更新内存中的PTE。
-
缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面现在缓存在物理内存中,所以就会命中,主存就会将所请求字返回给处理器。
⑸为什么有了高速缓存,还需要TLB呢?
局部性原则保证了在任意时刻, 程序将往往在一个较小的活动页面集合上工作,这个集合叫做工作集或者常驻集。换句话说, 局部性原则揭示了一个现象:在一段时间内,我们会反复调入或调出同一个或几个虚拟页页面。而且,每次CPU产生一个VA时, MMU就必须查阅PTE,以便将VA翻译为PA, 注意是每次,所以开销很大。
解决方法: 为了消除这样的开销,在MMU中包括了一个关于PTE的小缓存,称为翻译后备缓冲器,TLB(Translation Lookaside Buffer)。
关键点: 所有的地址翻译步骤都是在芯片上的MMU中执行的, 因此执行速度非常快。
二、主角登场:MMU是何方神圣
MMU是Memory Management Unit的缩写,中文名是内存管理单元,有时称作分页内存管理单元(英语:paged memory management unit,缩写为PMMU)。它是一种负责处理中央处理器(CPU)的内存访问请求的计算机硬件。它的功能包括虚拟地址到物理地址的转换(即虚拟内存管理)、内存保护、中央处理器高速缓存的控制,在较为简单的计算机体系结构中,负责总线的仲裁以及存储体切换(bank switching,尤其是在8位的系统上)。
内存管理单元(MMU)的一个重要功能是使系统能够运行多个任务,作为独立程序在自己的私有虚拟内存空间中运行。它们不需要了解系统的物理内存映射,即硬件实际使用的地址,也不需要了解可能同时执行的其他程序。
具体而言,MMU 肩负着两大关键职责。其一,负责虚拟地址与物理地址的映射,这意味着它能将程序使用的虚拟地址巧妙地转换为实际的物理内存地址,让每个进程都仿佛拥有了属于自己的独立内存空间,彼此互不干扰;其二,它还提供硬件机制的内存访问授权,严格把关每一次内存访问,确保进程只能在允许的范围内读写内存,有效防止非法访问,维护系统的稳定性与安全性。
2.1MMU的由来
⑴Swap模式时代
早期计算机在执行程序时,将程序从磁盘加载到内存执行中执行,在多用户系统中,当新的用户程序被执行前,需要先将当前用户的程序从内存swap到磁盘,然后从磁盘加载新的程序执行,当前用户退出后,在将前一用户程序从磁盘中加载到内存继续执行,每次用户切换伴随程序的swap,消耗较大。
⑵Page模式时代
后来人们将内存划分为固定大小的Page,一般为4K或者更小,这样用户程序按需以Page的方式加载到内存,不需要将整个程序加载到内存,这样内存可同时容纳更多程序,而无需按照用户切换进行swap,提升了内存利用率和加载时间(PageFault是Page时代的产物,而非MMU时代独有)。
⑶动态地址转换(DAT - Dynamic Address Translation)
最早可以追溯到1966年IBM研发的System/360-Model67,在该计算机的设计中首先引入了动态的地址转换机制,在Page模式基础上,为用户程序分配虚地址(VA),通过DAT转换为物理地址(PA)进行访问。通过好处是用户可以使用连续的地址,而不再受制于物理内存大小和Page碎片化的限制,原则上用户的程序只受磁盘大小限制,代价是增加虚实地址转换机制。
①实现方式
采用segment、page、offset模式,将24-bit虚拟地址分为3段,0-7bit保留,8-11bit索引segment,一共16个segment,12-19bit索引page,每个segment最多256个page,20-31bit为page offset,每个用户有一个虚实地址映射表,分为2级,即segment表和page表,每个segment指向一个page表。
②转换过程
当CPU访问一个虚地址时,先通过VA的8-11bit查找segment表得到page表,再根据12-19bit在page表中找到PA的page起始地址,加上20-31bit的page offset就得到实际的PA了。
这就是MMU最早的雏形了。
2.2现代MMU
实地址模式:即CPU状态位中MMU使能位清零,MMU处于关闭状态,此时CPU操作的地址不经过转换(VA=PA),直接作为物理地址进行访问,CPU上电时或者在异常入口时处于该状态,在该状态下可以访问任意物理内存,非常危险,一般操作系统在CPU上电后做完必要初始化以后便使能MMU,或者在异常处理的入口保存好必要信息后使能MMU。
块地址转换:或者成为固定的地址转换或静态配置的地址转换表,这种模式支持配置一些固定的内存地址映射(VPN->RPN),比如Linux Kernel加载的地址,以PowerPC604为例,0xC0000000这段地址开始的256M内存映射使用了该模式的转换,好处是这种配置转换速度快,一般在特定的寄存器中配置,没有页表查找过程,缺点是缺乏灵活性,一次配置永久使用。
页地址转换:类似于早期的动态地址转换DAT,即将VA的一部分bit用于索引segment,另外一部分bit用于索引PTE表,最终得到物理地址的Page起始地址,再加上最后12bit(4K)的Page offset得到真正的物理地址。
相比早期的DAT,有以下优化:
增加了PTE表的缓存TLB(Translation lookaside Buffer),有些处理器将ITLB(指令)和DTLB(数据)分开,以减少指令和数据之间的缓存冲突。
支持更大的物理地址(36bit以上)或逻辑地址,如在PowerPC中,用以应对现代操作系统的多进程管理将32bit通过segment寄存器扩展为52bit的逻辑地址,然后通过hash函数得到key用来查找PTE,并最终转换为36bit物理地址,逻辑地址的扩展用于减少多进程之间的PTE冲突。
支持多种PTE查找方式,如硬件查找和软件查找。现在CPU的MMU地址转换流程如下:
主要分为几个阶段:
-
用户进程访问虚存地址。
-
触发TLB查找过程,该部分通过硬件完成(灰色背景),没有软件参与。
-
TLB miss场景下,查找PTE(粉色背景),该部分在不同CPU上实现不同,像X86都是硬件查找,PowerPC有些处理器使用软件查找,即在内核实现一个TLB miss的异常处理,可以灵活做到TLB查找。
Do Page Fault,分为几种情况:
-
新申请内存第一次读写,触发物理内存分配
-
进程fork后子进程写内存触发Copy-On-Write。
-
非法内存读写,错误处理。
MMU在CPU的配合下(通过页异常触发),实现了线性地址到物理地址的动态映射,为正在CPU上运行的应用程序(进程)提供了一个独立的连续内存空间(线性地址空间,或称虚拟内存空间,其中放置了代码段、数据段和堆栈段),屏蔽了地址分配、内存分配和内存回收等一系列复杂的系统行为。
MMU的线性地址转换是通过页表进行的,具体过程如下图所示:
其实最简单明了的方法是通过一个一维数组来记录映射关系:下标代表线性地址,数组元素内容代表物理地址。可是如此一来,用来表示映射关系的内存空间比被表示的物理空间还要大,显然这不是一个可行的方案。
工程师们采用了分段分级的思路来表示这种映射关系:先把线性空间以4K大小为单位进行划分(页),然后再以大段连续空间进行转换,在每个大段空间内部再次划分成小段进行转换,直到段大小变为4K页大小。用以表示和段空间映射关系的结构称为页表,其大小也是一个页面。由于采用了分段的方法,页表空间大大减小;同时未映射的空间不必分配页表,这也进一步降低了页表占用空间。
x86_64架构下Linux用了四级页表来表示一个映射关系,依次为PGD、PUD、PMD、PT。每级页表4K大小,内部元素大小为8字节,高位指向了下一级页表的物理地址,低位表示页表属性(是否存在、读写权限、是否脏等等)。顶层页表PGD的物理地址存放在CPU的CR3寄存器中,供MMU访问。48位线性地址也相应地分成了五段:前四段,每段长9位,用来索引对应页表的元素;最后一段长12位,用来在页面中索引物理地址。
三、MMU的工作原理
3.1虚拟地址到物理地址的奇幻之旅
在计算机的世界里,每个程序都像是一个拥有独立王国的小世界,它们运行在自己的虚拟地址空间中。这个虚拟地址空间的大小,取决于计算机 CPU 的位数。例如,对于常见的 32 位 CPU,其虚拟地址空间范围是 0 到 2^32 - 1,也就是 4GB;而 64 位 CPU 的虚拟地址空间更是高达 2^64 - 1,这是一个极其庞大的数字,理论上能提供 16EB(1EB = 1024PB,1PB = 1024TB)的寻址能力。
然而,现实中的物理内存可没有这么夸张。一台普通的家用电脑,物理内存可能只有 8GB、16GB 甚至更小。这就好比,虚拟地址空间是一片广阔无垠的虚拟大陆,而物理内存只是这片大陆上实实在在存在的一小片陆地。那么,如何让程序在这片虚拟大陆上畅行无阻,又能精准地找到物理内存中的对应位置呢?这就轮到 MMU 大展身手了。
当程序运行并试图访问内存时,它使用的是虚拟地址。这个虚拟地址就像是一张神秘的地图,指引着程序前往它想去的地方。MMU 拿到这个虚拟地址后,会依据预先设置好的页表,进行一场精妙绝伦的 "翻译" 工作,将虚拟地址转换为物理地址。
这里就涉及到分页机制这个关键概念。分页,简单来说,就是把虚拟地址空间和物理地址空间都划分成一个个大小相等的小块,虚拟空间中的叫页(Page),物理空间中的叫页帧(Page Frame),页和页帧就如同拼图中的小块,大小必须完美匹配。比如说,在某个系统中,页和页帧的大小被设定为 4KB。当程序给出一个虚拟地址,比如 0x12345678,MMU 会迅速将这个虚拟地址拆分成两部分:虚拟页面号(VPN)和虚拟地址偏移(VA offset)。同样,物理地址也由对应的物理页帧号(PFN)和物理地址偏移(PA offset)组成。
MMU 转换地址的核心秘诀就在于找到 VPN 与 PFN 之间的对应关系,而这个关系,正静静地躺在页表之中。页表就像是一本神秘的魔法词典,存储着虚拟地址到物理地址的映射关系。MMU 根据 VPN,在页表中快速查找,找到对应的 PFN,再把 PFN 和 VA offset 相加,刹那间,物理地址就诞生了,程序也顺利找到了它梦寐以求的数据。举个例子,假设程序中的一个变量存储在虚拟地址 0x00401000,通过 MMU 的转换,发现它对应的物理页帧号是 0x0020,偏移量不变,最终得到物理地址 0x00201000,数据就这样被精准定位。
值得注意的是,页表本身通常存储在物理内存中,这就好像把寻宝的线索藏在了宝藏库里,每次 MMU 转换地址都得去物理内存里查找页表,会不会很慢呢?别担心,工程师们早就想到了办法 ------ 引入了快表(TLB,Translation Lookaside Buffer)。TLB 就像是 MMU 的 "私人小助手",它是一个高速缓存,专门用来存储最近使用过的页表项。MMU 查找地址时,会先在 TLB 中快速搜索,如果找到了,就能瞬间完成地址转换,大大提高了效率;要是 TLB 中没找到,才会去物理内存中查找页表,虽然慢点,但也能确保准确无误地找到目标。
3.2内存保护的安全护盾
在如今的多用户多进程操作系统环境下,计算机就像一个热闹非凡的大集市,众多进程熙熙攘攘地同时运行着。每个进程都怀揣着自己的 "小心思",处理着不同的数据,执行着各异的任务。要是没有严格的规则约束,它们在内存这个 "公共资源区" 里横冲直撞,必然会乱成一锅粥,数据混淆、程序崩溃等问题将接踵而至。
而 MMU,就是这个大集市的公正 "管理员",为每个进程精心打造了独立的地址空间,让它们 "井水不犯河水"。比如说,进程 A 和进程 B 都在运行,它们可能都用到了虚拟地址 0x1000,但在 MMU 的巧妙管理下,这两个虚拟地址会被分别映射到不同的物理内存区域,进程 A 访问 0x1000 时,得到的是自己的数据,进程 B 访问时,同样如此,彼此互不干扰,仿佛每个进程都独占了一片内存天地。
不仅如此,MMU 还是一位严谨的 "安全卫士",为内存的每一页都设置了精细的访问权限。这些权限就像是一道道坚固的门禁,严格限制着进程对内存的访问行为。页表项中的权限位详细规定了每页的访问规则,常见的权限有可读(Read)、可写(Write)、可执行(Execute)等。对于操作系统内核所在的内存页,为了防止用户进程误操作或恶意篡改,通常只设置为可读和可执行,禁止写入;而对于普通数据存储的内存页,可能允许进程读写,但禁止执行,避免代码注入攻击。
当进程试图访问某一内存页时,MMU 会第一时间检查该进程是否具备相应的权限。如果进程违反了权限规则,比如试图写入只读内存页,MMU 会立刻发出警报,抛出异常,并通知操作系统前来处理。操作系统就会像一位严厉的裁判,根据具体情况,可能采取终止违规进程、记录错误信息等措施,确保系统的整体稳定与安全。在一些复杂的服务器系统中,这种内存保护机制尤为重要,它保障着多个用户的任务能够安全、稳定地并行运行,互不干扰,为整个计算机系统的平稳运行保驾护航。
四、MMU与不同内存管理方式
在现代操作系统中,内存管理方式多种多样,而 MMU 在其中扮演的角色也因管理方式的不同而各具特色。
页式管理,是当下最为常见的内存管理方式之一。它把进程的虚拟地址空间等分成固定大小的页,物理内存也同样划分成与页大小一致的页帧。MMU 在此过程中,就像是一位精准的导航员,依据页表这一 "航海图",引导虚拟地址找到对应的物理页帧。页表记录着每页的映射关系,MMU 通过查找页表,迅速完成虚拟地址到物理地址的转换。
这种方式的优势显而易见,它极大地提高了内存的利用率,有效减少了外部碎片的产生,使得内存空间的分配更加规整、高效。例如,在一些大型数据库系统中,需要频繁地加载和卸载大量数据,页式管理能够确保数据在内存中的快速存储与读取,保障系统的高效运行。
段式管理,则侧重于按照程序的逻辑结构进行分段,每个段都有明确的意义,如代码段、数据段、堆栈段等。MMU 在段式管理里,负责将段式虚拟地址转换为物理地址。它借助段表来查找段的基地址,再结合段内偏移量,就能精准定位到物理内存位置。这种管理方式对于程序员而言更加友好,因为它贴合了程序的逻辑结构,方便程序的编写、编译与调试。以一个复杂的图形处理软件为例,不同功能模块的代码和数据可以分别存放在不同的段中,程序员能够清晰地管理各个模块,若某个模块出现问题,也能迅速定位排查,提高开发效率。
段页式管理,顾名思义,是将段式和页式管理的优点巧妙融合。它先把程序按逻辑分段,然后每段再进一步分页。在这种模式下,MMU 的工作变得更为复杂却也更加精细。系统需要为每个进程建立段表,段表项指向对应的页表,页表再负责具体的页到页帧的映射。当进程访问内存时,MMU 先根据段号在段表中找到页表的起始地址,再依据页号在页表中查找物理页帧号,最终确定物理地址。虽说段页式管理在地址转换时,可能需要多次访问内存,导致一定的性能开销,但它既具备段式管理的逻辑清晰、便于共享与保护的优点,又拥有页式管理内存利用率高的长处,适用于那些对内存管理要求极高、既需逻辑结构严谨又要高效利用内存的复杂系统,如大型服务器操作系统、高端工业控制系统等。
五、MMU在常见操作系统中的表现
在 Windows 操作系统中,MMU 为系统的稳定运行和多任务处理提供了坚实保障。当我们打开多个办公软件、浏览器窗口以及各类后台程序时,MMU 发挥着关键作用。它巧妙地将每个进程的虚拟地址空间转换为物理地址,确保进程间的内存互不干扰。例如,在 32 位的 Windows 系统中,每个进程都拥有独立的 4GB 虚拟地址空间,MMU 通过精细的页表管理,让这些进程能够在物理内存中和谐共存。同时,Windows 系统借助 MMU 的内存保护功能,严格限制进程对系统关键内存区域的访问,有效防止恶意软件或错误程序对系统造成破坏,保障了系统的安全性与稳定性,为用户提供流畅、可靠的使用体验。
Linux 作为一款广泛应用于服务器、嵌入式设备等多种领域的开源操作系统,MMU 的作用同样举足轻重。在服务器环境中,面对众多用户同时发起的复杂任务请求,Linux 依靠 MMU 实现高效的内存管理。它采用灵活的内存管理策略,如按需分页,结合 MMU 的地址转换机制,确保只有当前正在使用的内存页面被加载到物理内存中,大大提高了内存利用率。同时,对于实时性要求较高的嵌入式 Linux 系统,MMU 能够精准地为各个任务分配内存资源,保障任务的及时响应与执行,满足不同场景下的应用需求,充分展现了其强大的适应性与可靠性。
苹果公司的 macOS 操作系统,凭借其简洁易用的界面和出色的性能深受用户喜爱,而这背后离不开 MMU 的默默支持。macOS 在图形处理、多媒体编辑等高强度任务方面表现卓越,MMU 在其中功不可没。当运行大型图形设计软件或视频编辑工具时,这些应用程序需要大量的内存空间来存储图像、视频数据以及中间处理结果。MMU 高效地管理内存,快速进行虚拟地址到物理地址的转换,确保数据的快速读写,让软件运行流畅,避免卡顿现象,为创意工作者提供了稳定且高效的创作环境,助力他们尽情发挥才华。