页表描述符详解:段、粗页表、细页表与页描述符
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]:禁止执行。做安全加固时我经常用这个位。
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 细页表描述符(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,加上一级和二级页表,内存开销不小。我一般只在两种场景用细页表:一是映射非连续的小块物理内存,二是做精细的权限控制。
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,和段描述符一样。怎么区分?看它在哪一级页表里——一级页表里是段,二级/三级页表里就是页。
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 内核就是这么干的,经过千锤百炼。
这张图把四种描述符的关系画清楚了。从一级页表出发,段描述符直接到物理内存,粗页表描述符经过二级页表再到 4KB 页,细页表描述符经过三级页表再到 1KB 页。路径越长,灵活性越高,但性能也越差。
嗯,到这里页表描述符的核心内容就讲完了。记住一个原则:能用段不用粗,能用粗不用细。除非你真的需要那个粒度,否则别让 MMU 多跑冤枉路。