在前一篇文章中,我们介绍了 ESP 特权隔离机制,并借此在 ESP32-C3 SoC 上实现了“用户-内核”应用程序的相互隔离与独立。目前,您可以通过多种方法在您的项目中部署 ESP 特权隔离机制。本案例研究以 ESP RainMaker 为例,展示了如何将 ESP 特权隔离机制部署至一个真实的物联网应用程序。
ESP RainMaker 是一个完整的 AIoT 平台,可助力客户快速开发 AIoT 产品,详见这里。
如需应用 ESP 特权隔离机制,则项目的目录结构将与常规 ESP-IDF 项目略有差异。ESP 特权隔离项目在创建目录结构时,需要将程序放在两个子目录下:受保护的应用程序和用户应用程序,并保证这两个子文件夹中的应用程序均可被编译系统编译。以下为一个例子:
rmaker_switch
| — CMakeLists.txt
| — partitions.csv
| — protected_app/
| | — main/
| | — CMakeLists.txt
| | — protected_main.c
| — user_app/
| — main/
| | — CMakeLists.txt
| | — user_code.c
| — user_config.h
| — CMakeLists.txt
有关“目录结构”的详细说明,请见“ESP 特权隔离机制入门指南”文档。
根据系统调用的具体实现情况,我们可以选择将 ESP RainMaker 代理安装在两个位置:
在本文中,我们将以选项 2 为例,具体描述如何将 ESP RainMaker 代理安装至受保护的应用程序文件夹中。
上图展示了各组件在受保护应用程序和用户应用程序之间的分布情况。具体来说,所有库(如 ESP RainMaker、TLS 栈等)因承担了大部分繁重工作,都被放置在受保护的应用程序中;用户应用程序则主要包括与业务相关的轻量级应用。
构建系统将在 build 文件夹下生成 app_libs_and_objs.json 文件,该文件描述了所有库、其占用的内存及对应应用程序中包括的 object 文件。
在本案例研究中,我们选择将 ESP RainMaker 代理安装在受保护的应用程序中。这种情况下,我们必须为 ESP RainMaker 提供的所有公共 API,增加一个自定义系统调用。得益于 ESP 特权隔离机制非常易于扩展,因此增加这样一个自定义系统调用并不复杂,详见这里。
接下来,我们将以 ESP RainMaker 的公开 API esp_rmaker_start() 为例,展示如何为其增加一个自定义系统调用。首先,我们需要把 ESP RainMaker 移动至受保护的应用程序中。此后,用户应用程序中所有对 esp_rmaker_start() 的调用均将通过我们增加的自定义系统调用接口完成。下图展示了这一过程:
具体过程描述如下:
a. 首先,我们需要在用户应用程序中实现一个系统调用包装程序 (wrapper),名称即为系统调用程序名加一个 usr_ 前缀。此后,这个包装程序将通过宏 EXECUTE_SYSCALL 生成一个同步异常,并通过该异常进入受保护空间。宏 __NR_esp_rmaker_start 即为构建系统生成的系统调用号。
esp_err_t usr_esp_rmaker_start(void)
{
return EXECUTE_SYSCALL(__NR_esp_rmaker_start);
}
注意:构建系统已经将 esp_rmaker_start 关联至 usr_esp_rmaker_start,因此用户应用程序调用 esp_rmaker_start即可完成系统调用。
b. 接着,我们需要实现一个受保护应用程序的系统调用处理程序 (handler)。此后出现同步异常即可触发该处理程序,继而调用实际 API,并将错误代码返回给用户空间。
esp_err_t sys_esp_rmaker_start(void)
{
return esp_rmaker_start();
}
c. 为了绑定用户和受保护系统调用的实现,我们需要在 example 目录 (examples/rmaker_switch/components/rmaker_syscall/rmaker_syscall.tbl) 下创建一个自定义系统调用表。此时,我们需要定义四个属性:
1289 common esp_rmaker_start sys_esp_rmaker_start
此后,构建系统将通过这个系统调用表文件,来创建一个 __NR_esp_rmaker_start 宏。这个宏可以将用户应用程序的 EXECUTE_SYSCALL 调用绑定至受保护应用程序的 sys_esp_rmaker_start。
以上示例仅用于演示,具体实现请见 rmaker_syscall。
在完成这些系统调用实现后,我们可以在用户应用程序中使用 ESP RainMaker 提供的几乎所有公共 API。目前,我们已经使用受保护应用程序中添加的服务,实现了一个 IoT 开关应用程序,详见 GitHub repo。
可以看出,ESP 特权分离应用程序 bin 文件总大小与传统应用程序相仿,静态内存使用情况略有增加,但可以将单体式固件 (monolithic firmware) 划分为受保护的应用程序和用户应用程序。
首先,用户应用程序在启动代码注册 heap 和 console 后,将控制权移交给 user_main。接着,user_main 函数将初始化 ESP RainMaker 代理,创建一个 RainMaker 设备,并通过系统调用接口在受保护的应用程序中启动 ESP RainMaker 代理。受保护的应用程序管理 Wi-Fi 连接,并提供所有 RainMaker 服务。
一旦完成 Wi-Fi 配网和连接,受保护的应用程序将与 RainMaker 云建立 TLS 连接。在此之后,我们的 ESP32-C3 设备已准备好接收来自云端的事件。受保护的应用程序接收所有云事件,并根据配置触发用户空间回调。
我们的 ESP RainMaker 开关示例可见 ESP 权限隔离 repo,欢迎您尝试项目开发或提供意见反馈(可直接在 GitHub 上提交 issue)。