RISC-V PMP 实战进阶:优雅的“大块头”与“手术刀”——TOR + NAPOT 组合策略深度解析
一、引子:为什么我们不能“一招鲜吃遍天”?
很多 RISC-V 初学者在配置 PMP (Physical Memory Protection) 时,会听到一条金科玉律:
“能不用 TOR 就不用 TOR,尽量用 NAPOT,因为它省 Entry。”
这句话本身没错。NAPOT (Naturally Aligned Power-of-Two) 就像一块高效的大积木,一块就能盖住一大片区域。但在真实的嵌入式工程中,内存布局往往不是童话里的完美方块,而是像被狗啃过的饼干——参差不齐。
当你的代码段起始于 0x20010003,或者你需要在一片 Flash 中间切出一块不可执行的只读数据区时,只用 NAPOT 的结果往往是灾难性的:
- 要么 PMP Entry 瞬间爆炸(切成无数个 4KB 小块);
- 要么根本无法配置(因为起始地址没对齐,硬件直接拒绝)。
👉 结论先行:
NAPOT 负责“宏观覆盖”——快、省;TOR 负责“微观修补”——准、狠。
二、深度剖析:NAPOT 与 TOR 的硬件哲学
要写好 PMP 配置,必须先理解硬件是怎么看待这两个模式的。
1. NAPOT:不仅仅是 2^N,而是“自掩码”的智慧
NAPOT 的本质是 “地址高位匹配 + 低位任意”。
它的匹配规则基于一个核心公式:
RegionSize=2N+3 bytesRegionSize = 2^{N+3} \text{ bytes}RegionSize=2N+3 bytes
- 硬件行为: 配置寄存器
pmpaddr时,值形如...yyyy0111(看最低位)。 - 对齐要求(强制):
Start Address % Size == 0。- 通俗理解:你要画一个 256KB 的方块,这个方块的起点必须是 256KB 的整数倍。
[ NAPOT 示意图:完美的俄罗斯方块 ]
0x2000_0000 (对齐边界)
↓
┌──────────────────────────────┐
│ │
│ 256KB NAPOT 区域 │ ← 一块积木直接搞定
│ │
└──────────────────────────────┘
↑
0x2004_0000 (下一个对齐边界)
* 要求:起始地址必须能被 256KB 整除。
2. TOR:两堵墙之间的“任意形状”
TOR (Top of Range) 的匹配逻辑更像是在内存上插篱笆:
pmpaddr[i-1]≤Address<pmpaddr[i] \text{pmpaddr[i-1]} \leq \text{Address} < \text{pmpaddr[i]} pmpaddr[i-1]≤Address<pmpaddr[i]
- 硬件行为: 使用两个 Entry 构成一个区间。
- 灵活性: 不需要对齐。你可以精确到 4 字节边界。
[ TOR 示意图:任意位置的区间 ]
Prev_Entry_Addr Current_Entry_Addr
↓ (墙 A) ↓ (墙 B)
├──────────────────────────────┤
| 任意大小、任意起点的区间 |
└──────────────────────────────┘
↑ 这里可以是不对齐的地址 0x1003
注意,特别地,对于 pmpaddr[0]:0≤pmpaddr[0]<pmpaddr[1] \text{0} \leq \text{pmpaddr[0]} < \text{pmpaddr[1]} 0≤pmpaddr[0]<pmpaddr[1]
三、战场复盘:什么情况必须请出 TOR 这把“手术刀”?
当你的代码尝试只使用 pmpcfg.A = NAPOT 配置以下区域时,硬件会直接报错或产生非预期结果。
场景 1:无法整除的“尴尬起点”
问题: 起始地址 0x1003,大小 64KB。
NAPOT 校验: 0x1003 % (64*1024) != 0 ❌ 非法。
解决策略: 切一刀。
[ 解剖图 ]
0x1000 (合法对齐点) 0x1003 (真实起点) 0x11003
| | |
├───────── TOR ───────────┼─────────── NAPOT ───────────────┤
| (补丁区域) | (主体 64KB) |
└─────────────────────────┴─────────────────────────────────┘
场景 2:权限的“三八线”
问题: 同一块物理连续内存 0x20000000 - 0x20010000,前半段要执行代码 (X),后半段存只读数据 ®。
NAPOT 困境: NAPOT 块内权限统一。如果用一个大块覆盖,要么代码不能跑,要么数据能被当指令执行(安全隐患)。
解决策略: 权限隔离必用 TOR 切分边界。
场景 3:碎片的“收容所”
问题: 还剩 3KB 的区域没用。
NAPOT 表示: 最小颗粒是 8 字节 (N=0),但 3KB 不是 2 的幂,NAPOT 只能拆成 2KB + 1KB,甚至更碎。
工程实践: 最后那点不规则的小尾巴,直接用一个 TOR Entry 收尾,干净利落。
四、组合拳法:四大实战模型 (含动效图解)
核心思想:用 NAPOT 跑车拉货(运大块),用 TOR 叉车理货(摆正边角)。
模型 1:左切 —— 头部不对齐
场景: 区域从 0x1003 到 0x20000。
地址轴:
0x1000 0x1003 0x2000 0x20000
| | | |
|<-- TOR #0 -->|<----------------- NAPOT (起始对齐于 0x2000) ---------->|
| (修补头部) | (8KB/16KB/... 大块) |
^ ^
对齐边界 实际起点
配置逻辑:
1. TOR [0x1000, 0x1003) : 过渡区 (或无权限)
2. NAPOT [0x2000, ...) : 加载主体逻辑
模型 2:右切 —— 尾部残缺
场景: 大块 Flash (0x08000000 ~ 0x0804B000)。
地址轴:
0x08000000 0x08040000 0x0804B000
| | |
|<------- NAPOT (256KB 大块) ------->|<--- TOR #1 ---->|
| | (修补 44KB) |
^ ^
完美对齐 此处开始不对齐
配置逻辑:
1. NAPOT 覆盖 256KB 的倍数部分。
2. 剩余 44KB 使用 TOR 定义精确上界。
模型 3:中分 —— 权限隔离 (最关键)
场景: code (X) 紧挨着 flash_data (NX)。
物理内存视图:
┌─────────────────┬─────────────────┐
│ │ │
│ CODE (X) │ DATA (NX) │
│ │ │
└────────┬────────┴────────┬────────┘
│ │
└─── 边界线 ──────┘
这里必须用 TOR 对齐精确地址,否则权限会泄漏!
错误做法:[ 一个大 NAPOT (X or NX?) ] ❌ 灾难
正确做法:
[ NAPOT (X) ] [ TOR 边界 ] [ NAPOT (NX) ]
模型 4:真实战场推演方案
资源需求: code = 504KB, flash = 7688KB

