英文原帖:Page Colouring on ARMv6 (and a bit on ARMv7)
投稿人: Jacob Bramley,2012 年 5 月 14 日
页面着色是为 MMU 分配页面的一种技巧,以便让页面以特定的顺序保存在缓存中。这种技巧有时用作优化(也不是专用于 ARM);但作为缓存架构的结果,有些 ARMv6 处理器实际上要求分配程序使用一些页面着色。部分 ARMv7 处理器也有相关(尽管没那么严格)的限制。本文将阐述为何缓存架构要施加这一限制,以及它在实践中意味着什么。
请注意,极少情况下才需要在内核中的物理内存分配程序(或其他特权短代码)之外考虑此限制。典型的用户空间代码或许不必直接对此进行处理,但理解页面着色有所帮助,例如可以解释为何某些 mmap 调用在 ARMv7 上可行,却在 ARMv6 上失败。
该限制的源头在于许多 ARMv6 处理器使用 VIPT 缓存。VIPT 表示“虚拟索引,物理标记(virtually indexed, physically tagged)”。如果你不熟悉缓存术语,这可能意义不大,但我会试着用举例的方式来说明。
通常情况下,ARMv7 不会受到 ARMv6 页面着色限制的影响。但是,ARMv7 也有 VIPT 指令缓存,因此一些限制依然适用,即使在用户空间中也是如此。
虚拟和物理寻址
请思考处理器需要进行简单内存存取的情形。存取的原因无关紧要;例如,可以是处理器加载要执行的下一指令,或者是加载或存储指令导致的数据传输。不管怎样,我们假设请求的地址是 0x12345678。这是一个虚拟地址,其背后的实际内存中可能有不同的物理地址,而且 MMU(内存管理单元)将需要使用分页表来执行该转译。许多操作系统使用这一机制来同时运行多个应用程序,尽管应用程序预期会从同一地址运行:每一应用程序可以认为其入口点位于 0x8000,但物理内存很有可能是不同的。这一过程对用户空间应用程序实际上是透明的。
ARMv6 MMU 中内存映射的最细粒度是 4KB 页面1。虚拟地址的底部 12 位与基础物理地址的底部 12 位完全匹配。这是因为 12 位可以对 4KB 页面中的任何偏移进行寻址(因为 212 = 4096),而且在虚拟和物理内中页面是自然对齐2 的。因此,虚拟地址转译可以按照如下所示查看3:
使用 4KB 页面的 32 位寻址的虚拟到物理地址转译。
为清楚起见,我用绿色来表示虚拟地址部分,用蓝色表示物理地址部分。低 12 位(灰色)通常不会与虚拟和物理部分分开表示,但我已尽量表明它们在转译过程中不会更改,因为此属性与页面着色相关。在本例中,MMU 已配置为将虚拟地址 0x12345678 映射到物理地址 0x9abcd678。
VIPT 缓存查找
理想状态下,内存存取将导致缓存查找,因为缓存存取的速度比内存存取快得多。“VIPT”属性对于缓存查找的运作方式给了一个提示:
该过程在某种程度上做了简化描述,但对该过程的说明应该已足够了解页面着色。
下图对上述步骤可能有更清晰一些的说明:
VIPT 缓存中简化的缓存查找。
有些有趣的要点值得注意:
缓存路数
ARM1136 和 ARM1176 使用 4 路组相联缓存。当内存位置存储在缓存中时,从其地址生成的索引允许其存储在四路中的任意一路。这种机制用来提高缓存效率。我不准备更加深入地阐述缓存路数,但如果你感兴趣的话,维基百科上有不错的介绍(撰写本文时)。
路数对页面着色的影响仅仅在于它们会影响缓存行索引中位的数量。在 4 路 64KB 缓存中(这是 ARM1136 和 ARM1176 的限制),每一路将有 64/4 = 16KB 可寻址空间。因此上图中的缓存显示了单路 16KB 缓存。
VIPT 缓存查找:重复映射问题
如果每个虚拟地址和物理地址之间存在一对一映射,那么上述 VIPT 缓存查找能够完美运作。但事实常常不是这样。即使具体的应用程序没有请求重复的映射,也会由于使用共享内存和操作系统中的管理原因而出现重复映射。请思考物理地址 0x9abcd678 同时映射到 0x12345678 和 0xfedcb678 的情形:
0x12345678 和 0xfedcb678 的缓存行索引。
上图中的两个虚拟地址被配置为映射到我们示例中的同一物理地址。不过,请注意缓存行索引是不同的。因此,有可能相关内存已存在于具有不同虚拟索引的缓存行中。如果缓存行是未清除的(表示数据已被更改并且尚未写到内存中),那么对另一别名所做的更改可能丢失,或者可能会屏蔽原来的更改。这种重复映射甚至不必同时处于活动状态就能造成此问题,因为在分页表更新之后上次活动进程中的一些数据可能依然保存在缓存之中。无论出现什么情况,都不好,所以操作系统必须对内存分配实施页面着色限制。
解决方案:页面着色限制
页面着色为每一内存页面分配一个颜色。颜色可分配给虚拟地址和物理地址,但此处的问题是虚拟地址。(此处的“颜色”用作一种说明工具。)虚拟内存中的每一页面按照顺序被分配一种颜色,如下所示:
页面颜色,表明颜色如何映射到地址的第 12 和 13 位。同时也显示了两个示例虚拟地址,表明它们拥有不同的颜色。
实际选择用于代表各个页面的颜色无关紧要。不过,请注意这两个示例地址拥有不同的颜色。在拥有 4KB 页面和 16KB 缓存路数的示例系统中,虚拟地址的第 12 和第 13 位决定页面颜色。它们是 VIPT 查找示意图中缓存索引所使用的两个位。请注意,这里有四种页面颜色,因为缓存索引中有虚拟地址的两个位,而 22 = 4。
因此,页面着色限制要求页面分配程序将任何给定的物理页面限定到单个虚拟颜色。
有两种明显的方式可以符合 ARMv6 的页面着色限制:
如果仅使用 4KB 页面,则可以构建维持颜色限制的映射,但该映射中第 12 和 13 位在虚拟和物理地址之间应不同;不过,加上这额外的复杂度是不太值得的。
请注意,ARM1136 和 ARM1176 都拥有固定为 4 路的缓存,因此有 4KB 路数的此类系统将拥有 16KB 缓存。
ARMv7 和页面着色
ARMv7 处理器具有 PIPT 数据缓存(或者至少需要表现为拥有该缓存)。也就是说,它们拥有物理索引、物理标记的数据缓存,不适用任何页面着色限制。然而,它们可以有 VIPT 指令缓存,甚至在一定范围内还允许拥有 VIVT 指令缓存。它们也可以有 PIPT 指令缓存。(详情请见《架构参考手册》。)Cortex-A8 和 Cortex-A9 都有 VIPT 指令缓存。
对于大多数使用案例来说,VIPT 指令缓存的行为与 PIPT 指令缓存非常相似。指令缓存是只读的,因此不会出现最明显的连贯性问题。然而,在通过虚拟地址使缓存行无效时,VIPT 指令缓存可能依然需要特殊处理:具有不同颜色的别名条目将不会失效。实际上,《架构参考手册》中有如下陈述:
使 VIPT 指令缓存中某一物理地址的所有别名无效的唯一架构上有保证的方式是使整个指令缓存无效。
在实践中,你应该能够简单地使给定虚拟地址的所有可能的颜色变体失效。当然,如果不关心所有虚拟别名,那么其他别名的可能存在也不重要。自修改代码的作者(如 JIT 编译器)可以确保它们要执行(在修改后)的任何代码都通过用于运行它的地址(而不是使用别名)来失效,从而安全而高效地在这一系统中运作。
请注意,通过数据缓存对指令数据的写操作 — 例如,在自修改代码中 — 始终要求明示缓存维护操作,如我在前一篇文章中所述。这是因为 ARM 架构中的指令缓存和数据缓存之间没有连贯性。
ARMv6 用户空间示例:mmap
下列 mmap 序列在 ARMv7 上通常可正常运行(在 GNU/Linux 下),但在 ARMv6 上会失败:
请注意,第一个调用请求映射到颜色为 00 的虚拟地址,而第二个调用则请求映射到颜色为 01 的虚拟地址。
在 ARMv6 上,第二个 mmap 通常会失败(并返回 MAP_FAILED),但第一个将通过。在一些情形中,第一个调用可能也会失败,例如在物理页面已被分配了不同颜色时。用户进程对此没有任何控制,尽管似乎(实验性地,在 GNU/Linux 平台上)文件在 16KB 对齐上映射,因此有理由认为颜色为 00 的文件映射在 ARMv6 上是可靠的。
在 ARMv7 上,两个映射(通常)都将可行,而且可以指定任何用户可分配的虚拟地址,只要它在 4KB 页面界限上对齐。
请注意,这些 mmap 调用无法确保在任何平台上都成功。例如,某一给定平台可能会限制可以映射的区域数量,而一旦达到这一限制时,mmap 将返回 MAP_FAILED 并将 errno 设为 EMFILE。
每一缓存策略的属性
既然 VIPT 缓存有这一固有的页面着色限制,为何还要使用它们?
可以在 VIVT 缓存(即虚拟索引、虚拟标记的缓存)上进行最简单(或许最快速)的缓存查找。如果加载了缓存行,则完全不需要 MMU 查找。然而,VIVT 缓存有重复映射的连贯性问题,而且在更新分页表时(如上下文切换时)必须执行缓存维护操作。其中一些问题存在硬件解决办法,但最为高效的解决方案就是使用 VIPT 或 PIPT 缓存。
VIPT 缓存是 VIVT 缓存的自然演进。它们在进行任何存取时需要 MMU 转译,但物理标记意味着物理地址可以在缓存中进行唯一标识,因此重复映射是可能的(在页面着色限制中)并且上下文切换时不需要缓存维护操作。另外,尽管依然需要 MMU 转译,它可以和索引操作并行执行,如前文中所述。
ARMv7-A 指定数据缓存像 PIPT 缓存一样运作,所以给定的标记不会在标记 RAM 中重复,原因是缓存是通过物理地址索引的。其缺点在于,缓存查找无法与 MMU 转译并行执行,因为缓存查找的每一阶段中都需要物理地址。然而,TLB 命中率良好时,这一缺点在某些程度上得到了缓减,因为成功的 TLB 查找速度很快。
但请注意,PIPT 缓存依然能够(在一些情形中)从页面着色中获益,因为它能够改善缓存行驱逐行为。这方面的详细信息可以单独组成一篇博文了,所以我不准备进一步详述。但是,这篇维基百科文章有不错的介绍(撰写本文时)。
1
ARMv6 之前版本的 ARM 架构也支持 1KB“小页面”,但它们 ARMv6 或更近期的架构版本中已不再可用。
2
“自然对齐”的意思是对象对齐到其大小倍数的地址。也就是说,4KB 页面对齐至 4KB 界限,而 1MB 部分则对齐至 1MB 界限(举例来说)。
3
在现实中,该转译有可能要通过 TLB(转译后备缓冲区)执行,而不是直接由 MMU 进行,但原理是相同的。
Jacob Bramley 是 ARM 嵌入式软件工程师。Jacob 喜爱大多数技术主题,但尤其热衷于代码生成和汇编手工优化。他也着迷于硬件及软硬件交互,喜欢花上数小时时间对着流水线图发呆(即使效率不高),以便能够从各处省却一两个周期。