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