3. 安全世界与普通世界
TrustZone 的核心思想并不是把处理器“劈成两颗 CPU”,而是在同一颗物理核上建立两个相互隔离的执行环境:安全世界(Secure World)与普通世界(Normal World)。两者分时复用同一套寄存器、流水线和缓存资源,但通过硬件扩展在关键路径上强制隔离。连接这两个世界的唯一合法通道,就是安全监视器(Secure Monitor),而软件触发切换的标准入口是 SMC(Secure Monitor Call)指令。理解世界切换的完整流程与上下文保存恢复机制,是后续外设隔离、TZASC/TZPC 配置和 TEE 驱动开发的基础。
一、核心原理:三个世界与一个监视器
从 ARMv7 的 Security Extensions 到 ARMv8-A 的 EL3,TrustZone 都定义了三个逻辑执行层级:普通世界、安全世界,以及位于两者之间的安全监视器。安全监视器运行在最高特权级(ARMv8-A 中为 EL3),是唯一能够访问两个世界状态并执行切换的代码。它通常由 SoC 厂商提供的 BL31(ATF,Arm Trusted Firmware)实现,而不是由普通 OS 或 TEE 自行编写。
关键点在于:普通世界无法直接跳转到安全世界。任何跨世界调用都必须先陷入安全监视器,由监视器决定是否放行、如何传递参数、以及切换到哪个世界的哪个入口。这种“必经中间人”的设计,使得安全监视器成为整个信任链的咽喉,也是攻击面收敛的重点对象。
二、SMC 调用:跨世界的标准入口
SMC 是一条由软件主动执行的指令,用于请求安全监视器服务。它既可以被普通世界调用(如 Linux 内核的 PSCI、OP-TEE 驱动),也可以被安全世界调用(如 TEE 请求监视器切换回普通世界)。SMC 的调用约定由 SMC Calling Convention(SMCCC) 规范定义,ARMv8-A 中通常使用 SMC #0,并通过寄存器传递功能 ID 与参数。
典型的 64 位调用约定如下:
// 普通世界发起 SMC 调用(SMCCC 64-bit)
// x0 = Function ID (FID)
// x1..x6 = 参数
// 返回值:x0 = 返回码,x1..x3 = 返回值
register uint64_t x0 asm("x0") = fid;
register uint64_t x1 asm("x1") = arg0;
register uint64_t x2 asm("x2") = arg1;
asm volatile("smc #0"
: "+r"(x0), "+r"(x1), "+r"(x2)
:
: "x3","x4","x5","x6","x7","memory");
FID 的高位字段区分调用类型:0x02000000 段通常为 PSCI,0x80000000 段为 OEM/TEE 自定义服务,0xC0000000 段为 SiP(Silicon Provider)服务。OP-TEE 的 OPTEE_SMC_FUNCID_CALLS 就属于自定义服务范围。调用者必须严格遵循约定,否则监视器可能返回 NOT_SUPPORTED 或直接触发异常。
三、世界切换流程:从 SMC 到目标世界
一次完整的世界切换可以拆解为“陷入—判定—保存—恢复—返回”五个阶段。下图展示了普通世界通过 SMC 进入安全世界、再由安全世界返回的完整信号与状态流转关系。
流程中的关键动作是:SMC 触发同步异常,处理器进入 EL3;监视器读取 ESR_EL3 判断异常类型,读取 SCR_EL3.NS 判断来源世界;随后根据 FID 分派到对应服务。若需要进入安全世界,监视器会保存普通世界的通用寄存器与系统状态,设置 SCR_EL3.NS=0,配置 SPSR_EL3 与 ELR_EL3 指向安全世界入口,最后执行 ERET 完成切换。
四、上下文保存与恢复:隔离的“账本”
上下文保存不是简单地把所有寄存器压栈,而是有选择地保存“跨世界可见状态”。监视器必须保证:普通世界的寄存器内容不会泄露给安全世界,安全世界的密钥、会话状态也不会被普通世界窥探。因此,切换时至少需要处理以下几类状态:
- 通用寄存器:x0–x30、SP、PC、SPSR,通常保存在监视器为每个世界维护的独立栈或结构体中。
- 系统控制寄存器:SCR_EL3、SCTLR、TTBR0/TTBR1、VBAR 等,需按世界分别配置。
- 浮点与 SIMD:FPCR、FPSR、V0–V31,是否保存取决于是否允许两个世界共享 FPU,出于安全考虑通常需要隔离。
- 中断与异常状态:DAIF 屏蔽位、FIQ/IRQ 路由,避免安全世界中断被普通世界劫持。
- 缓存与 TLB:通过 NS 位标记,切换时可能需要执行 TLBI 或 cache 维护操作。
五、案例场景:一次 OP-TEE 安全服务调用
以 Linux 用户态通过 OP-TEE 驱动请求安全服务为例,完整链路如下:
- 用户态调用
ioctl(TEE_IOC_OPEN_SESSION),进入 Linux 内核 OP-TEE 驱动。 - 驱动将请求封装为共享内存,准备 FID 与参数,执行
smc #0。 - CPU 陷入 EL3,BL31 保存普通世界上下文,切换到 OP-TEE OS。
- OP-TEE OS 解析请求,调度对应 TA(Trusted Application)执行。
- TA 完成后,OP-TEE OS 通过 SMC 返回监视器,监视器恢复普通世界上下文并
ERET。 - Linux 驱动读取返回值,唤醒用户态,完成一次跨世界调用。
整个过程对用户态是透明的,但每一次切换都伴随着上下文保存、缓存/ TLB 维护和异常级别跳转,因此 SMC 的调用开销远高于普通函数调用。在高频场景中,应尽量批量传递参数、减少切换次数。
六、关键参数与检查清单
| 检查项 | 说明 | 典型值/位置 |
|---|---|---|
| SCR_EL3.NS | 标识当前执行世界,0=安全,1=普通 | EL3 系统寄存器 |
| SMC FID | 功能 ID,决定监视器分派路径 | x0 传入,如 0x02000000 |
| ELR_EL3 / SPSR_EL3 | 保存返回地址与处理器状态 | 切换前由监视器设置 |
| VBAR_EL3 | EL3 异常向量基址 | BL31 初始化时配置 |
| DAIF | 中断屏蔽位,切换时需谨慎处理 | PSTATE 字段 |
| FP/SIMD 保存 | 是否隔离浮点上下文 | 由 SCR_EL3 与实现决定 |
SCR_EL3.NS 在切换前后是否符合预期;以及普通世界是否错误地假设了某些寄存器会被保留。多数“随机死机”都源于上下文约定被破坏。
安全监视器、SMC 调用与世界切换流程共同构成了 TrustZone 的“世界间交通规则”。只有把上下文保存与恢复做对,后续的外设隔离、内存分区和中断路由才有可靠的执行基础。