2. 车载电子电气架构演进
各位好,我们开始第二章。
上一章聊了多域隔离的整体思路。今天要落地了——看看硬件层面到底怎么把“隔离”这件事做实。
你想想看,软件做得再花哨,如果 CPU 本身不配合,那隔离就是纸糊的。我早年做过一个项目,纯靠 MMU 做分区,结果一个内核模块的指针飞了,整个系统直接崩掉。从那以后,我对硬件隔离机制就特别较真。
今天咱们重点看三种主流方案:ARM TrustZone、RISC-V PMP、Intel MPK。它们思路不同,但目标一致——在硬件层面划出不可逾越的“红线”。
ARM TrustZone:系统级的世界分裂
TrustZone 是 ARM 在 ARMv6 之后引入的。它的核心思想很简单:把整个硬件系统切成两个世界——安全世界(Secure World)和非安全世界(Normal World)。
怎么切的?靠一根物理信号线,叫 NS bit(Non-Secure)。
这根线连在总线上。CPU、内存、外设,所有支持 TrustZone 的组件都能看到这个信号。NS=0 表示安全访问,NS=1 表示非安全访问。就这么一个比特,决定了你能不能访问某块内存、某个寄存器。
关键点:TrustZone 不是虚拟化。它不跑两个 OS,而是同一个 CPU 在不同时间片里切换“世界”。切换靠一条专用指令——SMC(Secure Monitor Call)。
我习惯把 TrustZone 理解成“硬件层面的内核态与用户态”。只不过这次,隔离的不是特权级,而是两个完整的软件栈。
TrustZone 的典型架构
看个实际布局:
Normal World (非安全) Secure World (安全)
+------------------+ +------------------+
| Rich OS | | Trusted OS |
| (Linux/Android)| | (OPTEE/Trusty) |
| | | |
| Normal App | | Trusted App |
+--------+---------+ +--------+---------+
| |
| SMC 指令 / 中断 |
+-----------+ +-----------+
| |
+-----v-----v-----+
| Monitor Mode |
| (EL3 / Secure |
| Monitor) |
+-----------------+
Monitor Mode 是 TrustZone 的“门卫”。它运行在最高特权级(EL3),负责处理世界切换。每次 SMC 触发,Monitor 保存当前世界状态,恢复另一个世界状态,然后切过去。
我的经验:TrustZone 的切换开销大概在几百纳秒到几微秒。如果你需要频繁切换(比如每毫秒一次),那实时性会受影响。我建议把安全服务的调用频率控制在 1kHz 以下。
内存隔离:TZASC 与 TZPC
光靠 CPU 的 NS bit 还不够。内存和外设也得配合。ARM 提供了两个关键组件:
- TZASC(TrustZone Address Space Controller):控制内存区域的访问权限。可以配置某块 DDR 只允许安全世界访问。
- TZPC(TrustZone Protection Controller):控制外设的访问权限。比如把 UART、GPIO 分配给安全世界。
举个例子,配置一块安全内存区域:
// 伪代码:配置 TZASC 区域 0 为安全内存
TZASC_REGION_ATTRIBUTES(0) =
(BASE_ADDR << 16) | // 基地址
SIZE_1MB | // 大小 1MB
SECURE_ACCESS_ONLY | // 仅安全访问
ENABLE; // 使能
配置完之后,非安全世界的任何访问都会触发 Data Abort。硬件直接帮你拦住,软件连犯错的机会都没有。
注意:TrustZone 的配置必须在安全世界初始化阶段完成。一旦系统跑起来,非安全世界不能修改这些寄存器。这是安全模型的基础——权限只能升不能降。
RISC-V PMP:简洁而优雅的物理内存保护
RISC-V 的 PMP(Physical Memory Protection)跟 TrustZone 思路完全不同。它不搞“两个世界”,而是用一组寄存器来定义内存区域的访问权限。
说白了,PMP 就是硬件层面的“白名单”。
PMP 的工作原理
RISC-V 支持最多 16 个 PMP 区域。每个区域由一对 CSR 寄存器定义:
- pmpcfg:配置该区域的权限(读、写、执行)和地址匹配模式。
- pmpaddr:配置该区域的基地址或边界地址。
地址匹配模式有两种:
| 模式 | 描述 | 适用场景 |
|---|---|---|
| NAPOT | 自然对齐的幂次方大小区域 | 缓存、SRAM 等连续内存块 |
| TOR | Top-of-Range,由上下边界定义 | 外设寄存器、非对齐区域 |
看个实际配置例子:
// 配置 PMP 区域 0:保护 0x80000000 ~ 0x8000FFFF
// 权限:只读 + 只执行(不允许写)
// 设置地址(TOR 模式)
csrw pmpaddr0, 0x80010000 // 上边界
// 设置配置
// bit[7:0] = 0b10011001
// L=1, A=01 (TOR), R=1, W=0, X=1
csrw pmpcfg0, 0x99
配置完之后,任何尝试写入该区域的指令都会触发 访问异常。硬件直接拦截。
PMP 的优势:它不需要 MMU,不需要页表,甚至不需要操作系统。你可以在裸机上直接用。这对于实时系统来说太友好了——没有页表切换的开销,没有 TLB 刷新的延迟。
PMP 的实时性优势
我做过一个对比测试:在 RISC-V 平台上,用 PMP 做隔离 vs 用 MMU 做隔离。
| 指标 | PMP | MMU |
|---|---|---|
| 配置延迟 | ~10 个时钟周期 | ~100 个时钟周期(含 TLB 刷新) |
| 访问检查开销 | 0(硬件并行检查) | 0(硬件并行检查) |
| 上下文切换开销 | ~20 个时钟周期 | ~500 个时钟周期 |
| 最大保护区域数 | 16 | 无限制(依赖页表) |
你看,PMP 在延迟上优势明显。但缺点也很突出——只有 16 个区域。如果你需要保护几十个独立的内存块,PMP 就不够用了。
我的建议:PMP 适合做“粗粒度”隔离。比如把关键代码段、数据段、外设寄存器各分配一个区域。细粒度的隔离还是交给 MMU 或软件层处理。
Intel MPK:用户态的内存密钥
MPK(Memory Protection Keys)是 Intel 在 Skylake 之后引入的。它的思路跟 PMP 有点像,但更灵活——它允许用户态程序自己管理内存保护。
你想想看,传统的内存保护全靠内核。每次要改权限,都得调 mprotect(),进内核,切页表,开销很大。MPK 把这个过程搬到了用户态。
MPK 的工作原理
MPK 的核心是 保护密钥(Protection Key)。每个页表条目里有一个 4-bit 的密钥字段(共 16 个密钥)。同时,CPU 提供了一个专用寄存器 PKRU,里面存放着当前线程对每个密钥的访问权限。
PKRU 的结构很简单:
PKRU 寄存器 (32-bit)
+----+----+----+----+ ... +----+----+
| AD0| WD0| AD1| WD1| | AD15|WD15|
+----+----+----+----+ ... +----+----+
ADn: 第 n 个密钥的访问禁止位 (1=禁止访问)
WDn: 第 n 个密钥的写禁止位 (1=禁止写入)
关键来了:PKRU 可以在用户态用 WRPKRU 指令修改。不需要进内核,不需要切页表。
看个实际用法:
// 假设页表里已经配置了密钥:
// - 代码段: 密钥 0
// - 数据段: 密钥 1
// - 安全缓冲区: 密钥 2
// 默认情况下,所有密钥都可读写
// PKRU = 0x00000000
// 现在要保护安全缓冲区,禁止写入
uint32_t pkru = 0;
// 设置密钥 2 的 WD 位 (bit[5:4] = 10)
pkru |= (1 << 5); // WD2 = 1
_wrpkru(pkru);
// 此时,任何写入密钥 2 对应页面的操作都会触发 #GP 异常
// 但读取仍然允许
// 需要写入时,临时放开
pkru &= ~(1 << 5); // WD2 = 0
_wrpkru(pkru);
// 写入安全缓冲区...
// 写完后立即重新保护
pkru |= (1 << 5);
_wrpkru(pkru);
MPK 的实时性价值:权限切换只需要一条 WRPKRU 指令,大约 10-20 个时钟周期。相比之下,mprotect() 需要 ~1000 个时钟周期。对于需要频繁切换权限的实时任务,这个差距是致命的。
MPK 的典型应用场景
- 库隔离:把不同第三方库的数据放在不同密钥的页面里,防止互相踩踏。
- 安全缓冲区:敏感数据(如密钥、证书)平时禁止写入,只在需要更新时临时放开。
- 实时任务保护:把实时任务的数据段用独立密钥保护,防止非实时任务误写。
我曾经踩过的坑:MPK 的 PKRU 是线程私有的。如果你开了多线程,每个线程都有自己的 PKRU 值。线程切换时,OS 必须保存和恢复 PKRU。我遇到过某个 RTOS 没做这个保存,结果线程切回来之后权限全乱了。嗯,调试了两天才找到原因。
三种机制的对比与选型
咱们把三种方案放在一起看看:
| 特性 | ARM TrustZone | RISC-V PMP | Intel MPK |
|---|---|---|---|
| 隔离粒度 | 系统级(两个世界) | 物理内存区域 | 虚拟内存页面 |
| 最大保护区域 | 2(安全/非安全) | 16 | 16(密钥数) |
| 切换开销 | ~1μs(含上下文保存) | ~20 时钟周期 | ~10 时钟周期 |
| 是否需要 OS | 需要(Monitor 固件) | 不需要(裸机可用) | 需要(页表支持) |
| 用户态可配置 | 否 | 否(仅 M 模式) | 是(WRPKRU 指令) |
| 典型延迟 | 几百纳秒 ~ 几微秒 | 纳秒级 | 纳秒级 |
怎么选?我个人的经验是:
- 如果你需要完全隔离两个安全等级的系统(比如支付系统 + 通用 OS),TrustZone 是唯一选择。
- 如果你在做裸机实时系统,需要轻量级的内存保护,PMP 最合适。
- 如果你在Linux 用户态做实时任务,需要频繁切换权限,MPK 是利器。
一个实际案例:我之前做工业控制器,用了 RISC-V + PMP。把实时控制任务放在 PMP 保护的区域里,非实时通信任务放在外面。PMP 配置在启动时一次完成,运行期间不修改。整个系统的隔离开销几乎为零。嗯,这才是实时系统该有的样子。
本章小结
三种硬件隔离机制,各有各的脾气。TrustZone 是“大而全”,PMP 是“小而美”,MPK 是“快而灵”。
选择哪种,取决于你的场景:
- 安全等级差异大 → TrustZone
- 裸机实时系统 → PMP
- 用户态频繁切换 → MPK