| 项 | 内容 |
|---|---|
| 文档版本 | v1.0 |
| 日期 | 2026-09-19 |
| 研究范围 | ESP32-S3 平台上的动态扩展/多语言运行时机制 |
| 结论来源 | 源码逐文件阅读 + 构建产物实测(.so 段表/符号表解析、固件 map 归因) |
依据说明(重要)
- 文中所有事实性结论均标注出处
文件路径:行号。 - 无法从现有源码/产物直接确认的,标注
【推断】。 - 尚未核对、需要后续确认的,标注
【待核实】。 - 路径基准:除特别说明外,路径相对于
bs_hmi_base_s3/,即[project_root]/。 - Brookesia 源码全部位于
so_build/ref/。 - 本文档不含推测性评价;涉及"建议"的部分集中在第 4 章,且均给出依据。
目录
0. 结论速览
| 结论 | 依据 |
|---|---|
elf_loader 基座架构是层级式:elf_loader 是唯一执行基座,Lua 是运行在其中一个 .so 内的内容 | so_build/lua_engine/CMakeLists.txt、components/elf_loader/src/esp_elf.c |
| Brookesia 是对等式:ELF/Lua/JS/WASM 四个平级后端,共享一套宿主契约 | brookesia_runtime_manager/include/brookesia/runtime_manager/backend.hpp:19-64 |
| elf_loader 基座的加载链路强于官方 ELF 后端(加密、符号白名单、host call) | 对比见 3.3;官方证据 brookesia_runtime_elf/src/backend.cpp:101-104, 266-281 |
lua_engine(跑在基座上的 Lua 层)定位更接近官方 system_core(应用服务器),而非 runtime_lua(脚本容器) | 对比见 3.4 |
Brookesia v0.8 全栈无法在 IDF 5.5.4 落地,硬门槛是 idf: >=6.0,<=6.2 | brookesia_lib_utils/idf_component.yml:8 |
| WASM 方向的关键未知项(AOT 代码落点)已确认:S3 开 PSRAM 时 AOT 进 PSRAM | wasm-micro-runtime/core/shared/platform/esp-idf/shared_platform.cmake:18-22、espidf_memmap.c:20-51 |
lua_engine.so 已完成 -Os + 元数据段裁剪,体积 553,048 → 257,972 B(-53.4%) | 本文 1.8 节实测数据 |
1. elf_loader 基座架构
命名说明:本文档用 “elf_loader 基座架构” 指代我们当前的实现(基座固件 +
elf_loader+.so+ Lua), 而不使用"我方架构"这类表述。原因有二:
- 它描述的是架构形态(单一 ELF 执行基座)而非归属,便于与 Brookesia 的"多后端对等"形态直接对照;
- Brookesia 的
runtime_elf后端同样复用官方espressif/elf_loader(ref/brookesia_runtime_elf/idf_component.yml:5-7), 若用"我方"会掩盖"同一个 loader、两种用法"这一关键对照点。后文对比表格中该列简称为 “elf_loader 基座”。
1.1 全貌
核心特征:.so 是唯一的扩展单元;脚本(Lua)不直接由基座执行,而是由运行在 .so 内的解释器执行。
依据:
so_build/lua_engine/CMakeLists.txt:154-155的自定义链接命令(-shared -fPIC -nostdlib -nostartfiles -Wl,--allow-shlib-undefined)components/elf_loader/src/esp_elf.c:127(读文件到 PSRAM)、:432-442(pdata 分配)components/elf_ext/src/elf_so_loader.c:247-283(dlsym("app_main")→ 起任务)
1.2 基座固件(bs_hmi_base_s3)
1.2.1 平台与关键配置
| 配置 | 值 | 出处 |
|---|---|---|
| 目标芯片 | esp32s3 | sdkconfig.defaults:1 |
| IDF 版本 | 5.5.4 | _run_build.bat:4(IDF_PATH=[idf_path]/) |
| ELF 加载到 PSRAM | CONFIG_ELF_LOADER_LOAD_PSRAM=y | sdkconfig:2394 |
| 总线地址镜像 | CONFIG_ELF_LOADER_BUS_ADDRESS_MIRROR=y | sdkconfig:2389 |
| Cache 偏移 | CONFIG_ELF_LOADER_CACHE_OFFSET=y | sdkconfig:2393 |
| libc 符号表 | CONFIG_ELF_LOADER_LIBC_SYMBOLS=y | sdkconfig:2399 |
| IDF 符号表 | CONFIG_ELF_LOADER_ESPIDF_SYMBOLS=y | sdkconfig:2400 |
| 符号表预分配数 | CONFIG_ELF_LOADER_NUMBER_SYMBOLS=32 | sdkconfig:2402 |
| PSRAM malloc | CONFIG_SPIRAM_USE_MALLOC=y | sdkconfig:1279 |
| PSRAM BSS 段 | CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y | sdkconfig:1284 |
| 编译优化 | CONFIG_COMPILER_OPTIMIZATION_SIZE=y | sdkconfig:654 |
| 断言 | CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y,LEVEL=2 | sdkconfig:657,662 |
| 日志等级 | CONFIG_LOG_DEFAULT_LEVEL=3(INFO),MAXIMUM=3 | sdkconfig:1683,1687 |
1.2.2 分区
| 分区 | 类型 | 偏移 | 大小 |
|---|---|---|---|
| nvs | data/nvs | 0x9000 | 0x4000 |
| otadata | data/ota | 0xd000 | 0x2000 |
| phy_init | data/phy | 0xf000 | 0x1000 |
| lfs_storage | data/littlefs | 0x10000 | 0x700000(7 MB) |
| ota_0 | app/ota_0 | 0x710000 | 0x400000(4 MB) |
| ota_1 | app/ota_1 | 0xB10000 | 0x400000(4 MB) |
出处:partitions.csv:1-7
1.2.3 构建产物规模(实测)
| 指标 | 值 | 说明 |
|---|---|---|
| app bin | 2,106,112 B(2056.75 KB) | build/bs_hmi_base_s3.bin |
.flash.text | 1,359,624 B | 代码(IROM) |
.flash.rodata | 653,092 B | 只读数据(DROM) |
.iram0.text | 69,423 B | 内部 RAM 代码 |
.iram0.vectors | 1,028 B | 中断向量 |
.dram0.data | 22,544 B | 内部 RAM 数据 |
.dram0.bss | 30,816 B | 内部 RAM 零初始化 |
.ext_ram.bss | 122,436 B | PSRAM 零初始化段 |
出处:build/bs_hmi_base_s3.map 输出段统计(解析脚本 build/analyze_map.py)
1.2.4 组件级 flash 占用 Top(实测归因)
| 组件 | flash.text | flash.rodata | 合计 |
|---|---|---|---|
| lvgl | 230,740 | 136,141 | 366,881 |
| esp_wifi | 227,570 | 43,950 | 272,685 |
| esp_app_format | 479 | 227,504 | 227,983(见下方说明) |
| esp-fs-webserver | 42,028 | 166,617 | 208,645 |
| lwip | 109,692 | 17,483 | 127,175 |
| arduino-esp32 | 69,777 | 23,189 | 92,966 |
| elf_ext | 5,588 | 87,232 | 92,820 |
| lcd_ui | 21,070 | 43,490 | 64,560 |
| mbedtls | 50,024 | 12,240 | 62,344 |
| wpa_supplicant | 54,085 | 1,980 | 56,065 |
| mdns | 39,907 | 5,449 | 45,356 |
| esp_hw_support | 22,918 | 9,070 | 42,375 |
关于
esp_app_format的 227,983 B:该数字挂在esp_app_desc.c.obj名下,但该源文件仅 5,394 B([idf_path]//components/esp_app_format/esp_app_desc.c)。实际读取固件 rodata 后确认,这是全工程.rodata.str1.1合并字符串池(SHF_MERGE 段的 map 归属特性)。池内容实测:227,496 B / 7,746 条字符串,其中日志格式串合计 62,477 B(E (%lu)978 条、W (%lu)74 条、I (%lu)138 条、[%6u][E]170 条、I (%d)101 条)、ESP_ERR_*223 条、LVGL 断言串(lv_obj_*137 条、lv_style_*111 条)、断言文件路径(//IDF/components/*)78 条共 6,648 B。 出处:build/bs_hmi_base_s3.map:115809-115810(.rodata.__esp_system_init_fn_init_show_app_info.str1.1=0x378a8)、ELF.flash.rodata起始内容解析。
1.3 ELF 动态加载层(elf_loader)
1.3.1 内存分配策略(决定性)
// components/elf_loader/src/esp_elf_adapter.c:29-51
void *esp_elf_malloc(uint32_t n, bool exec)
{
#ifdef CONFIG_ELF_LOADER_LOAD_PSRAM
caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
#else
caps = MALLOC_CAP_EXEC / MALLOC_CAP_8BIT;
#endif
caps |= MALLOC_CAP_CACHE_ALIGNED; // IDF >= 5.3
return heap_caps_malloc(n, caps);
}
因为基座开了 CONFIG_ELF_LOADER_LOAD_PSRAM=y,.so 的 text 与 data(含 bss)全部从 PSRAM 分配。
1.3.2 加载流程
| 步骤 | 行为 | 出处 |
|---|---|---|
| 打开 | 读整个 .so 文件到 PSRAM(size 字节一块) | esp_elf.c:127-131 |
| 解密 | 若注册了 esp_elf_decrypt_cb,原地解密 ELFC 格式(AES-256-CTR) | esp_elf.c:53, 148-153 |
| 保留 payload | file->payload = pbuf; file->size = size; | esp_elf.c:155-156 |
| 分配代码 | elf->ptext = esp_elf_malloc(text_size, true) | esp_elf.c:425 |
| 分配数据 | elf->pdata = esp_elf_malloc(DATA+RODATA+BSS+DRLRO, false)(一整块) | esp_elf.c:432-442 |
| 段拷贝 | 各节按 sec[].size 拷入 ptext/pdata | esp_elf.c:445-512 |
| 释放文件缓冲 | relocate 成功后立即 esp_elf_close(&file) | dlmod.c:251 |
| 执行入口 | dlsym(handle, "app_main") → elf_task_create() 起任务 | elf_so_loader.c:247-283 |
要点:.so 文件缓冲只是加载期峰值,不常驻(dlmod.c:251)。lua_engine.so 当前 257,972 B,即单次加载峰值约 252 KB PSRAM。
1.3.3 任务与栈
// components/elf_ext/src/elf_task_api.c:111,115
core_id, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT // .so 入口任务栈默认放 PSRAM
出处:elf_ext/include/elf_task_api.h:58(文档:“默认用 PSRAM (MALLOC_CAP_SPIRAM) 分配任务栈”)
1.3.4 崩溃保护
elf_so_loader.c:166-170:s_dynamic_loading_enabled 为假时拒绝加载(crash loop protection)。
1.4 符号与宿主能力导出(elf_ext)
1.4.1 机制
基座在编译期从指定静态库用 nm 抽取符号,生成 generated/<name>_syms.c,由 elf_sym_register.c 在启动时注册进 ELF 符号表。
// components/elf_ext/src/elf_tab_syms.c:188-192(部分)
ESP_ELFSYM_EXPORT(heap_caps_malloc),
ESP_ELFSYM_EXPORT(heap_caps_calloc),
ESP_ELFSYM_EXPORT(heap_caps_get_free_size),
// components/elf_ext/src/elf_sym_register.c:55
(esp_elf_symbol_table_t *)s_tables[i]
符号表宏与容器:so_build/lua_engine/elf_api/include/private/elf_symbol.h:24-34(ESP_ELFSYM_EXPORT / esp_elfsym / ESP_ELFSYM_END)
1.4.2 符号表清单(21 张)
出处:components/elf_ext/CMakeLists.txt:74-95
gpio, uart, lfs, log, freertos, wifi, netif, event, nvs, lvgl, lcd,
libgcc, libm, newlib, ping, mdns, i2c, mcpwm, syshal, vfs
生成方式:components/elf_ext/CMakeLists.txt:97-160(symbols.py -t l -i <lib.a> -of gen_<name>_syms.c)
过滤机制:components/elf_ext/tools/filter_syms.py(支持 --drop-pattern,如对 netif 过滤 esp_netif_action_、_unsafe$、_api$ 等)
1.4.3 关键特性
| 特性 | 说明 | 出处 |
|---|---|---|
| 白名单裁剪 | 只有清单里的库 + 过滤规则通过的符号才导出 | CMakeLists.txt:74-95、filter_syms.py |
| 编译期确定 | 新增宿主 API 需重跑 CMake 生成 + 重编基座 | 同上 |
| 符号可插拔解析 | elf_set_symbol_resolver 支持自定义 resolver | elf_api/include/private/elf_symbol.h:66-124 |
| 运行时注册 | esp_elf_register_symbol / esp_elf_unregister_symbol / esp_elf_find_symbol | elf_api/include/esp_elf.h:155-177 |
1.5 Lua 运行时层(lua_engine.so)
1.5.1 组成
构建源集合(so_build/lua_engine/CMakeLists.txt:160-188):
| 分组 | 来源 |
|---|---|
| 入口 | main/*.c |
| Lua 解释器 | components/georgik__lua/lua/*.c(排除 lua.c / onelua.c) |
| 引擎 | components/lua_engine/src/*.c |
| 引擎核心 | components/lua_engine_core/src/*.c |
| I2C 驱动 | components/espressif__i2c_bus/i2c_bus_v2.c |
| 内置模块 | lua_mods/lua_module_{storage,json,i2c,gpio,mcpwm,esp_heap,system,crc16,shared_memory}/src/*.c |
Lua 版本:5.5(so_build/lua_engine/components/georgik__lua/lua/lua.h:20-22:LUA_VERSION_MAJOR_N 5 / MINOR 5 / RELEASE 0)
1.5.2 引擎能力(源码事实)
| 能力 | 位置 | 说明 |
|---|---|---|
| PSRAM 分配器 | components/lua_engine/src/lua_runtime.c:183-223 | lua_psram_allocator,MALLOC_CAP_SPIRAM |
| Lua 状态创建 | lua_runtime.c:699 | lua_newstate(lua_psram_allocator, ...) |
| 输出缓冲 | lua_runtime.c:684(LUA_OUTPUT_BUFFER_SIZE)、lua_async.c:286(LUA_OUTPUT_BUF_SIZE) | PSRAM |
| 异步作业 | components/lua_engine/src/lua_async.c | 作业槽位、互斥组、状态机、停止请求 |
| 启动日志 | components/lua_engine/src/lua_bootlog.c | 4 KB 缓冲 + vprintf hook 捕获 E 级日志 + 文件轮转 |
| 自启动 | components/lua_engine/src/lua_autorun.c | 解析 autorun.ini(delay / loadui / 脚本指令) |
| 脚本管理 | lua_script.c:186,216 | 列表读写在 PSRAM |
| 字节码 | lua_bytecode.c:65 | 校验缓冲显式用内部 RAM |
| 模块注册表 | lua_engine_core.c:20-29 | s_modules[32] |
| 共享内存绑定 | lua_mods/lua_module_shared_memory/src/lua_module_shared_memory_lua.c:820-831 | 导出 get/create/read/write/list/wait_for/read_typed/write_typed/layout/read_struct/write_struct |
| 模块清单 | components/lua_engine/src/lua_modules.c:468 | shared_memory 模块描述:“读写与基座共享的内存区域(bat_ch_data 等电池模拟器数据结构)” |
1.5.3 静态数据落点
.so 的 .data/.rodata/.bss/.data.rel.ro 被 esp_elf_malloc(..., false) 整块分配到 PSRAM(见 1.3.1),因此:
s_jobs[LUA_ASYNC_MAX_JOBS]4,480 B → PSRAMg_log_buffer4,112 B → PSRAMinstructions[20]1,680 B → PSRAM- 全部
.bss11,612 B /.data2,696 B → PSRAM
注意:源码中这些变量标注了
EXT_RAM_BSS_ATTR(lua_async.c:95-98、lua_bootlog.c:87,613),但该宏在本.so编译环境中展开为空(so_build/lua_engine/build/config/sdkconfig.h中不存在CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY),所以落的是普通.bss。当前不影响结果(loader 统一放 PSRAM),但语义上名不副实。
刻意保留在内部 RAM 的少数(有源码注释依据):
| 位置 | 内容 | 依据 |
|---|---|---|
lua_async.c:1256-1257 | s_job_lock_buf(StaticSemaphore_t + 128 B) | 注释:“必须在 init 时用 heap_caps_malloc(MALLOC_CAP_INTERNAL) 显式分配” |
lua_async.c:1273-1274 | s_slot_sem_buf | 同上 |
lua_bootlog.c:54-55 | fs 互斥量缓冲 | MALLOC_CAP_INTERNAL |
lua_bytecode.c:65 | 字节码校验缓冲 | MALLOC_CAP_INTERNAL |
lua_autorun.c:447 | auto_start 任务栈 | 注释:“必须从内部RAM分配栈” |
1.5.4 编译与裁剪现状
# so_build/lua_engine/CMakeLists.txt:150-152
# -Os:手工调用编译器绕过了 IDF 默认编译选项,不加优化会按 -O0 编译,
# 导致 .text/.rela.dyn/.got 显著膨胀(见 release 目录 .so 体积分析)。
set(so_compile_flags -c -fPIC -Os -ffunction-sections -fdata-sections)
# so_build/lua_engine/CMakeLists.txt:222-226
# .xt.prop / .xt.lit 是 Xtensa relaxation 元数据(非 ALLOC),链接完成后
# 无任何作用,占成品 .so 约 1/3 体积,直接移除(PT_LOAD 内容不受影响)。
COMMAND ${CMAKE_STRIP_SO} --strip-unneeded -R .comment -R .note
-R .xt.prop -R .xt.lit
${so_output}
其它 .so 工程(bs_link_hmi、ui_mirror、terminal、bs_link_scpi、bs_hmi_320x240)在同一次优化中做了相同改动(各自 CMakeLists.txt 的 so_compile_flags 与 strip 命令)。
1.6 共享内存数据通道
1.6.1 组件实现(基座侧)
| 能力 | API | 出处 |
|---|---|---|
| 创建区域(幂等) | shared_mem_create | components/shared_memory/src/shared_memory.c:105-165 |
| 查询区域 | shared_mem_get | shared_memory.c:167-177 |
| 等待区域 | shared_mem_wait_for | shared_memory.h:150-158 |
| 登记字段布局 | shared_mem_set_layout | shared_memory.c:348-372(深拷贝到 PSRAM) |
| 查询字段布局 | shared_mem_get_layout | shared_memory.c:375-382 |
| 销毁 | shared_mem_destroy | shared_memory.c:207(全工程无调用点,见下) |
1.6.2 布局登记链路
已登记的 7 个区域与字段数:bat_ch_data(9)、bat_ch_cfg(10)、bat_dev_state(7)、bat_ota_state(12)、bat_pending_fw(4)、bat_fps(1)、bat_mode_slots(6)
出处:state_layout.c:27-117
1.6.3 Lua 侧读取路径
// lua_module_shared_memory_lua.c:573-592
shared_mem_region_t* region = shared_mem_get(name);
int count = shared_mem_get_layout(region, out_fields);
if (count <= 0) { lua_pushnil(L); lua_pushfstring(L, "No layout registered for '%s'", name); return NULL; }
关键约束:layout / read_struct / write_struct 依赖已有登记;Lua 侧没有 set_layout API(lua_module_shared_memory_lua.c:820-831 的导出清单中无登记接口)。因此若设备上未运行带 state_layout.c 的 HMI .so,这三个接口必然返回 nil。
shared_mem_destroy 在整个 bs_hmi_base_s3/ 内只有定义、没有调用(检索结果),shared_mem_create 同名幂等(shared_memory.c:127-136),所以区域一旦创建,其 layout 不会被销毁路径清掉。
1.7 包分发与加密
| 项 | 实现 | 出处 |
|---|---|---|
| 加密格式 | [4B "ELFC"] [16B IV] [AES-256-CTR 数据] | so_build/lua_engine/CMakeLists.txt:242-246 |
| 加密工具 | so_build/lua_engine/tools/encrypt_so.py | 同上 |
| 解密时机 | esp_elf_open() 中原地解密 | so_build/lua_engine/CMakeLists.txt:245、components/elf_loader/src/esp_elf.c:53,148-153 |
| 解密密钥注册 | elf_so_crypt.c 启动时注册 | components/elf_ext/src/elf_so_crypt.c:109 |
| 发布产物 | 调试期 .so 明文;正式 .epm 加密 | so_build/lua_engine/CMakeLists.txt:232-254 |
| 加密后仍未启用 | 该段 CMake 代码当前被注释 | so_build/lua_engine/CMakeLists.txt:248-254 |
LFS 分发:lfs_storage 分区 7 MB(partitions.csv:5),.so/脚本/资源经该分区下发(multi-partition-lfs 组件)。
1.8 关键指标实测
1.8.1 lua_engine.so 优化前后
| 阶段 | 文件大小 | PT_LOAD memsz 合计 |
|---|---|---|
| 原始(默认 -O0,含 Xtensa 元数据) | 553,048 B(540.1 KB) | 392,208 B(383.0 KB) |
+ -Os | 385,060 B(376.0 KB) | 268,784 B(262.5 KB) |
+ 移除 .xt.prop / .xt.lit | 257,972 B(251.9 KB) | 268,784 B(262.5 KB) |
| 累计变化 | -295,076 B(-53.4%) | -123,424 B(-31.5%) |
1.8.2 当前 .so 段构成(257,972 B)
| 段 | 字节 | 占比 | 属性 |
|---|---|---|---|
.text | 162,062 | 62.8% | ALLOC(代码) |
.rela.dyn | 31,944 | 12.4% | ALLOC(动态重定位 2,662 项) |
.rodata | 26,835 | 10.4% | ALLOC |
.dynstr | 8,820 | 3.4% | ALLOC |
.dynsym | 8,720 | 3.4% | ALLOC(545 个动态符号) |
.got.loc | 6,104 | 2.4% | ALLOC |
.rela.plt | 5,364 | 2.1% | ALLOC |
.hash | 4,272 | 1.7% | ALLOC |
.data.rel.ro | 2,632 | 1.0% | ALLOC |
.bss | 11,632 | 0(不占文件) | ALLOC |
其余(.dynamic/.data/.got/.xtensa.info/.shstrtab) | 587 | 0.2% | — |
PT_LOAD 两段:off=0 filesz=254,272 memsz=254,272 (R+X)、off=254,272 filesz=2,876 memsz=14,512 (RW)
1.8.3 -Os 后各模块体积(code + rodata + data = 198,001 B)
| 模块 | 新值 | 旧值(-O0) | 降幅 |
|---|---|---|---|
Lua 解释器(georgik__lua/lua) | 124,034 | 202,588 | -38.8% |
| lua_engine 引擎 | 39,981 | 56,216 | -28.9% |
| i2c_bus 驱动 | 6,295 | 10,281 | -38.8% |
| 9 个内置模块合计 | 25,828 | 35,378 | -27.0% |
| engine_core + main | 1,863 | 2,562 | -27.3% |
1.8.4 静态对象清单(从 .o 符号表统计,.so 已 strip)
| 符号 | 大小 | 来源 |
|---|---|---|
s_jobs | 4,480 | lua_async.c:96 |
g_log_buffer | 4,112 | lua_bootlog.c:87 |
instructions[20] | 1,680 | lua_autorun.c:63 |
script_list[256] | 256 | lua_bootlog.c:613 |
s_modules[32] | 256 | lua_engine_core.c:27 |
s_base_dir | 256 | lua_engine_main.c |
s_i2c_bus | 184 | i2c_bus_v2.c |
.bss 合计 / .data 合计 | 11,612 / 2,696 | — |
1.9 elf_loader 基座架构特征总结
| 特征 | 描述 | 依据 |
|---|---|---|
| 单一执行基座 | 所有扩展必须编译成 ELF .so,由 elf_loader 加载 | 1.1 节 |
| 层级组合 | Lua 运行在 lua_engine.so 内(Lua VM 是 .so 的内容) | 1.5.1 |
| 符号层抽象 | 宿主能力以编译期生成的 C 符号白名单表达 | 1.4 |
| 编译期耦合 | 新增宿主 API 需重新生成符号表并重编基座 | 1.4.3 |
| 单向调用 | .so → 基座(app_main 入口 + 符号调用);无基座 → .so 内部函数的反向调用通道 | elf_so_loader.c:247-283、1.4 |
| PSRAM 优先 | .so 的 text/data/bss 全部 PSRAM 分配;任务栈默认 PSRAM | 1.3.1、1.3.3 |
| 端到端加密 | .epm = AES-256-CTR,加载时原地解密 | 1.7 |
| 有状态隔离缺失 | lua_engine 单实例 + 作业槽位模型,无"多 app"概念 | lua_async.c 作业模型(1.5.2) |
| 跨实例数据通道 | 共享内存 + 字段布局(登记制) | 1.6 |
2. ESP-Brookesia 架构
以下内容全部来自
so_build/ref/下的源码。
2.1 组件全景
2.1.1 组件与版本(16 个目录)
| 组件 | 版本 | CHANGELOG 最新 | 职责 |
|---|---|---|---|
brookesia_lib_utils | 0.8.2 | 2026-07-22 | 基础工具:TaskScheduler(Boost.Asio)、Log、StateMachine、PluginRegistry、signal/slot、describe_helpers |
brookesia_service_manager | 0.8.2 | 2026-07-22 | 服务框架:ServiceBase + Function/Event Registry + 绑定 + 拓扑排序 |
brookesia_service_helper | 0.8.5 | 2026-09-10 | 纯头文件契约层(CRTP 类型安全包装) |
brookesia_service_wifi | 0.8.2 | 2026-07-27 | WiFi 服务实现(服务模板范例) |
brookesia_agent_manager | 0.8.2 | 2026-07-27 | Agent 生命周期/状态机/多后端选择 |
brookesia_runtime_manager | 0.8.2 | 2026-07-27 | 运行时门面 + IRuntimeBackend 抽象 |
brookesia_runtime_elf | 0.8.2 | 2026-07-27 | ELF 后端(复用 IDF elf_loader) |
brookesia_runtime_lua | 0.8.2 | 2026-07-27 | Lua 后端(PUC-Lua 5.5) |
brookesia_runtime_js | 0.8.3 | 2026-08-05 | JS 后端(QuickJS-NG 0.14) |
brookesia_runtime_wasm | 0.8.2 | 2026-07-27 | WASM 后端(WAMR) |
brookesia_system_core | 0.8.4 | 2026-08-31 | App/GUI/Runtime/Storage 系统框架、.bpk 包、验签、安装 |
brookesia_hal_interface | 0.8.2 | 2026-07-20 | HAL 抽象(Device/Interface) |
brookesia_hal_adaptor | 0.8.4 | 2026-08-25 | HAL 板级实现(按 Kconfig 条件编译) |
brookesia_hal_boards | 0.8.0 | 2026-06-28 | 板级 YAML 配置集 |
esp-brookesia | 0.5.0 | 2025-05-30 | 旧线:LVGL 9.2 + Phone 系统 UI |
wasm-micro-runtime | 2.4.0~1 | — | WAMR(Espressif fork) |
批次判断:14 个 brookesia_* 均含共同的 v0.8.0 - 2026-06-28 基线条目,互引 0.8.*,属同一 v0.8 训练线;patch 号差异(0.8.0~0.8.5)是各自独立迭代。
2.1.2 依赖关系图(源码确认)
缺失的第一方组件(被引用但 ref/ 中不存在,必须联网拉取):
| 组件 | 引用方 | 出处 |
|---|---|---|
brookesia_service_sntp | agent_manager (private) | brookesia_agent_manager/idf_component.yml:5-7 |
brookesia_gui_interface | system_core (public) | brookesia_system_core/idf_component.yml:2-4 |
brookesia_service_storage | system_core / service_wifi (public) | brookesia_system_core/idf_component.yml:11-13、brookesia_service_wifi/idf_component.yml:2-4 |
brookesia_service_usb | system_core (private) | brookesia_system_core/idf_component.yml:14-16 |
2.2 运行时层(runtime_manager)
2.2.1 核心接口
// brookesia_runtime_manager/include/brookesia/runtime_manager/backend.hpp:19-62
class IRuntimeBackend {
public:
struct Attributes { std::string name; };
virtual void set_native_modules(std::vector<NativeModule> modules) = 0;
virtual std::expected<void, std::string> init() = 0;
virtual void deinit() = 0;
virtual std::expected<void, std::string> load_app(AppId id, const AppConfig &config) = 0;
virtual std::expected<void, std::string> unload_app(AppId id) = 0;
virtual std::expected<void, std::string> start_app(AppId id) = 0;
virtual std::expected<void, std::string> stop_app(AppId id) = 0;
virtual std::expected<NativeValue, std::string> call_function(
AppId id, std::string_view module_name, std::string_view function_name, const NativeArgs &args) = 0;
virtual AppState get_app_state(AppId id) const = 0;
protected:
IRuntimeBackend(BackendType type, Attributes attributes);
private:
Attributes attributes_;
BackendType type_ = BackendType::Unknown;
};
2.2.2 数据模型
// types.hpp:22-29
enum class BackendType { Unknown, Lua, JavaScript, Wasm, Elf };
// types.hpp:31-38
enum class AppState { Unloaded, Loaded, Running, Stopped, Error };
// types.hpp:60-67
struct AppConfig {
BackendType type = BackendType::Unknown;
std::string app_path;
std::string entry;
std::string resource_dir;
std::vector<std::string> arguments;
};
2.2.3 注册与选择
| 机制 | 实现 | 出处 |
|---|---|---|
| 后端注册表 | lib_utils::PluginRegistry<IRuntimeBackend> | backend.hpp:64 |
| 自动发现 | Runtime::init() 遍历注册表 | src/runtime.cpp:60-85 |
| 后端选择 | 按 BackendType 查表;Unknown + 单后端时用唯一后端 | src/runtime.cpp:26-43 |
| 后端自注册 | 各后端用 -u <symbol> + 插件注册宏 | runtime_elf/cmake/esp_platform.cmake:18-24、runtime_lua/...:13-19、runtime_js/...:18-24、runtime_wasm/...:18-24 |
2.2.4 宿主桥接
| 机制 | 实现 | 出处 |
|---|---|---|
| 宿主模块容器 | NativeModule / NativeValue / NativeArgs | types.hpp:40-67 |
| 默认 Host Bridge | 内建 brookesia 模块,5 个函数:current_app_context / finish_app / attach_app_context / detach_app_context / print | src/host_bridge.cpp:196-225 |
| 模块来源 | 遍历 RuntimeFunctionProviderRegistry 自动收集(插件注册) | src/function_bridge.cpp:94-125 |
| 注册宏 | BROOKESIA_RUNTIME_FUNCTION_PROVIDER_REGISTER_WITH_SYMBOL | function_bridge.hpp:73-76 |
| App 上下文隔离 | thread_local std::optional<AppId> current_app_id + RAII AppContextGuard | function_bridge.cpp:24, 212-258 |
2.2.5 职责边界(关键)
brookesia_runtime_manager/docs/main.md:3-24 明确:
brookesia_runtime_manager只负责 runtime backend 的 app 生命周期和 native host bridge。.bpk解包、发布验签与 app package manifest 解析均在system/brookesia_system_core。
即:运行时管理器不认识包格式、不做验签、不解 manifest。
2.3 四个后端
2.3.1 后端能力对照(源码确认)
| 后端 | 底层引擎 | 宿主模块注入 | call_function | 异步宿主函数 | 中断 | 加密 |
|---|---|---|---|---|---|---|
| ELF | IDF elf_loader(esp_elf_*) | 空操作(只记日志) | 不支持 | — | 不支持(同步执行) | 无 |
| Lua | PUC-Lua 5.5(georgik/lua) | 全局 table + lua_pushcclosure | 支持 | 不支持 | 不支持 | 无 |
| JS | QuickJS-NG 0.14 | JS_NewCFunctionMagic | 支持 | 支持(Promise) | 不支持 | 无 |
| WASM | WAMR 2.4.0 | 单桩 (ii)i + JSON + attachment | 支持 | — | 不支持 | 无 |
出处:
- ELF:
runtime_elf/src/backend.cpp:101-104(set_native_modules无操作)、:266-281(call_function不支持)、:258-261(不可中断)、:160-183(esp_elf_init+esp_elf_relocate)、:233-235(esp_elf_request) - Lua:
runtime_lua/src/backend.cpp:93-104(闭包注入)、:74-76(异步不支持)、:229-237(luaL_dofile)、:185-190(luaL_newstate+luaL_openlibs) - JS:
runtime_js/src/backend.cpp:481-503(CFunc magic)、:412-479(Promise 异步)、:681-701(JS_NewRuntime/JS_SetMemoryLimit)、:188-224(app 生命周期 hook) - WASM:
runtime_wasm/src/backend.cpp:627-651(单桩注册)、:601-610(结果句柄)、:656-735(加载流程)、:494-518(PSRAM 分配器)、:682-683(64 KB 栈 / 256 KB 堆)
2.3.2 各后端细节
ELF 后端
- 委托给 IDF 官方
elf_loader(idf_component.yml:5-7:espressif/elf_loader: 1.*) - relocate 前用
BROOKESIA_THREAD_CONFIG_GUARD({ .stack_in_ext = false; })强制内部栈,注释说明"因为 relocate 会操作 flash"(backend.cpp:169-176) - 文件读取走 Storage 服务(
backend.cpp:38-59),非直接fopen - PC 平台直接
FATAL_ERROR(cmake/pc_platform.cmake:4)
Lua 后端
- 每个 app 一个
lua_State+luaL_openlibs(backend.cpp:185-190) - 宿主模块注入:把每个函数用
lua_pushlightuserdata+lua_pushcclosure包成闭包挂到全局表(backend.cpp:93-104) - PC 侧找 Lua 5.4(
pkg_check_modules(LUA54 REQUIRED lua5.4),cmake/pc_platform.cmake:24),与 IDF 侧 5.5 不一致
JS 后端
- QuickJS-NG 0.14,支持 ES module 与全局脚本(
backend.cpp:764-769) - 内存上限硬编码
16 * 1024 * 1024(backend.cpp:42-47),不可通过 Kconfig 调整 - 唯一提供 app 级生命周期 hook 的后端:
on_install / on_start / on_stop / on_pause / on_resume / on_uninstall / on_action / on_event / on_timer(backend.cpp:188-224) - 异步宿主函数用
JS_NewPromiseCapability+TaskScheduler(组RuntimeJsAsync)resolve(backend.cpp:412-479) - 无 QuickJS 时有 fallback 后端(
backend.cpp:878-1014)
WASM 后端
- 加载流程(
backend.cpp:656-735):read_wasm_file→register_wamr_brookesia_imports→wasm_runtime_load→wasm_runtime_set_wasi_args→wasm_runtime_instantiate(64KB, 256KB)→wasm_application_execute_main - 宿主桥接:模块名固定
"env",所有函数共用桩wamr_dynamic_import,签名统一(ii)i,通过wasm_runtime_get_function_attachment区分(backend.cpp:520-523, 620, 627-651) - 符号名约定:
<sanitized_module>_<sanitized_function>(private/import_descriptor.hpp:69-77) - 结果回传:
brookesia_result_len/brookesia_result_copy/brookesia_result_free,结果池最多 64 条(backend.cpp:601-610、private/host_utils.hpp:21-54) - 运行时内存与线性内存都指向 PSRAM(
backend.cpp:494-518, 781-793)
2.4 服务层(service_manager + helper + agent)
2.4.1 ServiceBase
// brookesia_service_manager/include/brookesia/service_manager/service/base.hpp:136-175
struct Attributes {
std::string name; // RPC 稳定标识
std::string description;
std::string version; // v0.8.1 起强制非空
std::vector<std::string> dependencies = {};
std::optional<lib_utils::TaskSchedulerStartConfig> task_scheduler_config;
SchedulerType scheduler_type = SchedulerType::Main;
bool bindable = true;
};
子类覆写:get_function_schemas() / get_event_schemas() / get_function_handlers()(base.hpp:228, 252, 536);生命周期钩子 on_init/on_deinit/on_start/on_stop(base.hpp:446-477)
2.4.2 关键机制
| 机制 | 实现 | 出处 |
|---|---|---|
| 服务注册 | ServiceRegistry = lib_utils::PluginRegistry<ServiceBase> + 静态注册宏 | manager.hpp:36、lib_utils/plugin.hpp:37-47, 415 |
| 自动装配 | add_all_registered_services() + 拓扑排序(Kahn) | manager.cpp:818-845、manager.cpp:750-816 |
| 函数调用 | FunctionValue variant(bool/double/string/object/array/RawBuffer) | function/definition.hpp:49 |
| Schema | FunctionSchema{name, description, parameters, require_scheduler, default_timeout_ms, return_value} | function/definition.hpp:147-154 |
| 同步调用 | call_function_sync(boost::promise + wait_for) | service/base.cpp:416, 491-518 |
| 事件 | lib_utils::signal + EventSchema + EventMonitor | event/definition.hpp:70,75,121-126、helper/base.hpp:982-1244 |
| 无订阅者跳过 | publish_event 前检查 has_subscribers | service/base.cpp:781, 804-807 |
| 线程模型 | 每服务 3 个串行任务组(<svc>_call/_event/_request) | service/base.cpp:1077-1133 |
| 绑定模型 | RAII ServiceBinding + 引用计数,ref_count 归零时 stop() | manager.hpp:44、manager.cpp:435, 569 |
| 跨进程 | v0.8.0 已移除 TCP RPC | service_manager/CHANGELOG.md v0.8.0 条目 |
2.4.3 service_helper(契约层)
- 纯头文件,0 个
.cpp,约 8.5k 行 - 核心 CRTP 基类
template <typename Derived> class Base(helper/base.hpp:77),要求Derived提供FunctionId/EventId枚举、get_name()、schema(helper/base.hpp:32-40) - 类型安全调用:
call_function_sync<ReturnType>(FunctionId, Args...)(helper/base.hpp:679) get_name()是稳定 RPC 标识,改名属于破坏性变更(service_helper/README.md:11-13)
2.4.4 service_wifi(服务模板)
可复用要点(service_wifi/src/service_wifi.cpp、service_wifi.hpp):
- 属性在构造函数注入(
service_wifi.hpp:66-96) get_function_schemas()直接返回 helper 的 schema(:122-132)- handler 用
BROOKESIA_SERVICE_HELPER_FUNC_HANDLER_N逐条绑定(:133-205) - 业务函数返回
std::expected<T, std::string> - 底部静态注册 + CMake
-u(service_wifi.cpp:1880-1885、CMakeLists.txt:32-38)
2.4.5 agent_manager
| 项 | 内容 | 出处 |
|---|---|---|
| Agent 抽象 | class Base : public service::ServiceBase,构造时 bindable=false | agent_manager/base.hpp:88, 132-138 |
| 生命周期钩子 | on_activate/on_startup/on_shutdown/on_sleep/on_wakeup + 可选钩子 | base.hpp:159-181, 188-263 |
| Manager | 20 个函数(SetAgentInfo/SetTargetAgent/TriggerGeneralAction/Suspend/Resume/…) | manager.hpp:131-215 |
| 状态机 | GeneralState:TimeSyncing/Ready/Activating/Activated/Starting/Started/Sleeping/Slept/WakingUp/Stopping | service_helper/agent/manager.hpp:141-153 |
| 后端 | Coze / OpenAI / XiaoZhi —— ref 中只有 helper 契约,无实现体 | service_helper/agent/{coze,openai,xiaozhi}.hpp |
| MCP 接入 | XiaoZhi helper:AddMCP_ToolsWithServiceFunction / AddMCP_ToolsWithCustomFunction / RemoveMCP_Tools | service_helper/agent/xiaozhi.hpp:31-37, 66-104 |
| 测试 | 无 test_apps 目录 | 目录列举 |
2.5 系统层(system_core)
2.5.1 定位
brookesia_system_core/README.md:7:
the app, GUI, runtime, storage, timer, and service bridge framework for products.
依赖(system_core/idf_component.yml:1-28):brookesia_gui_interface(缺)、brookesia_runtime_manager、brookesia_service_helper、brookesia_service_storage(缺)、brookesia_service_usb(缺)、zlib ^1.3.0
2.5.2 .bpk 包格式
| 项 | 内容 | 出处 |
|---|---|---|
| 容器 | ZIP,根含 manifest.json,签名材料在 META-INF/ | src/app/package.cpp:39-46 |
| 解包目标 | <install_dir>/<manifest.package.id>/(确定性路径) | package.cpp:887-890 |
| 安全校验 | 跳过 META-INF/;逐条检查 is_safe_relative_path(防 ../) | package.cpp:897-906 |
| 命名约定 | <package.id>.<debug|release>.<version>.bpk | package.cpp:887-889 |
2.5.3 manifest 字段(完整)
// include/brookesia/system_core/app/types.hpp:200-245
struct AppManifest {
std::string id;
std::string name;
std::map<std::string, std::string> localized_names;
std::string version = "0.1.0";
AppKind kind = AppKind::Native; // Native / Runtime
bool visible = true;
bool preload_dom = false;
std::string icon_id;
std::vector<std::string> supported_systems;
std::string icon_path;
runtime::BackendType runtime_type = runtime::BackendType::Unknown;
std::string app_path;
std::string entry;
std::string resource_dir;
std::vector<std::string> arguments;
std::vector<AppManifestService> services = {};
};
JSON 层级:顶层 package / runtime / services(package.cpp:672-700)
package:id(必填)、name(对象形式{"en":"..."}, 必填)、version(必填)、visible、systemsruntime:type(必填)、entry(必填)、resource_dir、argumentsservices[]:name(必填)、version(必填)、component
2.5.4 验签
| 项 | 内容 | 出处 |
|---|---|---|
| 算法 | RSA-PSS / SHA-256,覆盖 META-INF/hash.json + 逐成员 SHA-256 | include/brookesia/system_core/package.hpp:56-69 |
| 签名材料 | META-INF/hash.json、META-INF/signature.sig | src/package_release_verify.cpp:42-45 |
| 公钥来源 | 文件系统 PEM(mbedtls_pk_parse_public_key) | package_release_verify.cpp:551-558 |
| 开关 | CONFIG_BROOKESIA_SYSTEM_CORE_ENABLE_PACKAGE_RELEASE_VERIFY 默认 y | Kconfig:90-107 |
| 载荷加密 | CONFIG_BROOKESIA_SYSTEM_CORE_ENABLE_PACKAGE_PAYLOAD_ENCRYPTION 默认 n,帮助文本写明 “Not implemented” | Kconfig:90-107 |
| 工具 | tools/app_pack.py(pack|sign --private-key)——不在 ref 中【待核实】 | package_release_verify.cpp:455-457 引用 |
2.5.5 安装/卸载
// include/brookesia/system_core/system/system.hpp:158-175
std::expected<AppId, std::string> install_app(std::shared_ptr<IApp> app);
std::expected<AppId, std::string> install_runtime_app(const AppManifest &manifest);
std::expected<AppId, std::string> install_runtime_app_package(
std::string_view package_path, bool replace_existing = true);
std::expected<void, std::string> install_registered_apps();
std::expected<void, std::string> uninstall_app(AppId app_id);
- 无独立 update API:更新 =
install_runtime_app_package(..., replace_existing=true) - 安装含回滚(
src/app/manager.cpp:393-482) - 启动时扫描已解包目录并安装(
src/app/package_scan.cpp:30-119) - 不存在 “App Store” 组件(
ref/全境检索仅命中esp-brookesia/README.md规划文字)
2.6 HAL 层
| 层 | 形态 | 出处 |
|---|---|---|
hal_interface | 抽象:Device / Interface / InterfaceHandle<T> | device.hpp:48-131、interface.hpp:51-80 |
| 接口类型 | audio / bluetooth / display / network / power / storage / system / video / wifi | interfaces.hpp:13-36 |
hal_adaptor | 板级实现,按 Kconfig 条件编译(36 个 .cpp) | CMakeLists.txt:117-265 |
hal_boards | 纯 YAML 配置(board_info.yaml / board_devices.yaml / board_peripherals.yaml / sdkconfig.defaults.board) | boards/**/ |
与 LVGL 解耦(源码确认):hal_interface/idf_component.yml:1-14 仅依赖 brookesia_lib_utils;hal_adaptor 依赖列表无 LVGL;display 仅提供 panel/touch/backlight 原生接口。
ESP32-S3 板子(boards/**/board_info.yaml):esp_box_3、esp32_s3_korvo2_v3、waveshare/esp32_s3_touch_amoled_{1_75c,1_8,2_16}、rymcu/rymcu_bigsmart
2.7 旧线 esp-brookesia 0.5.0
| 项 | 内容 | 出处 |
|---|---|---|
| 版本/日期 | 0.5.0 / 2025-05-30 | esp-brookesia/CHANGELOG.md:3 |
| 依赖 | esp-lib-utils 0.2.*、lvgl/lvgl 9.2.*、idf >=5.3 | esp-brookesia/idf_component.yml:2-17 |
| 内容 | LVGL 封装 + Phone 系统 UI(app_launcher / gesture / navigation_bar / recents_screen / status_bar + 多分辨率 stylesheet) | src/systems/phone/widgets/** |
| 资源规模 | 壁纸:720×720 8.9 MB、480×480 3.96 MB、240×240 1.0 MB;字体 24 个文件合计约 2.5 MB | src/systems/phone/assets/** |
| 示例镜像 | >2.3 MB(boot 日志 size=1c5f04h + 76e50h) | examples/phone_s3_box_3/README.md:65-69 |
| 与 0.8 关系 | 无依赖关系,属不同代 | 依赖图核对 |
2.8 WAMR 在 ESP-IDF / ESP32-S3 上的集成事实
| 项 | 结论 | 出处 |
|---|---|---|
| 支持芯片 | esp32, esp32s3, esp32c3, esp32c6, esp32p4, esp32c5 | wasm-micro-runtime/idf_component.yml:28-34 |
| IDF 约束 | >=5.1 | 同文件 :2 |
| AOT 默认 | WAMR_ENABLE_AOT=y | build-scripts/esp-idf/wamr/Kconfig:13-15 |
| 解释器默认 | WAMR_ENABLE_INTERP=y + WAMR_INTERP_MODE=Fast | Kconfig:17-19, 23-32 |
| libc 默认 | libc-builtin=y、libc-wasi=y | Kconfig:50-56 |
| JIT | ESP-IDF 不提供(Kconfig 无 JIT 项;Fast JIT codegen 仅 x86-64) | build-scripts/esp-idf/wamr/Kconfig、core/iwasm/fast-jit/iwasm_fast_jit.cmake:97-101 |
| 架构分支 | xtensa → WAMR_BUILD_TARGET=XTENSA;riscv → RISCV32_ILP32F | build-scripts/esp-idf/wamr/CMakeLists.txt:7-17 |
| AOT 代码落点 | S3 开 PSRAM → WASM_MEM_DUAL_BUS_MIRROR=1 → MALLOC_CAP_SPIRAM(PSRAM)+ ibus 镜像;否则 MALLOC_CAP_EXEC(内部 RAM) | core/shared/platform/esp-idf/shared_platform.cmake:18-22、core/shared/platform/esp-idf/espidf_memmap.c:20-51 |
| XIP 模式 | AOT ELF 支持 E_TYPE_XIP,is_indirect_mode 时不 mmap/copy text,就地执行 | core/iwasm/aot/aot_loader.c:260-263, 4283-4291, 4349-4350 |
| xtensa 重定位限制 | 仅 R_XTENSA_32 / R_XTENSA_SLOT0_OP;l32r 越界需 wamrc --size-level=0 | core/iwasm/aot/arch/aot_reloc_xtensa.c:8-9, 272-280 |
| 内存模型 | 分配器三选一:Pool / Allocator / System;brookesia 用 Alloc_With_Allocator → PSRAM | core/iwasm/include/wasm_export.h:169-200、runtime_wasm/src/backend.cpp:781-793 |
| 全局堆池 | 默认关闭;ESP-IDF 上不支持(示例 #error) | core/config.h:376-379、external_examples/968944e8/esp-idf/main/main.c:45-52 |
| 体积参考(官方 README) | fast-interp 58.9 KB / classic 56.3 KB / AOT 29.4 KB / libc-wasi 21.4 KB / libc-builtin 3.7 KB | wasm-micro-runtime/README.md:25-29 |
2.9 落地门槛
2.9.1 IDF 版本(决定性)
| 文件:行 | 约束 | 影响 |
|---|---|---|
brookesia_lib_utils/idf_component.yml:8 | idf: '>=6.0,<=6.2' | 几乎所有第一方组件的传递依赖,5.5.4 直接求解失败 |
esp-brookesia/idf_component.yml:5 | idf: '>=5.3' | 仅旧线可用 |
wasm-micro-runtime/idf_component.yml:2 | idf: '>=5.1' | 可单独用于 5.5.4 |
其余 0.8 组件(service_manager / service_helper / service_wifi / agent_manager / runtime_* / system_core / hal_*)的 idf_component.yml 均未声明 IDF 约束,其下限由 lib_utils 决定。
2.9.2 其它门槛
| 门槛 | 内容 | 出处 |
|---|---|---|
| Boost | esp-boost 0.6.*(Boost thread/system/chrono/json) | lib_utils/idf_component.yml:5-7、service_manager/Kconfig:2-6 |
| C++ 标准 | runtime_*/system_core 强制 cxx_std_23;使用 std::expected(runtime_elf/src/backend.cpp:38 等)、std::span | 各 cmake/esp_platform.cmake |
| pthread wrap | -Wl,--wrap=pthread_mutex_init / --wrap=pthread_mutex_destroy,INTERFACE 传播 | lib_utils/cmake/esp_platform.cmake:12-18 |
| 插件符号保留 | -u <symbol>(后端、服务、HAL 设备共十余处) | 各 CMakeLists.txt / cmake/esp_platform.cmake |
| 强制 mbedtls | system_core 在 ESP 恒追加 mbedtls(验签用) | system_core/cmake/esp_platform.cmake:4-6 |
| 运行期打补丁 | hal_adaptor 用 git apply 给 media_lib_sal 打 IDF 6 TLS 补丁,需要 git | hal_adaptor/CMakeLists.txt:26-99, 284-295 |
| 运行时 RAM | 默认主调度器 3 worker × 64 KB 栈(≈192 KB 内部 RAM)+ 每服务 3 个串行任务组 | system_core/include/brookesia/system_core/macro_configs.h:108-140、service/base.cpp:1077-1133 |
2.9.3 缺失组件(需联网拉取)
brookesia_service_sntp、brookesia_gui_interface、brookesia_service_storage、brookesia_service_usb(出处见 2.1.2)
2.10 Brookesia 特征总结
| 特征 | 描述 | 依据 |
|---|---|---|
| 对等后端 | ELF/Lua/JS/WASM 四个平级运行时,各自实现同一接口 | 2.2、2.3 |
| 后端层抽象 | 语言差异由后端吸收;宿主能力用与语言无关的 NativeModule 元数据描述 | 2.2.4 |
| 反向调用 | 宿主可通过 call_function(app_id, module, fn, args) 进入 app 内部(ELF 后端除外) | 2.3.1 |
| App 隔离 | AppId + thread_local 上下文 + RAII guard | 2.2.4 |
| 编译期装配 | 插件注册表 + -u 符号,Runtime::init() 自动发现;基座不硬编码后端 | 2.2.3 |
| 包/运行时解耦 | .bpk + manifest 由 system_core 解析,运行时只收 AppConfig | 2.2.5 |
| 验签不加密 | RSA-PSS 完整性/来源验证默认开;载荷加密未实现 | 2.5.4 |
| 进程内服务框架 | schema 校验 + 依赖拓扑 + 引用计数绑定 + 事件 await;v0.8.0 移除跨进程 RPC | 2.4.2 |
| MCP 桥 | 服务函数可直接暴露为 MCP 工具(经 XiaoZhi helper) | 2.4.5 |
| 快速演进 | 0.8.x 内多次 Breaking Change(删 RPC、强制 version、隐藏 Impl) | 各 CHANGELOG |
| HAL 与 GUI 解耦 | HAL 三层不依赖 LVGL | 2.6 |
3. 对比分析
3.1 根本差异:抽象放在哪一层
| elf_loader 基座 | Brookesia | |
|---|---|---|
| 抽象单元 | 符号(esp_elfsym 数组 + 白名单) | 后端(IRuntimeBackend 实现) |
| 语言差异处理方 | .so 内部自己消化(lua_module_* 绑定) | 后端实现吸收(Lua 闭包 / JS CFunc / WASM 单桩) |
| 宿主能力描述 | 编译期 C 符号名 → 地址 | 运行期 NativeModule 元数据 |
| 新增语言 | 必须编成新的 .so | 加一个后端组件 + 链接项 |
| 新增宿主 API | 重生成符号表 + 重编基座 | 加一个 Provider + -u 符号 |
3.2 分层图对照
elf_loader 基座(层级式)
Brookesia(对等式)
3.3 逐维度对比
| 维度 | elf_loader 基座 | Brookesia | 证据 |
|---|---|---|---|
| 执行基座 | 单一(ELF) | 四个对等 | 1.1 / 2.1 |
| 语言扩展方式 | 新编 .so | 加后端组件 | 1.4 / 2.2.3 |
| 宿主能力导出 | 编译期符号白名单 + nm 生成 | 运行期 NativeModule 元数据 + 后端翻译 | 1.4 / 2.2.4 |
| 宿主→app 反向调用 | 无 | 有(call_function,ELF 后端不支持) | 1.4 / 2.3.1 |
| app 生命周期管理 | .so 入口任务;Lua 走作业模型 | AppState 状态机 + 可选 hook(JS 最全) | 1.3.2 / 2.2.2、2.3.2 |
| 多实例隔离 | 无(单引擎 + 作业槽) | AppId + thread-local 上下文 | 1.5.2 / 2.2.4 |
| 包格式 | .so/.epm(加密)+ 明文 .lua(两套) | .bpk(ZIP + manifest,统一容器) | 1.7 / 2.5.2-2.5.3 |
| 完整性/来源验证 | 无(仅加密) | RSA-PSS/SHA-256 验签(默认开) | 1.7 / 2.5.4 |
| 载荷加密 | AES-256-CTR(已实现) | 官方未实现 | 1.7 / 2.5.4 |
| 跨实例/跨进程通道 | 共享内存 + 字段布局(已实现) | v0.8.0 已移除 TCP RPC,仅进程内 | 1.6 / 2.4.2 |
| 服务框架 | claw agent(Lua + skill,运行期加载) | ServiceBase + schema + 拓扑 + 事件 await(编译期注册) | 【待核实】 / 2.4 |
| 事件机制 | 需自建 | lib_utils::signal + EventMonitor(schema 校验,可 await) | — / 2.4.2 |
| 依赖拓扑/引用计数 | 无 | 有(Kahn 排序 + ref_count 绑定) | — / 2.4.2 |
| LLM 工具接入 | skill 即工具 | MCP 工具 = 服务函数封装 | 【待核实】 / 2.4.5 |
| 沙箱能力 | 无(native 同地址空间) | JS / WASM 强隔离 | 1.1 / 2.3 |
| 代码体积控制 | 已做(-Os + 段裁剪,-53.4%) | 每后端一套运行时,体积叠加 | 1.8 / 2.3.1 |
| 运行内存策略 | 全 PSRAM(loader 决定) | 全 PSRAM(WAMR 分配器决定) | 1.3.1 / 2.3.2 |
| IDF 要求 | 5.5.4(现状) | 6.0~6.2 | _run_build.bat:4 / 2.9.1 |
| 语言标准 | C(.so 全 C) | C++23 + Boost | 1.5.1 / 2.9.2 |
3.4 Lua 层的定位差异(重点)
elf_loader 基座上的 lua_engine | Brookesia runtime_lua | |
|---|---|---|
| 形态 | 常驻引擎(单实例) | 每 app 一个 lua_State |
| 脚本角色 | 作业(job):异步、互斥组、状态查询、输出捕获、停止请求 | app 本体:luaL_dofile 执行一次 |
| 内置能力 | i2c/mcpwm/storage/json/gpio/crc16/system/esp_heap/shared_memory 9 个模块 | 无内置模块,全靠宿主注入 |
| 异步 | 有(lua_async) | 不支持异步 native 函数 |
| 生命周期 | 作业状态机(lua_job_record_t.status) | AppState(Unloaded→Loaded→Running→Stopped/Error) |
| Lua 版本 | 5.5 | 5.5(同一个 georgik/lua 组件) |
| 定位类比 | 更接近 system_core(应用服务器/运行时容器) | 脚本容器(与 JS/WASM 平级的第四种后端) |
Lua 版本相同这一点值得注意:两个方案底层用的是同一份 Lua 源码(
georgik__lua/lua/lua.h:20-22与runtime_lua/idf_component.yml:5-7的georgik/lua 5.5.*)。差异全在宿主层设计,不在语言实现。
3.5 双方强项/弱项清单
elf_loader 基座架构强项(有依据)
| 强项 | 依据 |
|---|---|
| ELF 加载链路内聚(加载+重定位+解密+白名单+执行) | components/elf_loader/src/esp_elf.c、components/elf_ext/** |
端到端加密(AES-256-CTR + ELFC 容器) | so_build/lua_engine/CMakeLists.txt:242-254、esp_elf.c:148-153 |
| 宿主符号裁剪(白名单 + drop-pattern 过滤) | elf_ext/CMakeLists.txt:74-95、tools/filter_syms.py |
| Lua 作业模型(异步/互斥/状态/停止/输出捕获) | components/lua_engine/src/lua_async.c |
共享内存 + 字段布局(跨 .so 数据通道) | components/shared_memory/**、lua_module_shared_memory_lua.c |
| 内置硬件模块(9 个) | so_build/lua_engine/CMakeLists.txt:170-178 |
.so 体积优化已落地(-53.4%) | 1.8.1 |
elf_loader 基座架构弱项(有依据)
| 弱项 | 依据 |
|---|---|
| 无反向调用(基座无法调 app 内函数) | 1.4 |
| 无沙箱(native 同地址空间,崩溃即整机) | 1.3 |
| 无统一包格式/验签(两套分发路径) | 1.7 |
| 无多 app 隔离 | 1.5.2 |
| 新增宿主 API 需重编基座(编译期耦合) | 1.4.3 |
Brookesia 强项(有依据)
| 强项 | 依据 |
|---|---|
| 统一后端抽象 + 插件化 | 2.2 |
| 反向调用 + app 上下文隔离 | 2.2.4、2.3.1 |
.bpk 统一包 + RSA-PSS 验签 | 2.5.2-2.5.4 |
| 服务框架工程化(schema/拓扑/引用计数/事件 await) | 2.4.2 |
| JS/WASM 沙箱后端 | 2.3.2 |
| MCP 工具接入 | 2.4.5 |
| HAL 三层与 GUI 解耦、S3 板级配置齐全 | 2.6 |
Brookesia 弱项(有依据)
| 弱项 | 依据 |
|---|---|
| IDF 6.0~6.2 硬门槛 | 2.9.1 |
| 缺 4 个第一方组件(离线不可构建) | 2.1.2 |
| 载荷加密未实现 | 2.5.4 |
| 跨进程 RPC 已移除 | 2.4.2 |
| ELF 后端能力弱于 elf_loader 基座(无 host call/无加密/不可中断) | 2.3.1 |
| 0.8.x 频繁 Breaking Change | 各 CHANGELOG |
| Boost + C++23 + 192 KB 内部 RAM 开销 | 2.9.2 |
| 无 App Store 实体(仅安装原语) | 2.5.5 |
4. 可借鉴项与验证计划
4.1 零成本借鉴(只读源码,不改架构)
| 借鉴项 | 参考出处 | 用途 |
|---|---|---|
IRuntimeBackend 接口形态 | runtime_manager/include/brookesia/runtime_manager/backend.hpp:19-62 | 给现有 .so 与 Lua 加统一抽象层 |
AppState 状态机 | types.hpp:31-38 | 统一 app/作业状态表达 |
NativeModule/NativeValue 元数据 | types.hpp:40-67 | 替代/包装现有编译期符号白名单 |
.bpk manifest 字段表 | system_core/include/brookesia/system_core/app/types.hpp:200-245 | 设计自有包格式 |
| RSA-PSS 验签方案(hash.json + 逐成员 SHA-256) | system_core/src/package_release_verify.cpp:24-45, 551-558 | 与现有 AES 加密叠加,补"来源可信" |
| 服务依赖拓扑排序(Kahn) | service_manager/src/service/manager.cpp:750-816 | 服务/模块依赖管理 |
schema 校验 + FunctionValue 变体 | service_manager/include/brookesia/service_manager/function/definition.hpp:49, 147-154 | 服务接口契约化 |
| JS 后端 Promise 异步桥接 | runtime_js/src/backend.cpp:412-479 | 优化 lua_async 的宿主回调设计 |
| Lua 后端闭包注入 | runtime_lua/src/backend.cpp:93-104 | 优化 Lua 模块绑定写法 |
| WASM 单桩 + attachment 桥接 | runtime_wasm/src/backend.cpp:520-523, 627-651 | 未来接 WASM 时的绑定方案 |
| WAMR AOT 落 PSRAM 配置 | wasm-micro-runtime/core/shared/platform/esp-idf/shared_platform.cmake:18-22、espidf_memmap.c:20-51 | 引入 WAMR 的关键配置依据 |
4.2 需要实测验证的
| 验证项 | 方法 | 预期要回答的问题 |
|---|---|---|
| WAMR 单独引入(不碰 IDF 6) | 依赖 wasm-micro-runtime(idf ≥5.1),照 runtime_wasm/src/backend.cpp:656-735 写加载器 | 固件增量 / 执行耗时 / PSRAM 峰值 / 加载耗时 |
| AOT vs interp 在 S3 上的实际表现 | 用 wamrc 编译同一模块,分别加载 | AOT 落 PSRAM 后的 icache 影响、相对 native .so 与 Lua 的比值 |
| 每实例内存默认值是否过大 | 对比 brookesia 默认 64 KB 栈/256 KB 堆(backend.cpp:682-683)与示例 32 KB/32 KB(external_examples/.../main.c:76-77) | 合理的最小配置 |
.bpk 验签与现有 AES 加密的叠加可行性 | 参考 package_release_verify.cpp 实现独立验签函数 | 是否需要在既有 .epm 外层再加签名清单 |
4.3 不建议直接采用的部分
| 项 | 原因 | 依据 |
|---|---|---|
| 整体迁移到 Brookesia v0.8 | IDF 需升到 6.0~6.2;缺 4 个组件;与自研体系重叠度高 | 2.9 |
| 替换现有 ELF 加载器为官方 ELF 后端 | 官方后端无加密、无宿主符号注册、不支持 call_function、不可中断 | 2.3.1 |
用 runtime_lua 替换 lua_engine | 官方不支持异步 native 函数、无内置硬件模块、每 app 一 state | 2.3.2、3.4 |
采用编译期静态注册(-u 符号) | 会破坏 claw agent 的运行期脚本/技能加载范式 | 2.2.3 |
| 移除共享内存改用服务框架 | v0.8.0 已移除跨进程 RPC,服务框架仅进程内 | 2.4.2 |
使用旧线 esp-brookesia 0.5.0 | 与 0.8 不同代;UI 资源 MB 级;无 .bpk 能力 | 2.7 |
5. 附录
5.1 依据索引
elf_loader 基座架构
| 主题 | 关键文件 |
|---|---|
| 基座配置 | bs_hmi_base_s3/sdkconfig、sdkconfig.defaults、partitions.csv、_run_build.bat |
| 固件体积归因 | build/bs_hmi_base_s3.map、build/analyze_map.py(本次新增的分析脚本) |
| ELF 加载 | components/elf_loader/src/esp_elf.c、esp_elf_adapter.c、src/dlso/dlmod.c |
| 符号导出 | components/elf_ext/CMakeLists.txt、src/elf_sym_register.c、src/elf_tab_syms.c、tools/filter_syms.py |
.so 调度/加载 | components/elf_ext/src/elf_so_loader.c、src/elf_task_api.c |
| 加密 | so_build/lua_engine/tools/encrypt_so.py、components/elf_ext/src/elf_so_crypt.c |
| Lua 引擎 | so_build/lua_engine/CMakeLists.txt、components/lua_engine/src/*.c、components/georgik__lua/lua/*.c |
| Lua 模块 | so_build/lua_engine/lua_mods/lua_module_*/src/*.c |
| 共享内存 | components/shared_memory/src/shared_memory.c、include/shared_memory.h、lua_mods/lua_module_shared_memory/src/*.c |
| 布局登记 | so_build/bs_hmi_1ch_480x272/main/state_layout.c、main/main.c;so_build/bs_hmi_1ch_320x240/main/* |
.so 体积分析 | so_build/lua_engine/build/analyze_so_syms.py(本次新增) |
| claw agent | doc/so_lua_claw_integration_plan.md【待核实:实现代码位置】 |
Brookesia
| 主题 | 关键文件 |
|---|---|
| 后端接口 | ref/brookesia_runtime_manager/include/brookesia/runtime_manager/{backend,types,runtime,host_bridge,function_bridge}.hpp |
| 运行时实现 | ref/brookesia_runtime_manager/src/{runtime,host_bridge,function_bridge}.cpp、docs/main.md |
| ELF 后端 | ref/brookesia_runtime_elf/src/backend.cpp、idf_component.yml |
| Lua 后端 | ref/brookesia_runtime_lua/src/backend.cpp、idf_component.yml |
| JS 后端 | ref/brookesia_runtime_js/src/backend.cpp、idf_component.yml |
| WASM 后端 | ref/brookesia_runtime_wasm/src/backend.cpp、src/private/{import_descriptor,host_utils,native_call_utils}.hpp |
| WAMR | ref/wasm-micro-runtime/build-scripts/esp-idf/wamr/{Kconfig,CMakeLists.txt}、core/shared/platform/esp-idf/*、core/iwasm/aot/*、README.md |
| 服务框架 | ref/brookesia_service_manager/include/brookesia/service_manager/service/base.hpp、src/service/{base,manager}.cpp、include/brookesia/service_manager/helper/base.hpp |
| 服务契约 | ref/brookesia_service_helper/include/brookesia/service_helper/** |
| WiFi 服务范例 | ref/brookesia_service_wifi/{include,src}/** |
| Agent | ref/brookesia_agent_manager/include/brookesia/agent_manager/{base,manager,state_machine}.hpp、ref/brookesia_service_helper/include/brookesia/service_helper/agent/*.hpp |
| 系统层 | ref/brookesia_system_core/include/brookesia/system_core/app/{types,package}.hpp、src/app/package.cpp、src/package_release_verify.cpp、Kconfig |
| HAL | ref/brookesia_hal_interface/include/brookesia/hal_interface/**、ref/brookesia_hal_adaptor/CMakeLists.txt、ref/brookesia_hal_boards/boards/** |
| 旧线 | ref/esp-brookesia/{README.md,CMakeLists.txt,CHANGELOG.md,idf_component.yml,src/systems/phone/**} |
| 依赖与版本 | 各组件 idf_component.yml、CHANGELOG.md |
5.2 待核实清单
| 编号 | 事项 | 说明 |
|---|---|---|
| Q1 | claw agent 的实现代码位置与能力边界 | 目前仅见规划文档 doc/so_lua_claw_integration_plan.md 与 skills 目录,未做代码级核对 |
| Q2 | so_build/ 下 .so 工程的完整清单 | 已确认 lua_engine / bs_link_hmi / bs_link_scpi / ui_mirror / terminal;bs_hmi_320x240 与 bs_hmi_1ch_{480x272,320x240} 的目录关系需最终核对 |
| Q3 | esp-brookesia-toolkit 的 tools/app_pack.py | system_core 引用但不在 ref/ 中 |
| Q4 | brookesia_gui_interface 的实现 | system_core 的 public 依赖,未获取 |
| Q5 | WAMR 在 S3 上 AOT 的实测数据 | 只有配置层面的源码依据,无实测 |
| Q6 | esp_elf 组件版本与上游 elf_loader 1.* 的关系 | 本仓库 components/elf_loader 与 ref/brookesia_runtime_elf/idf_component.yml:5-7 所依赖的 espressif/elf_loader 是否同源同版本,未核对 |
| Q7 | 基座固件的日志/断言裁剪收益 | 曾改配置但构建受阻后已回退(sdkconfig 当前为原始值,见 1.2.1),收益未测出 |
文档变更记录
| 版本 | 日期 | 变更 |
|---|---|---|
| v1.1 | 2026-09-19 | 术语调整:“我方架构” → “elf_loader 基座架构”,并补充命名说明(1 章开头) |
| v1.0 | 2026-09-19 | 首版:整合本次运行时架构研究(elf_loader 基座 + Brookesia + 对比 + 借鉴项) |
📚 相关阅读
- ESP32-S3 三种执行方式效率对比:原生 / WebAssembly / Lua 的 1000 位圆周率对决 —— 同一算法三种执行方式的实测性能差距:原生 427ms、WASM 11.4×、Lua 63.9×