页表描述符详解:段、粗页表、细页表与页描述符

ARM 处理器的内存管理,说白了就是靠页表描述符来干活。每个描述符就像一张标签,告诉 MMU 这块内存该怎么用。我刚开始接触 ARM 页表时,也被这四种描述符搞得有点晕——段描述符、粗页表描述符、细页表描述符、页描述符。它们各有各的脾气,选错了性能直接打折扣。

今天咱们就把这四种描述符掰开揉碎,看看它们到底长什么样,什么时候该用谁。

4.1 段描述符(Section Descriptor)

段描述符是 ARM 页表里最“粗犷”的一种。它直接映射 1MB 的内存块。你想想看,一级页表里一个条目就能管 1MB 空间,整个 4GB 地址空间只需要 4096 个条目。查表快,TLB 压力也小。

段描述符的格式长这样:

位  [31:20]  [19:12]  [11:10]  [9]  [8:5]  [4]  [3]  [2]  [1:0]
内容  基地址    未使用    域       nG   SBZ     XN   C    B   10

关键字段我列一下:

  • 位 [1:0] = 10:标识这是个段描述符,不是别的类型。
  • 基地址 [31:20]:物理地址的高 12 位,低 20 位全是 0。说白了就是 1MB 对齐。
  • 域(Domain)[11:10]:16 个域之一,配合域访问控制寄存器用。
  • C 和 B 位:控制 cache 和 buffer 策略。C=1 开 cache,B=1 开写缓冲。
  • XN 位 [4]:禁止执行。做安全加固时我经常用这个位。
我的经验:段描述符最适合映射大块连续内存,比如 SDRAM、NOR Flash。我在一个视频采集项目里就用段映射把 64MB 的帧缓冲一口气映射完,TLB 缺失率低得让人舒服。

4.2 粗页表描述符(Coarse Page Table Descriptor)

粗页表描述符不直接映射内存,它指向一个二级页表。这个二级页表有 256 个条目,每个条目管 4KB。所以粗页表能覆盖 256 × 4KB = 1MB 空间。

它的格式:

位  [31:10]  [9]  [8:5]  [4]  [3]  [2]  [1:0]
内容  二级页表基址  nG   SBZ     XN   域   01

注意位 [1:0] = 01,这是粗页表描述符的标识。二级页表基址 [31:10] 要求 1KB 对齐,因为低 10 位是 0。

什么时候用粗页表?当你的内存区域需要细粒度控制,但又不想让页表太大时。比如映射一个设备寄存器区域,有些地址可读可写,有些只读,用段描述符就太粗了。

我曾经踩过的坑:粗页表描述符里的域位 [4:3] 只占 2 位,不是段描述符的 4 位。这意味着粗页表下的所有二级条目都共享同一个域。如果你需要不同子页有不同的域权限,那就得用细页表。

4.3 细页表描述符(Fine Page Table Descriptor)

细页表描述符是 ARM 页表里最“精细”的一种。它指向一个三级页表,这个三级页表有 1024 个条目,每个条目管 1KB。覆盖范围还是 1MB,但粒度到了 1KB。

格式如下:

位  [31:12]  [11]  [10]  [9]  [8:5]  [4]  [3]  [2]  [1:0]
内容  三级页表基址  nG    S    SBZ    域    XN   C    B   11

位 [1:0] = 11 是细页表描述符的标识。三级页表基址 [31:12] 要求 4KB 对齐。

细页表的好处是粒度小,但代价也大:三级页表本身占 4KB,加上一级和二级页表,内存开销不小。我一般只在两种场景用细页表:一是映射非连续的小块物理内存,二是做精细的权限控制。

关键对比:细页表描述符有位 [10] 叫 S 位(Shareable),这是段和粗页表描述符没有的。在多核系统中,S 位控制页面是否在多个核之间共享缓存一致性。做 AMP(非对称多处理)系统时,这个位我每次都会仔细检查。

4.4 页描述符(Page Descriptor)

页描述符是二级页表或三级页表里的条目,直接映射一个物理页。根据所在的页表层级不同,页描述符有两种尺寸:

页表层级 页大小 描述符数量 覆盖范围
粗页表(二级) 4KB 256 1MB
细页表(三级) 1KB 1024 1MB

4KB 页描述符的格式:

位  [31:12]  [11]  [10]  [9]  [8:5]  [4]  [3]  [2]  [1:0]
内容  物理页基址   nG    S     APX   域    XN   C    B   10

1KB 页描述符的格式类似,只是基址位是 [31:10],低 10 位为 0。

页描述符的位 [1:0] 也是 10,和段描述符一样。怎么区分?看它在哪一级页表里——一级页表里是段,二级/三级页表里就是页。

我个人的习惯:做 Linux 内核移植时,我几乎只用粗页表 + 4KB 页。Linux 内核本身也默认用 4KB 页,兼容性好。细页表的 1KB 页在大多数场景下性能并不好,因为页表遍历次数多,TLB 覆盖范围也小。

4.5 四种描述符对比总表

描述符类型 位 [1:0] 映射粒度 页表层级 页表大小 适用场景
段描述符 10 1MB 一级 16KB 大块连续内存、快速映射
粗页表描述符 01 4KB(二级) 一级→二级 1KB + 1KB 通用场景、设备寄存器
细页表描述符 11 1KB(三级) 一级→二级→三级 1KB + 1KB + 4KB 精细权限控制、非连续内存
页描述符 10 4KB 或 1KB 二级或三级 — 实际物理页映射

4.6 描述符选择策略

选哪种描述符,说白了就是权衡三个东西:粒度、页表大小、TLB 效率。

  • 粒度优先:需要 1KB 粒度?用细页表。但页表内存开销大,TLB 压力也大。
  • 性能优先:用段描述符。一次查表搞定,TLB 覆盖 1MB。适合实时性要求高的系统。
  • 通用优先:粗页表 + 4KB 页。Linux 内核就是这么干的,经过千锤百炼。
注意:ARM 架构中,段描述符和页描述符的位 [1:0] 都是 10。MMU 通过页表层级来区分它们。如果你在二级页表里误放了段描述符,MMU 会当成页描述符解析,结果就是访问错误地址。我调试过一个系统,就是有人把一级页表的段描述符复制到了二级页表里,系统跑起来随机崩溃,查了两天才找到原因。
ARM 页表描述符层级结构 一级页表 4096 个条目 段描述符 (10) 1MB 直接映射 粗页表描述符 (01) 指向二级页表 细页表描述符 (11) 指向三级页表 二级页表 256 个条目 三级页表 1024 个条目 页描述符 (10) 4KB 物理页 页描述符 (10) 1KB 物理页 位 [1:0] 标识描述符类型:10=段/页,01=粗页表,11=细页表

这张图把四种描述符的关系画清楚了。从一级页表出发,段描述符直接到物理内存,粗页表描述符经过二级页表再到 4KB 页,细页表描述符经过三级页表再到 1KB 页。路径越长,灵活性越高,但性能也越差。

嗯,到这里页表描述符的核心内容就讲完了。记住一个原则:能用段不用粗,能用粗不用细。除非你真的需要那个粒度,否则别让 MMU 多跑冤枉路。