MMU的重要性不言而喻,支撑操作系统之上的各种复杂应用。但在正式讲MMU之前,我们先说说MMU的发展史,因为ARMv8-A的MMU相当复杂,直接切入正题,会显得比较枯燥。废话不多说,咱们马上开始:

一、前言:

关于虚拟内存系统的演变史,MMU在其中扮演了什么样的角色。

可以参考这一篇文章​《操作系统的靠谱管家:虚拟内存》​

早期计算机是没有MMU的概念的,也没有对存储器的抽象,直接将物理内存暴露给程序用。那个时候硬件资源有限,大家都勒紧裤腰带写程序,不敢多浪费一个字节。我曾经规划过一个游戏掌机的开源项目,将早期的街机游戏移植到现代硬件上。

在调研阶段我就发现,这些程序都很小,只有十几KB,但无一例外,都是对内存进行直接操作(有点类似单片机/微控制器编程)。

ARMv8-A 地址翻译技术之MMU的前世今生_虚拟内存

图 1.1 GAME BOY 掌机

但在那个年代,硬件的迭代速度不如软件快,而彼时的代码业务逻辑已经相当复杂,程序的体积呈指数级增长。

所以早期开发人员,为了使得越来越庞大的程序,能够在资源有限的硬件上运行,发明了ovelay-覆盖块技术,对程序进行分割、分段运行。

但这招治标不治本,并不能保证程序运行的足够顺畅,反而要为程序分段,增加的额外的工作量,这是一件相当痛苦的事情。所以开发人员把目光看向了硬件,决定从软硬两个方向下手。

说到这里,就不得不提一下早期的虚拟化技术。该技术的硬件虚拟化方向,为以后MMU的发展奠定了基础。

“硬件虚拟化”在狭义上是对内存、硬盘等硬件做虚拟化处理(软件为主),配合操作系统以达到分时复用的效果。1964 年,IBM 推出了大名鼎鼎的 System/360。它不仅提供了新型的操作系统,还实现了基于全硬件虚拟化(Full Hardware Virtualization)的虚拟机解决方案。

ARMv8-A 地址翻译技术之MMU的前世今生_页表项_02

图 1.2 IBM 员工在调试机器

这其中就包括页式虚拟内存(4k 分页虚拟存储系统)、虚拟磁盘以及 TSS 分时系统。System/360 最多可提供14个虚拟机,每个虚拟机具有 256k 固定虚拟内存。有意思的是,System/360 的开发过程被视为了计算机发展史上最大的一次豪赌,为了研发 System/360,IBM 决定征召六万多名新员工,创建了五座新工厂。即便如此,当时的出货时间仍被不断顺延。

在后续的计算机发展中,内存被操作系统抽象出来,作为地址空间进行统一管理,按需分配给应用程序使用。这样作为内存的硬件细节(容量/类型/厂商/…)被操作系统隐藏了,程序只需要关心地址空间就可以。

同时操作系统引入了虚拟化技术,可以对内存进行"扩容",本质是对内存的分时复用,让每个程序认为自己独占了整个地址空间。但是这样做效率并不高,因为操作系统需要不断地做内存搬运。并且操作系统还要为内存的管理算法,付出许多额外的计算(比如地址翻译),非常浪费性能。

所以我们就需要设计硬件,来代替一些比较繁琐复杂的流程。最好能形成标准流程化操作,这样做成的硬件,可以持续优化(GPU也是这么出现的)。

因此,MMU诞生了,它接替了操作系统内存管理的比较复杂的部分,比如地址翻译;内存访问效率,则交给了 cache(高速缓存)去做,或者提高内存总线的带宽。

在现代操作系统中,衍生了一种更先进的内存管理技术,叫做虚拟内存系统,这是对主存的一种抽象概念。它由硬件异常、物理地址翻译、主存、磁盘文件和内核软件相互配合,共同组成。

虚拟内存系统主要提供了三种能力:一、提供主存的高速缓存,加快内存访问速度(高速缓存);二、为每个进程提供一致的地址空间,从而简化内存管理;三、防止每个进程的地址空间被其他进程所破坏(内存保护)。

ARMv8-A 地址翻译技术之MMU的前世今生_虚拟内存_03

图 1.3 虚拟内存系统简易图

MMU的作用,就是将虚拟地址翻译成物理地址。简单来说,CPU会生成一个虚拟地址(Virtual Address)来访问主存(这个地址实际上是操作系统来产生的,最后交给CPU执行)。访问之前,需要先将虚拟地址转换为物理地址,这个过程称作为地址转换(地址映射/地址翻译)。

为进行此操作,需要CPU硬件和操作系统合作,通过内存管理单元(Memory Management Unit)上的硬件翻译地址,来完成虚拟地址到物理地址的转换。MMU利用存储在主存上的查询表(translation table)来翻译虚拟地址,该表的内容由操作系统维护和管理。

ARMv8-A 地址翻译技术之MMU的前世今生_虚拟内存_04

