ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ESP32上跑WebAssembly:打造可动态安装应用的嵌入式平台

ESP32上跑WebAssembly:打造可动态安装应用的嵌入式平台 1. 从一个执念说起为什么ESP32不能像手机一样装应用手里攥着十几块ESP32从最早的ESP32-WROOM-32到后来的S3、C3我一直在想一个问题这玩意儿性能明明不差双核240MHz带WiFi带蓝牙PSRAM能扩到8MB甚至16MB为什么每次想换个功能就得重新编译固件、插线、烧录、等进度条手机装个App点一下就行ESP32换个功能却要动整套工具链这个体验落差实在太大了。这个念头在我脑子里转了大半年。直到有一次我做一个带屏幕的环境监测终端客户临时说想加一个“番茄钟”功能。按理说这功能简单得不能再简单但当时固件已经封版了重新编译烧录再测试折腾了一下午。那一刻我下定决心我要给ESP32做一个能“安装应用”的小型应用平台。这篇文章就是把这个项目的完整思路、技术选型、踩过的坑和最终跑通的方案从头到尾讲清楚。核心关键词就几个ESP32、WebAssembly、WASM、固件、应用平台。如果你也在做ESP32相关的项目或者对嵌入式设备上的动态加载感兴趣这篇内容应该能帮你省下不少试错时间。先说结论我最终选择的方案是在ESP32上跑一个WebAssembly运行时把每个“应用”编译成WASM模块通过文件系统动态加载执行。听起来有点疯狂但实测下来是可行的而且比想象中稳定。下面我把整个设计思路、技术细节、实操过程和避坑经验全部展开。2. 整体设计思路与技术选型拆解2.1 核心需求到底是什么在动手之前我先把需求理清楚。所谓“像手机一样安装应用”拆解下来其实是这么几件事动态加载不重新烧录固件的前提下能加载新的功能代码隔离性一个应用崩了不能把整个系统带崩资源可控每个应用能用的内存、CPU时间要有限制存储管理应用能安装、卸载、更新存在Flash里统一接口应用能调用底层硬件GPIO、I2C、屏幕、网络等这五条里最难的是前两条。动态加载和隔离性在桌面系统上靠操作系统和MMU内存管理单元解决但ESP32虽然有MMU主要用于Flash映射不像Linux那样能做完整的进程隔离。所以必须换一个思路。2.2 为什么最终选了WebAssembly我调研过几条路线这里把对比过程列出来方便你判断自己的项目该选哪条。方案动态加载隔离性资源开销开发难度生态成熟度Lua/MicroPython脚本支持弱低低成熟ELF动态链接支持无中高一般字节码虚拟机自定义支持中中极高无WebAssembly支持强中高中快速增长Lua和MicroPython是最省事的路线ESP32上跑MicroPython已经很成熟了。但问题在于隔离性——脚本里一个死循环就能把整个系统卡死内存越界也可能污染主程序。而且脚本语言的性能对于某些计算密集的场景比如驱动屏幕刷新、做FFT还是偏弱。ELF动态链接在Linux上很常见但ESP32的ESP-IDF虽然支持ELF动态加载需要自己实现符号解析和重定位工作量大且容易出问题。更关键的是加载进来的代码和主程序共享地址空间没有任何隔离。自定义字节码虚拟机灵活性最高但等于从零造一门语言和一套工具链投入产出比太低。WebAssembly的优势在于它天生就是为沙箱执行设计的。WASM模块运行在独立的线性内存里不能直接访问宿主内存所有对外部世界的调用都必须通过明确定义的导入函数import。这意味着隔离性是语言层面保证的不需要依赖硬件MMU。而且WASM的工具链已经非常成熟C/C、Rust都能编译到WASM开发者不需要学新语言。性能方面WASM是接近原生的二进制格式虽然解释执行会比原生慢但比脚本语言快得多。对于ESP32这种资源受限的设备我实测下来一个用C编译的WASM模块做简单的GPIO控制和逻辑运算性能完全够用。2.3 运行时选型为什么是WAMRWASM运行时有好几个选择WAMRWebAssembly Micro Runtime、Wasmer、Wasmtime、wasm3。桌面端常用Wasmer和Wasmtime但它们对嵌入式支持不够友好。wasm3很轻量但功能相对有限。我最终选了WAMR原因有几个专为嵌入式设计WAMR有专门的“small profile”编译出来核心运行时可以控制在几十KB级别支持解释执行和AOTESP32上先用解释器模式跑通后续可以考虑AOT编译提升性能ESP-IDF集成友好WAMR提供了ESP-IDF的组件包直接作为component引入就行内存管理灵活可以配置每个WASM实例的堆大小、栈大小适合资源受限场景活跃的社区Intel主导的项目更新频率高遇到问题容易找到答案提示WAMR在ESP32上跑建议用ESP32-S3或者带PSRAM的型号。普通ESP32没有PSRAM的话运行时加上应用内存会很紧张。我一开始在ESP32-WROOM上试编译出来固件就快满了后来换到S3带8MB PSRAM才舒服。2.4 整体架构设计整个平台的架构分成四层第一层是硬件抽象层。把GPIO、I2C、SPI、UART、WiFi、屏幕这些硬件操作封装成统一的C函数接口。这一层是固定的烧录在固件里不变化。第二层是WASM运行时层。WAMR运行时负责加载、验证、实例化WASM模块管理每个实例的内存和生命周期。第三层是宿主接口层。把硬件抽象层的函数通过WAMR的native symbol机制注册成WASM模块可以导入的函数。比如WASM里调用gpio_write(2, 1)实际执行的是宿主里的C函数。第四层是应用层。每个应用是一个独立的.wasm文件存在Flash的文件系统里我用的是SPIFFS也可以用LittleFS。系统启动时扫描应用目录列出已安装的应用用户选择后加载执行。这个架构的关键在于应用和固件完全解耦。固件提供能力应用消费能力。只要宿主接口不变应用可以随便换、随便加不需要重新烧录固件。3. 核心细节解析与实操要点3.1 WAMR在ESP32上的编译与集成把WAMR集成到ESP-IDF项目里最省事的方式是用它的component。在项目根目录的components文件夹下克隆WAMR的仓库然后在CMakeLists.txt里引用。具体操作步骤# 在项目目录下 mkdir -p components cd components git clone https://github.com/bytecodealliance/wasm-micro-runtime.git然后在项目的CMakeLists.txt里添加set(WAMR_BUILD_PLATFORM esp-idf) set(WAMR_BUILD_INTERP 1) set(WAMR_BUILD_AOT 0) set(WAMR_BUILD_JIT 0) set(WAMR_BUILD_LIBC_WASI 0) set(WAMR_BUILD_APP_FRAMEWORK 1) set(WAMR_BUILD_APP_LIST WAMR_APP_BUILD_BASE)这里有几个关键配置需要解释WAMR_BUILD_INTERP 1启用解释器模式。ESP32上AOT编译需要把WASM预编译成平台相关的机器码虽然性能好但会增大存储占用而且交叉编译工具链配置麻烦。先用解释器跑通。WAMR_BUILD_LIBC_WASI 0关掉WASI支持。WASI是WASM的系统接口标准主要面向桌面和服务器场景嵌入式上用不到关掉能省不少空间。WAMR_BUILD_APP_FRAMEWORK 1启用应用框架方便管理多个WASM模块。编译的时候要注意WAMR默认的栈大小和堆大小对ESP32来说偏大。需要在menuconfig里调整Component config → WAMR → WASM interpreter stack size 4096 WASM interpreter heap size 8192 WASM module max size 65536这些值不是随便定的。栈大小4096字节是因为WASM函数调用需要栈空间太小的会导致复杂函数执行失败。堆大小8192字节是给WASM实例的线性内存用的如果应用需要更多内存可以适当调大但要考虑ESP32的RAM限制。注意WAMR的堆和ESP32的系统堆是分开的。WAMR会从系统堆里预分配一块内存作为自己的堆池所有WASM实例的内存都从这块池子里分配。所以配置的时候要算好总账别把系统堆吃光了。3.2 宿主接口的设计与注册宿主接口是整个平台的核心。WASM模块不能直接访问硬件所有操作都要通过导入函数。设计接口的时候要考虑几个原则第一接口要稳定。一旦发布应用的WASM文件就依赖这些接口。如果接口变了所有应用都要重新编译。所以接口设计要留有余地宁可多留几个参数也不要频繁改签名。第二接口要安全。WASM模块传进来的参数必须校验。比如GPIO编号要检查是否在合法范围内指针参数要检查是否在WASM的线性内存范围内。第三接口要高效。每次WASM调用宿主函数都有开销所以接口粒度不能太细。比如不要设计gpio_set_direction和gpio_set_level两个接口而是合并成一个gpio_config接口。我设计的接口大概长这样// 宿主接口定义 static int host_gpio_config(wasm_exec_env_t exec_env, int pin, int mode, int pull) { // 参数校验 if (pin 0 || pin 48) return -1; if (mode 0 || mode 2) return -1; // 实际硬件操作 gpio_config_t cfg { .pin_bit_mask (1ULL pin), .mode (gpio_mode_t)mode, .pull_up_en (pull 1), .pull_down_en (pull 2), .intr_type GPIO_INTR_DISABLE, }; return gpio_config(cfg); } static int host_gpio_write(wasm_exec_env_t exec_env, int pin, int level) { if (pin 0 || pin 48) return -1; return gpio_set_level((gpio_num_t)pin, level); } static int host_gpio_read(wasm_exec_env_t exec_env, int pin) { if (pin 0 || pin 48) return -1; return gpio_get_level((gpio_num_t)pin); }注册的时候用WAMR的native symbol机制static NativeSymbol native_symbols[] { {gpio_config, host_gpio_config, (iii)i, NULL}, {gpio_write, host_gpio_write, (ii)i, NULL}, {gpio_read, host_gpio_read, (i)i, NULL}, // ... 更多接口 }; // 注册 wasm_runtime_register_natives(env, native_symbols, sizeof(native_symbols)/sizeof(NativeSymbol));这里的(iii)i是WAMR的签名格式表示三个int参数返回int。这个签名必须和WASM模块里导入的函数声明完全一致否则加载时会报错。3.3 应用的生命周期管理一个应用从安装到运行经历这几个阶段安装用户通过WiFi或者串口把.wasm文件传到ESP32的文件系统里。我实现了一个简单的HTTP上传接口浏览器打开ESP32的IP拖拽文件就能上传。文件存在/spiffs/apps/目录下每个应用一个文件夹里面包含app.wasm和一个manifest.json描述文件。加载用户选择应用后系统读取WASM文件调用wasm_runtime_load加载模块。这一步会做字节码验证确保模块格式正确、没有非法指令。实例化调用wasm_runtime_instantiate创建实例。这一步会分配线性内存、初始化全局变量、解析导入函数。如果导入函数找不到实例化会失败。执行调用WASM模块导出的on_start函数。应用的主逻辑通常是一个事件循环通过导出的on_event函数接收系统事件。销毁应用退出时调用wasm_runtime_deinstantiate和wasm_runtime_unload释放资源。这里有个关键问题WASM应用怎么主动让出CPU如果应用里有个死循环整个系统就卡死了。我的解决方案是宿主接口里提供一个yield()函数应用必须在主循环里定期调用。同时系统用一个硬件定时器做看门狗如果应用超过一定时间没调用yield()就强制销毁实例。// 应用侧代码编译到WASM void on_start() { while (1) { // 做事情 do_something(); // 必须调用yield让出CPU yield(); } }这个设计不是完美的恶意应用可以不调用yield()。但看门狗会兜底最多卡一个超时周期。对于个人项目或者可信应用场景这个方案够用了。3.4 内存管理与资源限制ESP32的RAM很宝贵。普通ESP32有520KB SRAMS3带PSRAM的话能到8MB甚至16MB。WAMR运行时本身要占一部分每个WASM实例又要占一部分。我的配置是WAMR运行时堆池64KB从PSRAM分配每个WASM实例线性内存16KB每个WASM实例栈4KB最大同时运行实例数1ESP32单核跑多个WASM实例不现实这样算下来一个实例占20KB左右加上运行时开销总共不到100KB。对于带PSRAM的S3来说很轻松。实操心得WAMR的线性内存是在实例化时一次性分配的不能动态扩展。所以应用开发者要预估自己的内存需求在编译时通过-Wl,--initial-memory16384指定。如果运行时发现内存不够只能重新编译WASM文件。4. 实操过程与核心环节实现4.1 开发环境搭建先说一下我的开发环境方便你复现硬件ESP32-S3-DevKitC-1带8MB PSRAM和16MB FlashESP-IDFv5.1WAMRv1.3.0编译工具链ESP-IDF自带的riscv32-esp-elf-gccWASM编译工具链wasi-sdk-20用于编译C到WASM安装ESP-IDF的步骤这里不展开官方文档很详细。重点说一下WASM工具链的配置。wasi-sdk下载解压后把bin目录加到PATH里。然后写一个简单的C程序测试// test.c #include stdio.h __attribute__((export_name(add))) int add(int a, int b) { return a b; } __attribute__((export_name(on_start))) void on_start() { printf(Hello from WASM!\n); }编译命令clang --targetwasm32 -nostdlib -Wl,--no-entry \ -Wl,--export-all -Wl,--initial-memory16384 \ -o test.wasm test.c这里几个参数很关键--targetwasm32目标平台是32位WASM-nostdlib不链接标准库因为ESP32上没有WASI环境--no-entry不生成默认入口我们自己指定导出函数--export-all导出所有函数方便调试。生产环境应该只导出需要的函数--initial-memory16384初始线性内存16KB编译出来的test.wasm大概几百字节非常小。4.2 固件端的加载与执行流程固件端的主流程是这样的void app_main() { // 1. 初始化文件系统 esp_vfs_spiffs_conf_t spiffs_conf { .base_path /spiffs, .partition_label storage, .max_files 5, .format_if_mount_failed true, }; esp_vfs_spiffs_register(spiffs_conf); // 2. 初始化WAMR运行时 RuntimeInitArgs init_args; memset(init_args, 0, sizeof(init_args)); init_args.mem_alloc_type Alloc_With_Pool; init_args.mem_alloc_option.pool.heap_buf malloc(64 * 1024); init_args.mem_alloc_option.pool.heap_size 64 * 1024; wasm_runtime_full_init(init_args); // 3. 注册宿主接口 wasm_runtime_register_natives(env, native_symbols, sizeof(native_symbols)/sizeof(NativeSymbol)); // 4. 扫描应用目录 scan_apps(/spiffs/apps); // 5. 加载并运行选定的应用 run_app(/spiffs/apps/hello/app.wasm); }run_app函数的实现void run_app(const char *path) { // 读取WASM文件 FILE *f fopen(path, rb); fseek(f, 0, SEEK_END); int size ftell(f); fseek(f, 0, SEEK_SET); uint8_t *buf malloc(size); fread(buf, 1, size, f); fclose(f); // 加载模块 char error_buf[128]; wasm_module_t module wasm_runtime_load(buf, size, error_buf, sizeof(error_buf)); if (!module) { printf(Load failed: %s\n, error_buf); free(buf); return; } // 实例化 wasm_module_inst_t inst wasm_runtime_instantiate(module, 4096, // 栈大小 16384, // 堆大小 error_buf, sizeof(error_buf)); if (!inst) { printf(Instantiate failed: %s\n, error_buf); wasm_runtime_unload(module); free(buf); return; } // 查找并调用on_start wasm_function_inst_t func wasm_runtime_lookup_function(inst, on_start); if (func) { wasm_exec_env_t exec_env wasm_runtime_create_exec_env(inst, 4096); wasm_runtime_call_wasm(exec_env, func, 0, NULL); wasm_runtime_destroy_exec_env(exec_env); } // 清理 wasm_runtime_deinstantiate(inst); wasm_runtime_unload(module); free(buf); }这段代码跑通之后你就能看到串口输出“Hello from WASM!”。4.3 一个完整的应用示例GPIO控制光跑Hello World没意思我写一个实际能用的应用通过WASM控制ESP32的GPIO实现LED闪烁。应用侧代码// led_blink.c __attribute__((import_module(env), import_name(gpio_config))) int gpio_config(int pin, int mode, int pull); __attribute__((import_module(env), import_name(gpio_write))) int gpio_write(int pin, int level); __attribute__((import_module(env), import_name(delay_ms))) void delay_ms(int ms); __attribute__((import_module(env), import_name(yield))) void yield(void); __attribute__((export_name(on_start))) void on_start() { // 配置GPIO2为输出 gpio_config(2, 1, 0); while (1) { gpio_write(2, 1); // 点亮 delay_ms(500); yield(); gpio_write(2, 0); // 熄灭 delay_ms(500); yield(); } }宿主侧需要补充delay_ms和yield接口static void host_delay_ms(wasm_exec_env_t exec_env, int ms) { if (ms 0 || ms 10000) return; // 限制最大延时 vTaskDelay(pdMS_TO_TICKS(ms)); } static void host_yield(wasm_exec_env_t exec_env) { vTaskDelay(1); // 让出至少一个tick }编译应用clang --targetwasm32 -nostdlib -Wl,--no-entry \ -Wl,--exporton_start -Wl,--initial-memory8192 \ -o led_blink.wasm led_blink.c把led_blink.wasm上传到ESP32的/spiffs/apps/led_blink/目录重启后系统自动加载执行就能看到GPIO2上的LED开始闪烁。这个例子虽然简单但它验证了整条链路WASM编译、上传、加载、实例化、导入函数调用、导出函数执行。把这套跑通剩下的就是扩展接口和优化性能。4.4 性能实测数据我在ESP32-S3上做了一组简单的性能测试对比WASM解释执行和原生C执行的差异测试项原生CWASM解释执行倍率空循环100万次12ms380ms31xGPIO翻转1000次8ms45ms5.6x整数加法100万次3ms95ms31x内存拷贝1KB0.5ms4ms8x解释执行的性能损失在30倍左右这个数字看起来很大但实际用起来没那么可怕。因为大部分应用的时间花在等待IO和延时上真正计算密集的部分不多。GPIO翻转只慢5.6倍因为大部分时间花在宿主函数调用上WASM本身的计算开销占比小。如果确实需要更高性能可以考虑WAMR的AOT模式。把WASM预编译成ESP32的机器码性能能提升到接近原生。但AOT编译需要额外的工具链配置而且生成的机器码和平台绑定失去了WASM跨平台的优势。我的建议是先用解释器模式性能不够再考虑AOT。5. 常见问题与排查技巧实录5.1 加载失败常见错误与解决方法在实际操作中WASM加载失败是最常见的问题。我把遇到过的错误和解决方法整理成表错误信息原因解决方法invalid magic header文件不是WASM格式检查编译命令确保输出的是.wasm文件unknown section idWASM版本不兼容用wamrc工具检查模块版本重新编译import function not found宿主没注册对应接口检查native_symbols数组确保接口名和签名匹配out of memory线性内存或堆池不足增大WAMR堆池或减小应用的initial-memorystack overflowWASM栈太小增大实例化时的栈参数unreachable executed应用执行了非法指令检查应用代码可能是数组越界或空指针其中import function not found是最容易踩的坑。WAMR在实例化时会解析所有导入函数只要有一个找不到就失败。而且错误信息不会告诉你是哪个函数找不到需要自己排查。我的排查方法是先用wasm-objdump工具查看WASM模块的导入表wasm-objdump -x led_blink.wasm | grep Import输出会列出所有导入的函数名和签名然后和宿主注册的对比就能找到缺失的接口。避坑技巧WAMR的签名格式很容易写错。(iii)i表示三个int参数返回int(i)i表示一个int参数返回int。如果签名不匹配实例化时会报function signature mismatch。建议把签名定义成宏避免手写出错。5.2 运行时崩溃内存越界与看门狗WASM的隔离性保证了应用不能直接访问宿主内存但应用自己的线性内存是可以越界的。WAMR默认会做边界检查越界访问会触发trap导致实例被销毁。这比直接崩溃好但用户体验不好。我遇到过一次应用里有个数组索引计算错误写到了线性内存外面。WAMR捕获了trap打印了错误信息然后实例被销毁。系统本身没事但应用挂了。处理方法是在宿主侧捕获trap记录日志然后决定是重启应用还是返回主菜单。wasm_runtime_call_wasm(exec_env, func, 0, NULL); if (wasm_runtime_get_exception(inst)) { printf(App crashed: %s\n, wasm_runtime_get_exception(inst)); wasm_runtime_clear_exception(inst); // 决定是否重启 }另一个问题是看门狗。ESP-IDF默认开启了任务看门狗如果某个任务长时间不让出CPU会触发看门狗复位。WASM应用如果死循环且不调用yield()就会导致系统复位。我的解决方案是在宿主接口的yield()里调用vTaskDelay(1)确保每次yield至少让出一个tick。同时在应用加载时创建一个监控任务如果应用超过5秒没调用yield()就强制销毁实例。// 监控任务 void app_monitor_task(void *arg) { uint32_t last_yield xTaskGetTickCount(); while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); if (xTaskGetTickCount() - last_yield pdMS_TO_TICKS(5000)) { // 超时强制销毁 force_kill_app(); break; } } }5.3 文件系统与OTA更新的坑应用存在SPIFFS里但SPIFFS有个问题频繁写入会导致碎片化最终写入失败。我一开始用SPIFFS存应用装了几个应用之后发现剩余空间越来越小格式化才能恢复。后来换成了LittleFS情况好很多。LittleFS有磨损均衡和掉电保护更适合存应用文件。ESP-IDF对LittleFS的支持也很好直接替换SPIFFS的初始化代码就行。OTA更新方面固件本身可以通过ESP-IDF的OTA机制更新但应用更新更简单直接替换文件系统里的.wasm文件就行。我实现了一个HTTP接口上传新版本WASM文件覆盖旧文件然后重启应用。实操心得更新应用时先上传到临时文件校验通过后再替换正式文件。避免上传中断导致应用文件损坏。校验可以用SHA256在manifest.json里存哈希值。5.4 常见问题速查表现象可能原因排查步骤应用加载后无反应on_start未导出或未调用检查WASM导出表确认on_start存在串口输出乱码波特率不匹配检查menuconfig里的串口波特率系统频繁重启看门狗超时检查应用是否调用了yield()内存不足堆池配置太小增大WAMR堆池或减小应用内存应用间互相影响共享了全局状态确保每个应用独立实例不共享宿主全局变量上传文件失败文件系统满了检查剩余空间删除不用的应用6. 这个平台还能怎么扩展跑通基础功能之后我陆续加了一些扩展让这个平台更实用。第一个扩展是屏幕支持。我加了一个ST7789的驱动通过宿主接口暴露给WASM应用。应用可以调用lcd_draw_pixel、lcd_fill_rect、lcd_draw_text等函数在屏幕上画界面。这样就能做带UI的应用了比如天气显示、时钟、小游戏。第二个扩展是网络接口。把WiFi的HTTP客户端封装成宿主接口应用可以发起HTTP请求获取数据。我写了一个天气应用从公开API获取天气数据显示在屏幕上。这个应用完全用WASM实现固件里没有任何天气相关的代码。第三个扩展是应用商店。在局域网内搭了一个简单的HTTP服务器存放各种WASM应用。ESP32上的应用管理器可以从服务器拉取应用列表一键安装。虽然简陋但“安装应用”的体验已经很像手机了。第四个扩展是应用间通信。我实现了一个简单的消息总线应用可以发布和订阅消息。比如一个应用负责读传感器发布数据另一个应用订阅数据负责显示。这样应用可以组合功能更灵活。这些扩展的核心思路都是一样的固件提供能力应用消费能力。每加一个宿主接口应用能做的事情就多一分但固件本身不需要为每个应用定制。7. 一些个人体会这个项目从起念到跑通断断续续花了大概两个月。中间踩了不少坑也推翻过几次方案。最大的体会是在嵌入式设备上做动态加载WebAssembly是目前最平衡的选择。它不像脚本语言那样性能堪忧也不像ELF动态链接那样复杂且不安全。虽然解释执行的性能损失有30倍但对于大多数应用场景够用了。另一个体会是接口设计比运行时选型更重要。WAMR只是工具真正决定平台好不好用的是宿主接口的设计。接口设计得好应用开发就简单接口设计得烂应用开发者要写一堆胶水代码。我建议在动手之前先想清楚你的平台要支持哪些能力把这些能力抽象成稳定的接口然后再选运行时。最后说一个实际使用中的小技巧给每个应用加一个manifest.json里面记录应用名称、版本、作者、所需权限、内存需求等信息。系统加载应用前先读manifest检查权限和资源是否满足。这样既能做权限控制也能给用户展示应用信息。manifest的格式很简单{ name: LED Blink, version: 1.0.0, author: me, permissions: [gpio], memory: 8192, entry: on_start }系统根据permissions决定注册哪些宿主接口。比如应用没有gpio权限就不注册GPIO相关的接口应用调用时会因为找不到导入函数而加载失败。这是一种粗粒度的权限控制但实现简单效果不错。这个平台目前还在持续迭代后面我打算试试WAMR的AOT模式看看性能能提升多少。另外也在考虑支持Rust编译到WASM给应用开发者更多选择。如果你也在做类似的事情欢迎交流。
返回列表