3. OP-TEE 源码结构分析:核心目录与关键文件
拿到 OP-TEE 源码后,很多人第一反应是「从哪看起?」。我当年也踩过这个坑——对着几十个文件夹发呆。其实它的结构非常清晰,说白了就四个核心目录加几个关键文件。今天咱们就把这块彻底捋清楚。
核心要点:OP-TEE 源码按功能分层,core/ 是内核态、lib/ 是库、ta/ 是可信应用、scripts/ 是工具链。理解这个分层,你就能快速定位问题。
3.1 四层架构:core/、lib/、ta/、scripts/
先看整体结构。我习惯用一张图来理解它:
3.2 core/ 目录深度解析
这是 OP-TEE 的心脏。所有安全世界的内核代码都在这里。我刚开始调优时,90% 的问题都出在这个目录里。
| 子目录/文件 | 作用 | 我踩过的坑 |
|---|---|---|
| arch/arm/ | 架构相关代码,含启动、异常向量、MMU | MMU 配置错了,TA 直接 panic |
| kernel/ | 线程管理、调度、IPC | 线程栈大小设太小,跑复杂 TA 就崩 |
| tee/ | 系统调用实现、TA 管理、属性处理 | tee_svc.c 里加新调用,忘了注册 |
| mm/ | 内存管理、页表、缓存操作 | 缓存一致性没处理好,数据全乱 |
| drivers/ | 外设驱动(UART、SPI、RNG 等) | RNG 驱动不工作,TA 随机数全一样 |
| lib/ | 内核态库(libtomcrypt、libutils) | 链接顺序错了,符号找不到 |
我的建议:第一次看 core/,先读 kernel/thread.c 和 tee/tee_svc.c。理解了线程怎么跑、系统调用怎么分发,其他模块就顺了。
3.3 三个关键文件:entry.S、tee_svc.c、thread.c
这三个文件是 OP-TEE 的「三驾马车」。我每次移植新平台,第一个改的就是它们。
3.3.1 entry.S — 世界的入口
这是安全世界的启动入口和异常向量表。CPU 一进入安全模式,第一条指令就在这里。
/* core/arch/arm/kernel/entry.S — 简化版启动流程 */
.section .text.boot
.global _start
_start:
/* 1. 设置栈指针 */
ldr sp, =stack_top
/* 2. 清零 BSS 段 */
ldr r0, =__bss_start
ldr r1, =__bss_end
mov r2, #0
bl memset
/* 3. 初始化 MMU 和缓存 */
bl arm_init_mmu
/* 4. 跳转到 C 语言入口 */
bl main_init
嗯,这里要注意:不同 ARM 架构(Cortex-A vs Cortex-M)的 entry.S 差异很大。我曾经在 Cortex-M 上直接用了 Cortex-A 的启动代码,结果 MMU 初始化就挂了——因为 M 系列根本没有 MMU。
3.3.2 tee_svc.c — 系统调用的调度中心
所有从普通世界(REE)发来的请求,最终都会落到 tee_svc.c 里。它负责解析命令、调用对应的处理函数。
/* core/tee/tee_svc.c — 系统调用分发核心 */
TEE_Result tee_svc_do_call(struct tee_ta_session *sess,
uint32_t cmd, struct tee_ta_param *param)
{
TEE_Result res;
/* 1. 检查会话是否有效 */
if (!sess || !sess->ctx)
return TEE_ERROR_BAD_PARAMETERS;
/* 2. 调用 TA 的入口函数 */
res = sess->ctx->ops->invoke(sess->ctx, cmd, param);
/* 3. 处理返回值 */
if (res != TEE_SUCCESS)
EMSG("TA invoke failed: 0x%x", res);
return res;
}
避坑指南:我曾经在 tee_svc.c 里加了一个新的系统调用编号,但忘了更新 tee_svc_syscall_table 数组。结果调用时直接跳到了错误函数,系统崩溃。记住:加新调用必须同步更新表。
3.3.3 thread.c — 线程的命脉
OP-TEE 使用线程池来处理并发请求。thread.c 管理线程的创建、调度、同步。
| 函数 | 作用 | 典型问题 |
|---|---|---|
| thread_init() | 初始化线程池,分配栈空间 | 栈大小不足,导致栈溢出 |
| thread_alloc() | 分配一个空闲线程 | 线程数不够,请求排队超时 |
| thread_free() | 释放线程回池 | 忘记释放,线程泄漏 |
| thread_sched() | 线程调度,切换上下文 | 优先级反转,低优线程饿死 |
我记得有一次调优,发现 TA 响应越来越慢。查了半天,原来是 thread_alloc 返回了 NULL——线程池用完了。后来把 CFG_NUM_THREADS 从 2 改成 4,问题解决。
3.4 lib/ 目录:共享库的智慧
lib/ 是 OP-TEE 的「工具箱」。它分为用户态库和内核态库,但代码放在一起管理。
- libutee/:TA 开发的标准接口库,你写的 TA 都会链接它。包含
TEE_*系列 API 的实现。 - libutils/:通用工具函数,比如
malloc、memcpy、字符串操作。内核和 TA 都用。 - libmpa/:多精度大数运算库,RSA、ECC 等算法依赖它。
- libmbedtls/:mbed TLS 加密库的封装,提供对称加密、哈希、证书解析。
- libkernel/:内核态辅助函数,比如缓存操作、原子操作。
实用技巧:如果你要移植新的加密算法,优先看 libmbedtls 的接口。它已经封装好了,你只需要注册算法 ID 和回调函数。我移植 SM4 时就是这么干的,两天搞定。
3.5 ta/ 目录:可信应用的样板间
ta/ 里放的是官方示例 TA。每个 TA 都是一个独立的子目录,结构非常固定:
ta/hello_world/
├── user_ta_header.c # TA 的 UUID、堆栈大小、入口函数声明
├── entry.c # TA 的入口函数(TA_CreateEntryPoint 等)
├── Makefile # 编译规则
└── sub.mk # 子模块配置
你想想看,如果你要写自己的 TA,直接复制 hello_world 改改就行。我刚开始做 TA 开发时,就是这么干的——把 UUID 一换,逻辑一改,新 TA 就跑起来了。
3.6 scripts/ 目录:开发者的瑞士军刀
这些脚本平时不起眼,但关键时刻能救命。
| 脚本 | 用途 | 我什么时候用它 |
|---|---|---|
| sign_encrypt.py | 对 TA 进行签名和加密 | 每次编译完 TA 都要跑,否则加载失败 |
| ta_bin_to_c.py | 将 TA 二进制转为 C 数组 | 需要把 TA 嵌入到 bootloader 时 |
| symbolize.py | 解析崩溃栈,还原函数名和行号 | TA 崩溃时,用它把地址转成可读信息 |
| checkpatch.pl | 检查代码风格是否符合内核规范 | 提交代码前跑一遍,避免被 review 打回 |
重要提醒:symbolize.py 需要编译时保留符号表。我吃过亏——为了省空间把 CFG_TA_DEBUG 关了,结果 TA 崩溃后只有一堆地址,完全没法调试。调试阶段一定要开。
3.7 构建系统:Makefile 与 CMake 的博弈
OP-TEE 早期只用 Makefile,后来引入了 CMake 支持。现在两者并存,但 Makefile 仍是主流。
3.7.1 Makefile 体系
根目录的 Makefile 是总入口,它会递归调用各子目录的 sub.mk。核心变量:
# 核心构建变量
PLATFORM ?= vexpress
PLATFORM_FLAVOR ?= qemu_virt
CFG_TEE_CORE_LOG_LEVEL ?= 2
CFG_NUM_THREADS ?= 2
CFG_TA_DEBUG ?= y
# 编译命令示例
make PLATFORM=stm32mp1 PLATFORM_FLAVOR=stm32mp157c-dk2 \
CFG_TEE_CORE_LOG_LEVEL=3 CFG_NUM_THREADS=4
3.7.2 CMake 支持
CMake 主要用于跨平台构建和 IDE 集成。我个人觉得 CMake 的依赖管理更清晰,但社区还是以 Makefile 为主。
# CMakeLists.txt 片段
cmake_minimum_required(VERSION 3.10)
project(optee_os)
option(CFG_TEE_CORE_DEBUG "Enable debug" OFF)
set(PLATFORM "vexpress" CACHE STRING "Target platform")
add_subdirectory(core)
add_subdirectory(lib)
我的习惯:开发阶段用 Makefile,因为改一行配置就能重新编译,速度快。做 CI/CD 集成时用 CMake,因为依赖管理和测试框架更完善。
3.8 本章小结
OP-TEE 的源码结构其实不复杂。记住四层架构:core/ 是内核、lib/ 是库、ta/ 是应用、scripts/ 是工具。三个关键文件 entry.S、tee_svc.c、thread.c 是入门的钥匙。构建系统 Makefile 为主、CMake 为辅。
下次你遇到问题,先问自己:这个问题出在哪一层?是内核态还是用户态?是启动阶段还是运行时?定位清楚了,改起来就快了。