方案一:单 NAPOT + 优先级 + TOR 补充策略
Step 1:Code 区域优化处理
- 原始 Code 大小:
504KB(非 2^N 对齐) - 优化方案:向上对齐至
512KB - 优势:
- 仅需 1 个 NAPOT entry
- 保留执行权限(X)
- 避免多 entry 拆分
Step 2:Flash 区域处理
- 原始 Flash 大小:7688KB
情况一:地址优先级递增(低→高)
- 策略:
- Flash 调整为
7680KB(保留8KB缓冲) - Code 扩展至
512KB
- Flash 调整为
- 内存布局:
| code (512KB, X) | flash (7680KB, RW) | - 特点:
- 无地址冲突
- 权限清晰
- 存在少量空间浪费
情况二:地址优先级递减(高→低)
- 策略:
- Flash 扩展至8MB(
8192KB) - Code 保持
512KB
- Flash 扩展至8MB(
- 内存布局:
[Flash 8MB RW](基础配置) ↓ 权限覆盖 [Code 512KB X](优先配置) - 机制:
- 利用PMP后配置优先特性
- 实现权限自动修正
方案对比
| 方案 | Code | Flash | Entry数 | 特点 |
|---|---|---|---|---|
| 拆分NAPOT | 504KB | 多块 | 多 | 通用但效率低 |
| 单NAPOT(递增) | 512KB | 7680KB | 少 | 安全稳妥 |
| 单NAPOT(递减) | 512KB | 8MB | 最少 | ⭐ 最优方案 |
TOR补充策略应用场景
-
边界不对齐情况
- 解决代码与Flash间的间隙问题
- 使用TOR精确覆盖间隙区域
-
严格保持Code尺寸
- 当必须保持504KB时
- 采用NAPOT拆分+TOR补充方案
-
细粒度权限控制
- 混合使用NAPOT和TOR
- 实现代码、只读数据、Flash的差异化权限
方案演进
初级方案:
- 强制拆分504KB
- 导致entry数量多
- 实现不够优雅
工程级方案:
- 允许合理扩展
- 善用覆盖优先级
- 核心思路:从"精确匹配"转向"最优覆盖"
实施条件
单NAPOT最优方案需满足:
- 允许区域适当扩展(2^N对齐)
- 合理利用PMP优先级机制
- 确保权限配置无冲突
五、避坑指南:PMP 优先级与 Entry 编排艺术
RISC-V PMP 的匹配逻辑是静态优先级:
pmp7cfg > pmp6cfg > … > pmp0cfg
这会导致一种常见的覆盖效应。
实用心法:
“底层的铺地板,上层的贴瓷砖。”
错误示范:
- Entry 0:
TOR保护0x0 - 0x1000(RW) - Entry 1:
NAPOT保护0x0 - 0x2000® - 结果: 访问
0x500时,Entry 0 先命中,拿到 RW 权限;Entry 1 永远没机会匹配0x0-0x1000区间。
正确编排顺序(自底向上配置):
- Entry 0 (低优先级): 放最大的背景权限(例如整个 Flash 设为只读)。
- Entry N (高优先级): 放特殊的、需要提升权限的小块(例如某个 ROM 函数需要执行权限)。
┌──────────────────────────────────────┐
│ Entry 7: [ 特殊小块 X ] │ ← 优先级最高,盖在下面
├──────────────────────────────────────┤
│ Entry 6: [ 中等区域 RW ] │
├──────────────────────────────────────┤
│ Entry 0: [ 巨大背景区域 R ] │ ← 优先级最低,铺底
└──────────────────────────────────────┘
六、总结:工程师的 PMP 配置口诀
在实际写代码配置 PMP 时,请默念以下四句口诀:
- 先断权限后画图 (按 X/W/R 把内存切段)。
- 能对齐者用 NAPOT (效率优先,省 Entry)。
- 遇门不对找 TOR (起点不对齐、权限切换点、碎尾巴)。
- 小块在上大在下 (优先级倒置,特例覆盖通例)。
掌握了 TOR 与 NAPOT 的组合拳,你不仅能省下宝贵的 PMP Entry,更能写出健壮、无漏洞的 RISC-V 底层安全代码。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)