4. TZASC 的工作原理:内存隔离的“交通警察”
好,咱们进入正题。上一章我们聊了系统内存映射,知道了安全世界和非安全世界各自的地盘。但有个关键问题:谁来看住这些地盘?谁在物理层面阻止非安全世界访问安全内存?
答案就是 TZASC——TrustZone 地址空间控制器。我习惯叫它“内存交通警察”。
说白了,TZASC 就是挂在系统总线上的一个硬件防火墙。它盯着每一个内存访问请求,检查发起者是谁、目标地址在哪、想干什么。如果不符合规则,直接掐断。
4.1 TZASC 在系统中的位置
先看一张图,你就明白它待在哪了。
看到没?TZASC 卡在总线和内存之间。所有主控——CPU、DMA、GPU——发出的访问请求,都得先经过它。它手里有一张“规则表”,对照着决定放行还是拦截。
核心要点:TZASC 不关心你运行的是哪个操作系统。它只认硬件信号——AXI 总线上的 NS 属性位。这个位为 0 表示安全访问,为 1 表示非安全访问。
4.2 区域(Region)的概念
TZASC 把物理内存划分成若干个区域(Region)。每个区域就是一段连续的地址空间,有自己的安全属性配置。
我记得第一次看 TZASC 的寄存器手册时,被一堆 Region 配置项搞晕了。后来我总结了一个简单的理解方式:
每个 Region 就是一个“内存段”,你告诉 TZASC:
- 这段内存从哪开始(基地址)
- 到哪结束(大小)
- 谁可以访问(安全/非安全/混合)
- 可以做什么(读/写/执行)
举个例子,我在一个项目中这样划分:
| Region | 地址范围 | 大小 | 安全属性 | 用途 |
|---|---|---|---|---|
| Region 0 | 0x0000_0000 - 0x0FFF_FFFF | 256 MB | 非安全 | Linux 系统内存 |
| Region 1 | 0x1000_0000 - 0x100F_FFFF | 1 MB | 安全 | 安全监控器代码 |
| Region 2 | 0x1010_0000 - 0x101F_FFFF | 1 MB | 安全 | 安全世界堆栈 |
| Region 3 | 0x2000_0000 - 0x2FFF_FFFF | 256 MB | 非安全 | GPU / 多媒体缓冲区 |
我的习惯:把安全内存放在物理地址的高端或低端,不要和非安全内存交错。这样配置 Region 时不容易出错,排查问题也方便。
4.3 安全属性检查流程
当一个内存访问请求到达 TZASC 时,它内部走的是这么一套流程:
- 地址匹配:检查目标地址落在哪个 Region 范围内
- NS 位检查:读取总线上的 NS 属性位,判断是安全还是非安全访问
- 权限比对:对照该 Region 的配置,看是否允许当前访问
- 裁决:允许则放行,拒绝则返回错误响应
你想想看,这个流程是纯硬件完成的,一个时钟周期内就能搞定。所以 TZASC 不会成为系统性能瓶颈——除非你配错了。
我曾经踩过的坑:有一次我把安全 Region 的基地址写错了,导致安全世界自己都访问不到那块内存。系统一启动就挂,查了两天才发现是地址对齐问题。TZASC 对 Region 基地址有严格的对齐要求——通常是 64KB 或 1MB 对齐,具体看芯片实现。
4.4 子区域(Subregion)与细粒度控制
一个 Region 可以进一步划分成 8 个子区域(Subregion)。每个子区域可以独立配置安全属性。
为什么要这么设计?
举个例子。你有一块 1MB 的安全内存,想在里面放三样东西:安全监控器代码、安全世界堆栈、安全存储缓冲区。你可以把这 1MB 分成 8 个 128KB 的子区域,分别赋予不同的权限。
配置子区域的寄存器通常是这样的:
// 伪代码:配置 Region 1 的子区域
// 假设 Region 1 基地址 0x1000_0000,大小 1MB
// 每个子区域 128KB
// 子区域 0-2: 安全,可读可执行(代码区)
TZASC->REGION1_SUBREGION_CTRL = 0x00000007;
// 子区域 3-4: 安全,可读可写(堆栈区)
TZASC->REGION1_SUBREGION_CTRL |= 0x00000018;
// 子区域 5-7: 安全,可读可写(存储区)
TZASC->REGION1_SUBREGION_CTRL |= 0x000000E0;
嗯,这里要注意:子区域的粒度不能太细。每个子区域至少是 64KB,这是硬件决定的。你想保护一个 4KB 的缓冲区?不行,得用其他机制。
4.5 锁定机制:一次配置,永不更改
TZASC 有一个很“绝”的特性——锁定(Lockdown)。一旦你把某个 Region 的配置锁定,除非硬件复位,否则谁也改不了。
我特别喜欢这个设计。为什么?
- 防止非安全世界的恶意软件篡改安全配置
- 防止安全世界的代码 bug 意外改掉内存保护
- 满足一些安全认证(比如 GP TEE 认证)的要求
锁定操作很简单,往锁定寄存器写一个特定的值就行:
// 锁定 Region 0 的配置
TZASC->REGION0_LOCK = 0x1; // 写入 1 即锁定
// 有些芯片需要写特定的解锁序列才能再次配置
// 比如:先写 0xC5, 再写 0x3A, 再写 0x1
// 具体看芯片手册
实战建议:在安全监控器(Secure Monitor)的初始化代码中,把所有安全 Region 配置好,然后立即锁定。之后安全世界和非安全世界都只能使用,不能修改。这是 TrustZone 内存隔离的“最后一道锁”。
4.6 多 TZASC 实例与复杂系统
在一些复杂的 SoC 中,可能不止一个 TZASC。比如:
- 一个 TZASC 管理 DDR 内存
- 另一个 TZASC 管理片上 SRAM
- 第三个 TZASC 管理外设地址空间
每个 TZASC 独立工作,互不干扰。配置时你得分别初始化它们。
我记得在一个多核芯片项目中,我们用了三个 TZASC。调试的时候发现一个奇怪的问题:非安全世界的 DMA 能读到安全内存的数据。查了半天,原来是第二个 TZASC 的 Region 配置漏掉了——DMA 走的是另一条总线路径,绕过了第一个 TZASC。
排查技巧:遇到内存隔离失效时,先画出系统的总线拓扑图。搞清楚每个主控的访问路径经过哪些 TZASC。然后逐个检查每个 TZASC 的 Region 配置。别偷懒,这一步省不了。
4.7 性能考量
TZASC 会增加内存访问的延迟吗?会,但微乎其微。
现代 TZASC 的实现都是流水线化的。地址匹配和权限检查在地址传输阶段就完成了,不会额外插入等待周期。我实测过,开启 TZASC 保护对内存带宽的影响不到 1%。
但有一个例外:Region 数量太多。每个 TZASC 支持的 Region 数量是有限的,通常是 8 个或 16 个。如果你把 Region 用满,地址匹配的逻辑会变复杂,延迟会略微增加。所以我的建议是:够用就好,别把 Region 全用上。
| Region 数量 | 典型延迟增加 | 适用场景 |
|---|---|---|
| 4 个 | < 0.5 ns | 简单系统,少量安全内存 |
| 8 个 | < 1 ns | 大多数嵌入式系统 |
| 16 个 | 1-2 ns | 复杂 SoC,多安全域 |
这些数字是大概的参考值,具体看芯片工艺和频率。但你可以放心,TZASC 不会是性能瓶颈。
4.8 小结
TZASC 的工作原理,说白了就是:基于地址的安全访问控制。它用 Region 划分内存,用 NS 位区分世界,用硬件逻辑保证隔离。
配置 TZASC 时,记住三个要点:
- 地址要对齐——基地址和大小必须符合 Region 的对齐要求
- 权限要最小——只给必要的访问权限,不要图省事全开
- 配置要锁定——初始化完成后立即锁定,不留后门