图 1.4 MMU地址翻译/转换简化流程

说到这里,你可能对MMU的描述感到云里雾里,但不要紧,接下来我们会对它抽丝剥茧,一点点讲清楚。不过我们还需要对操作系统的虚拟内存系统,做一些更详细的讲解,这样再看MMU,就会非常轻松了。记住,这个先后顺序非常重要。

二、虚拟内存系统的原理:

这里我们只探讨虚拟内存系统如何管理虚拟内存的,关于cache部分,我们另说。

一般虚拟内存系统会将虚拟内存,划分为固定大小的块(又叫做最小粒度,一般有4KB、16KB、64KB等大小,其中4KB最流行),这个块我们称作为虚拟页(Virtual Page简称VP),同理将物理内存划分为物理页(Physical Page简称PP),也叫页帧(Page Frame)。

每个虚拟页的首地址,会被维护在一个表内,这个表叫做查询表或页表(Page Table,简称PT),页表内的每一个条目,被称为页表项(Page Table Entry,简称PTE)。

这个页表可能有多级,一般来说,级数越多,能覆盖的虚拟地址范围就越大。在多级页表当中,除最后一级页表之外的所有页表,它的页表项存放的不一定是物理页,也可能是下一级页表的地址。

ARMv8-A 地址翻译技术之MMU的前世今生_页表_05

图 2.1 虚拟内存系统的地址翻译/转换流程

在这个基础上,虚拟系统会产生一个虚拟地址,由虚页号+页偏移组成。 虚页号存放的一般是页表项的偏移地址(也可能是多级页表的组合),通过对页表的不断查询,最后找到对应的物理页(的首地址),然后加上虚拟地址的页偏移,就能顺利计算出真实的物理地址。如图2.2所示:

ARMv8-A 地址翻译技术之MMU的前世今生_页表_06

图 2.2 虚拟内存系统的地址翻译/转换详细流程

这个流程,可以用一个很恰当的例子去描述,就是在地图软件上找一个地方儿。

以我在地图上找酒店入住为例,目的地在“四川省-成都市-青羊工业区-成飞路-5号”。于是,我通过手机软件,找到一家到酒店,位置是“四川省-成都市-青羊工业区-成飞路10号”。

哎~,有意思的地方来了, 现在我们以虚拟内存系统的概念,去套这个地址。

首先说虚拟地址(映射为我们生活上划分的位置)。“四川省”是一级页表项的偏移地址,一级页表的范围最大,它有好多个城市(页表项);“成都市”是二级页表项的偏移地址,二级页表的范围次之,它有好多个区(页表项);“青羊工业区”是三级页表项的偏移地址,三级页表的范围已经很小了,但是它也有好多条路(页表项);成飞路就是四级页表项的偏移地址,通过访问成飞路这个页表项,就可以找到它代表的物理页,里面有很多地址(也就是门牌号),目的地在5号,我要住的酒店在10号。

再说对应物理地址。这里从地理角度讲,可以划分为不同的坐标,这里我们就把四川省划分为同等大小的区域,正好被虚拟地址规定的几级页表瓜分。这个区域(路)就是物理页,假设坐标的最小寻址单位,就是通过门牌号寻址。现在,我们再变化一下图2.2,那么你就可以清晰的认识这个过程了。如图2.3(一定要好好观察,并结合我上面的文字走一遍这个流程,图中的每一个箭头都有意义,应该好好研究):

ARMv8-A 地址翻译技术之MMU的前世今生_页表项_07

图 2.3 类比地址转换

同样的道理和方法,就可以找到我酒店的位置,是不是没有想象中那么复杂?而MMU的工作,主要就是做地址的转换、翻译。在这里,虚拟内存系统主要扮演的角色,维护多级页表,控制虚拟地址的映射范围。虚拟系统一般是操作系统的组成部分,这部分工作,也可以说是操作系统帮我们做了。MMU只需要不停地从页表里逐级查找,得到最后的物理页,然后结合虚拟的地址后半部分的页偏移,计算真实的物理地址。

不知道大家有没有注意到,上面我说过,“最后一级页表前的所有页表,它的页表项存放的不一定是物理页,也可能是下一级页表的“页表项的偏移””,反过来讲,每一级页表项,都可以存放物理页和下一级页表的地址,这里我们给存放物理页的页表项叫做块(Block Entry,简称BE);给存放下一级页表地址的页表项叫做TE(Table Entry,简称TE,中文不知道叫啥)。不同的是,每一级页表项存放的块,它的大小是不同的。一般一级页表的块最大,之后逐级递减。

最后,大家可以思考一下,页表级数、块大小的不同,会对地址翻译产生什么样的影响?如果我的CPU只有32位,但是我想实现大于32位地址范围的虚拟地址访问,又该如何操作?

今天先讲到这个,以上全当一道开胃菜儿。关于MMU的故事,我们娓娓道来。关于我上面提出的问题,我们将在下一篇文章揭晓,并为大家介绍ARM的MMU是如何工作的。