3. OP-TEE系统架构

要真正理解 OP-TEE Trusted Application 的开发方式,必须先建立一张清晰的系统架构图。OP-TEE 并不是一个孤立的库,而是一套横跨两个执行世界、多个软件层次的完整可信执行环境。它的核心思想是:把敏感逻辑放进 Secure World,把普通业务留在 Normal World,两者通过受控的通信通道协作。只有理解了这条链路上每一层的职责,才能写出正确的 TA 与 CA。

一、双世界架构:Normal World 与 Secure World

ARM TrustZone 把 CPU 划分为两个安全状态:Normal World(非安全世界)和 Secure World(安全世界)。Normal World 运行 Linux 等通用操作系统,负责 UI、网络、存储等普通业务;Secure World 运行 OP-TEE OS,负责密钥管理、加解密、身份认证等敏感操作。两者物理上共享同一颗 CPU,但通过 TrustZone 硬件隔离,Normal World 无法直接读取 Secure World 的内存。

两个世界之间的切换由 SMC(Secure Monitor Call)指令触发,经过 EL3 的 Secure Monitor 完成上下文保存与状态切换。这意味着一次 TA 调用并不是普通函数调用,而是一次跨越安全边界的“世界切换”,其开销远高于普通系统调用,这也是 TA 设计要尽量减少跨世界往返次数的原因。

二、OP-TEE 的软件分层

从下到上,OP-TEE 的软件栈可以划分为几个关键层次,每一层都有明确的职责边界:

  • OP-TEE OS(Secure World 内核):运行在 Secure EL1,负责 TA 的加载、调度、内存隔离、系统调用处理,以及密码学原语、安全存储等核心服务。
  • TEE Supplicant(Normal World 用户态守护进程):运行在 Linux 用户态,代替 Secure World 完成它无法直接做的文件系统操作,例如加载 TA 二进制、读写安全存储文件。
  • Linux 内核驱动(optee.ko):提供 /dev/tee0 与 /dev/teepriv0 设备节点,负责 SMC 调用、共享内存管理以及和 TEE Supplicant 的 RPC 交互。
  • 用户态库(libteec / optee_client):为 CA 提供 TEEC_InitializeContext、TEEC_OpenSession、TEEC_InvokeCommand 等标准 API。
  • CA 与 TA:CA 是 Normal World 的客户端应用,TA 是 Secure World 中真正执行敏感逻辑的可信应用。
关键点:TA 运行在 Secure World,但它不能直接访问文件系统。当 TA 需要读取持久化数据时,会通过 RPC 请求 Normal World 的 TEE Supplicant 代为完成,结果再回传给 TA。

三、通信机制与调用流程

一次完整的 TA 调用,数据流大致如下:CA 调用 libteec 的 TEEC_InvokeCommand,libteec 通过 ioctl 进入 Linux 内核驱动;驱动准备共享内存并执行 SMC 陷入 Secure World;OP-TEE OS 根据 session 找到目标 TA,把参数映射进 TA 地址空间并调用对应 command;TA 执行完毕后原路返回结果。若 TA 中途需要文件操作,则触发 RPC,由驱动通知 TEE Supplicant 处理,处理完再回到 Secure World 继续执行。

Normal World CA 客户端应用 TEEC_OpenSession / InvokeCommand 用户态库 libteec 封装 ioctl 与共享内存 Linux 内核驱动 optee.ko /dev/tee0 /dev/teepriv0 TEE Supplicant 加载 TA / 安全存储文件读写 Linux 文件系统 / 存储介质 Secure World TA 可信应用 TA_InvokeCommandEntryPoint TEE Core API 内存 / 会话 / 密码学服务 OP-TEE OS 内核 TA 调度 / 隔离 / 系统调用 安全存储 / 密钥管理 加密后交由 Supplicant 落盘 TrustZone 硬件隔离 SMC / EL3 SMC 调用 RPC 请求

图中可以清楚看到:Normal World 与 Secure World 之间只有两条主要通道——向下的 SMC 调用和向上的 RPC 请求。CA 与 TA 之间并不存在直接内存共享,所有参数都通过共享内存加描述符的方式传递。

四、典型场景:一次安全存储写入

假设 TA 需要把用户密钥写入安全存储,流程如下:

  1. CA 调用 TEEC_InvokeCommand,请求写入密钥。
  2. libteec 通过 ioctl 进入 optee.ko,驱动执行 SMC 切换到 Secure World。
  3. OP-TEE OS 调度目标 TA,TA 调用 TEE Core API 加密密钥。
  4. TA 需要落盘,触发 RPC;OP-TEE OS 通过驱动通知 TEE Supplicant。
  5. TEE Supplicant 在 Linux 文件系统中写入加密后的数据文件。
  6. 结果回传 TA,TA 返回成功,CA 收到返回值。
CA: TEEC_InvokeCommand(session, CMD_STORE_KEY, &op, &err)
  -> libteec: ioctl(/dev/tee0, IOCTL_INVOKE)
  -> optee.ko: smc(OPTEE_SMC_CALL_WITH_ARG)
  -> OP-TEE OS: TA_InvokeCommandEntryPoint(CMD_STORE_KEY)
  -> TA: TEE_WriteObjectData() -> RPC -> Supplicant -> 文件系统
  -> 返回 TEEC_SUCCESS

五、常见误区与复盘

误区一:认为 TA 可以直接调用 open()、read() 等 Linux 系统调用。实际上 TA 运行在 Secure World,没有 Linux 系统调用接口,必须通过 RPC 委托 TEE Supplicant 完成文件操作。
误区二:把 TEE Supplicant 当成安全组件。它运行在 Normal World,本身并不受 TrustZone 保护,只是 Secure World 的“文件系统代理”。因此写入安全存储的数据必须由 TA 侧加密后再交给它,不能假设 Supplicant 可信。

复盘来看,理解 OP-TEE 架构的关键在于抓住三条线:世界边界(Normal 与 Secure 的隔离)、调用路径(CA → libteec → 驱动 → SMC → OP-TEE OS → TA)、RPC 回环(TA → OP-TEE OS → 驱动 → Supplicant → 文件系统)。开发 TA 时,凡是涉及资源访问的设计,都要先问一句:这件事应该由 Secure World 做,还是必须委托 Normal World 做?想清楚这一点,TA 的接口划分和命令设计就会自然清晰。

层次 运行世界 核心职责 典型接口/文件
CA 客户端 Normal World 用户态 发起会话与命令调用 TEEC_* API
libteec Normal World 用户态 封装 ioctl 与共享内存 libteec.so
Linux 驱动 Normal World 内核态 SMC 调用、RPC 转发 optee.ko、/dev/tee0
TEE Supplicant Normal World 用户态 加载 TA、安全存储落盘 tee-supplicant
OP-TEE OS Secure World TA 调度、隔离、系统服务 tee_core、TEE Core API
TA Secure World 执行敏感业务逻辑 TA_InvokeCommandEntryPoint

掌握这张分层表,就等于掌握了 OP-TEE 的“地图”。后续开发 TA 时,无论是配置交叉编译、定义命令 ID,还是排查调用失败,都可以回到这张地图上定位问题发生在哪一层、属于哪个世界。