前言
最近在项目开发中,需要从 REE(Rich Execution Environment,即普通执行环境)触发一个需要更高异常等级才能执行的操作。最终,我们通过 SMC(Secure Monitor Call)接口实现了这一功能。借此机会,也系统学习了一下 SMC 接口的使用方法。
ARM SMC(Secure Monitor Call)接口是 ARM 架构中用于在非安全世界(Normal World)和安全世界(Secure World)之间进行上下文切换的关键机制,其底层基于 ARM TrustZone 技术实现。通过执行 SMC 指令,软件可以从当前运行环境(例如 Linux 内核所在的非安全世界)切换到安全监控器(Secure Monitor),从而与运行在安全世界的可信执行环境(TEE,Trusted Execution Environment)进行安全交互。
一、SMC接口的基本原理
触发方式
通过执行SMC汇编指令触发异常,指令中可通过寄存器传递参数,用于指定操作类型及相关数据。世界切换
执行SMC指令后,处理器会从当前异常等级(如 EL1,对应内核态)切换到安全监控器所在的更高异常等级(如 EL3,适用于 ARMv8 及以上架构)。安全监控器的作用
运行在 EL3 的安全监控器负责解析 SMC 请求,判断其合法性,并决定是否允许访问安全世界资源,最后将结果返回给调用方。
二、SMC调用的参数传递
在 ARM 架构中,SMC 调用的参数通过通用寄存器传递,具体规范如下(不同架构或 TEE 实现可能略有差异):
| 寄存器 | 调用时用途 | 返回时用途 |
|---|---|---|
x0 |
SMC 功能号(Function ID) | 返回状态码或数据 |
x1-x7 |
输入参数 | 输出参数 |
x8 |
部分实现中用于传递 SMC 状态 | - |
- 功能号(Function ID):用于标识具体的 SMC 操作,通常由芯片厂商或 TEE 系统定义,例如在 OP-TEE 中,功能号通常以
0x32000000开头。 - 参数传递限制:最多可通过
x1-x7传递 7 个参数,若参数数量超出或需要传递复杂数据结构,可通过共享内存的方式传递其物理地址。
三、SMC调用的类型
以 ARMv8 为例,SMC 调用主要分为以下几类:
标准 SMC(Standard SMC)
由 ARM 架构标准定义,功能号通常以0x80000000开头,用于基本的世界切换或系统信息查询。厂商自定义 SMC(Vendor SMC)
由芯片厂商或 TEE 系统自定义实现,功能号范围通常为0x00000000-0x7FFFFFFF。Hypervisor SMC
用于虚拟化场景,与 Hypervisor 交互,功能号以0x40000000开头。
四、示例:Linux内核中的SMC调用
在 Linux 内核中,可以通过 smc() 或 hvc()(用于虚拟化调用)函数触发 SMC,以下是一个基于 ARMv8 汇编的简化示例:
1 | // 函数:smc_call |
在 C 代码中,可以使用内联汇编或调用内核封装好的接口:
1 |
|
arm_smccc_smc()是 Linux 内核中封装好的 SMC 调用函数,定义在include/linux/arm-smccc.h中,会自动处理寄存器传递和结果保存。
五、注意事项
权限控制
SMC 调用通常需要在内核态执行,用户态程序直接执行SMC指令会触发异常,因此一般需通过系统调用(如ioctl)间接调用内核中的 SMC 接口。安全限制
安全监控器会对所有 SMC 请求进行安全检查,非法请求会被拒绝并返回错误码。兼容性
不同 ARM 架构(如 ARMv7 与 ARMv8)及不同 TEE 实现(如 OP-TEE、Trusty)之间的 SMC 功能号及参数规范可能存在差异,开发时应参考对应平台的说明文档。
六、测试 Demo
以下是一个基于 Linux 内核模块的 SMC 测试示例,通过设备节点供用户态触发 SMC 调用:
1 | // 定义设备名称 |
测试结果如图所示:
完整代码已开源,可在Gitee仓库查看
总结
通过 SMC 接口,系统能够在不同安全世界之间建立可控的通信机制,使得敏感操作(例如加密计算、密钥管理与安全存储)可以在受保护的环境中执行,从而有效防止非安全世界直接访问关键资源,极大提升了系统的整体安全性。
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。

博客地址: https://fishmwei.github.io
掘金主页: https://juejin.cn/user/2084329776486919