概要
の続きで、外だしのllextを試す。
その後、llext内でスレッドを起動する。
MPUエラーのデバッグ
[00:00:00.975,341] <dbg> llext: do_llext_load: Loaded llext: 40 bytes in heap, .text at 0x20002404, .rodata at 0x20
[00:00:00.986,724] <inf> llext: Loaded extension ext
[00:00:00.992,156] <err> os: ***** MPU FAULT *****
[00:00:00.997,619] <err> os: Instruction Access Violation
[00:00:01.003,906] <err> os: r0/a1: 0x20002405 r1/a2: 0x0000ae1e r2/a3: 0x00000000
[00:00:01.012,634] <err> os: r3/a4: 0x00000000 r12/ip: 0x00002b55 r14/lr: 0x000006ed
[00:00:01.021,301] <err> os: xpsr: 0x61000000
[00:00:01.026,550] <err> os: Faulting instruction address (r15/pc): 0x20002404
LLEXTのコードが.text at 0x20002404にロードされて、
その領域のコード実行が禁止となっている。
このMPU FAULTが起きたビルド環境を見ると、
$grep -n "CONFIG_ARM_MPU" sample_llext_module/zephyr/.config
721:CONFIG_ARM_MPU_REGION_MIN_ALIGN_AND_SIZE=32
766:# CONFIG_ARM_MPU_SRAM_WRITE_THROUGH is not set
779:CONFIG_ARM_MPU=y
なので、CONFIG_ARM_MPUがyになっていた。
なので、
$ west build -d sample_llext_module -b nrf52840dk/nrf52840 -T sample.llext.modules.module_build samples/subsys/llext/modules/
でsample.yamlを見て、CONFIG_ARM_MPU=nにしたつもりでも、どこかでyに戻されている。
どこでyにしているのかがわからないので、一旦強制的にnにする
$cat > /tmp/llext.conf <<'EOF'
CONFIG_HELLO_WORLD_MODE=m
CONFIG_ARM_MPU=n
CONFIG_ARM_AARCH32_MMU=n
EOF
$west build -p sample_llext_module -b nrf52840dk/nrf52840 samples/subsys/llext/modules -- -DEXTRA_CONF_FILE=/tmp/llext.conf
[185/185] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used
FLASH: 54844 B 1 MB 5.23%
RAM: 74000 B 256 KB 28.23%
IDT_LIST: 0 B 32 KB 0.00%
ちなみに、静的リンク時は
[186/186] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used
FLASH: 42804 B 1 MB 4.08%
RAM: 7752 B 256 KB 2.96%
IDT_LIST: 0 B 32 KB 0.00%
なので、動的LLEXTの方がFLASHもRAMも使用量が多い。
どうもLLEXT実行のために、Loaderや専用RAMが切られるので、こうなってしまう様子。
なので、あまり小さなプログラムを実行する場合はこういったデメリットがあると分かった。
なおできたイメージをwest flashすると、hello worldがllextから実行された。
*** Booting Zephyr OS build v4.4.0-1647-g29d97bdaf61b ***
[00:00:00.255,920] <inf> app: Calling hello world as a module
[00:00:00.262,207] <dbg> llext: do_llext_load: Loading ELF data...
[00:00:00.268,768] <dbg> llext: llext_load_elf_data: Loading relocatable ELF
[00:00:00.276,275] <dbg> llext: do_llext_load: Finding ELF tables...
・・・
[00:00:00.958,953] <dbg> elf: arch_elf_relocate: 2 200022b0 2000228d hello_world
[00:00:00.966,796] <dbg> llext: llext_export_symbols: sym 0x2000228d name hellod
[00:00:00.975,067] <dbg> llext: do_llext_load: Loaded llext: 40 bytes in heap, 0
[00:00:00.986,450] <inf> llext: Loaded extension ext
Hello, world, from an llext!
[00:00:00.994,445] <dbg> llext: llext_free_regions: freeing memory region 0
[00:00:01.001,861] <dbg> llext: llext_free_regions: freeing memory region 4
続いて、以下のコードで、hello_worldをスレッドのエントリに変えてみた。
diff --git a/samples/subsys/llext/modules/src/hello_world_ext.c b/samples/subsys/llext/modules/src/hello_world_ext.c
index b7251d6917d..80c5af148e6 100644
--- a/samples/subsys/llext/modules/src/hello_world_ext.c
+++ b/samples/subsys/llext/modules/src/hello_world_ext.c
@@ -13,7 +13,7 @@
#include <zephyr/llext/symbol.h>
#include <zephyr/sys/printk.h>
-
+#include <zephyr/kernel.h>
void hello_world(void)
{
#if defined(CONFIG_HELLO_WORLD_MODE)
@@ -22,6 +22,10 @@ void hello_world(void)
#else
/* HELLO_WORLD_MODE=m: CONFIG_*_MODULE is defined instead */
printk("Hello, world, from an llext!\n");
+ while (1) {
+ printk("Hello from LLEXT thread\n");
+ k_sleep(K_SECONDS(1));
+ }
#endif
}
EXPORT_SYMBOL(hello_world);
diff --git a/samples/subsys/llext/modules/src/main_module.c b/samples/subsys/llext/modules/src/main_module.c
index fd7f3dd5bf7..c15e61083e6 100644
--- a/samples/subsys/llext/modules/src/main_module.c
+++ b/samples/subsys/llext/modules/src/main_module.c
@@ -10,11 +10,17 @@ LOG_MODULE_REGISTER(app);
#include <zephyr/llext/llext.h>
#include <zephyr/llext/buf_loader.h>
+#include <zephyr/kernel.h>
static uint8_t llext_buf[] = {
#include "hello_world_ext.inc"
};
+extern void ext_thread_entry(void *, void *, void *);
+
+K_THREAD_STACK_DEFINE(ext_stack, 1024);
+static struct k_thread ext_thread;
+
int main(void)
{
LOG_INF("Calling hello world as a module");
@@ -40,7 +46,14 @@ int main(void)
return -1;
}
- hello_world_fn();
+ //hello_world_fn();
+
+ k_thread_create(&ext_thread,
+ ext_stack,
+ 1024,
+ hello_world_fn,
+ NULL, NULL, NULL,
+ 5, 0, K_NO_WAIT);
結果、以下の通り、llext側のスレッドのエントリがコールされて、スレッドの処理になった。
[00:00:01.049,377] <dbg> llext: llext_link: relocation 5:1 info 0xa02 (type 2, 1
[00:00:01.063,110] <dbg> llext: llext_link: writing relocation type 2 at 0x2000)
[00:00:01.074,401] <dbg> elf: arch_elf_relocate: 2 20002800 200027c5 hello_world
[00:00:01.082,244] <dbg> llext: llext_export_symbols: sym 0x200027c5 name hellod
[00:00:01.090,515] <dbg> llext: do_llext_load: Loaded llext: 96 bytes in heap, 8
[00:00:01.101,898] <inf> llext: Loaded extension ext [00:00:01.107,299] <dbg> llext: llext_free_regions: freeing memory region 0
[00:00:01.114,685] <dbg> llext: llext_free_regions: freeing memory region 4
Hello, world, from an llext!
Hello from LLEXT thread
Hello from LLEXT thread
main側でK_THREAD_STACK_DEFINEせずに、hello_world側でスレッドを完結しようとすると、
[00:00:00.586,212] <dbg> llext: llext_map_sections: section 8 name .noinit.WEST3 [00:00:00.601,226] <err> llext: Multiple SHT_NOBITS sections are not supported [00:00:00.609,130] <err> llext: Failed to map ELF sections, ret -134
とLoaderのエラーになるので注意。
ここまでのまとめ
LLEXTの内包と外だしの方法は分かった。
LLEXTは「任意の未知バイナリを後から何でも実行できる仕組み」というより、本体側が用意したABI/呼び出し規約に合う拡張モジュールを後からロードする仕組みだと理解できた。
LLEXTは後からロードできるが、本体側に“受け口”が必要。受け口のABIを設計していないものはロードできても安全に実行できないので、ちょっと不便。
次は、LLEXTを以下のような拡張の切り替えで使えるかを検討する。
本体Zephyrを焼き直さずに機能差し替え
オプション機能を後からロード
現場で拡張タスクを更新
FOTA対象を小さくする
【実用例】
本体: kernel / driver / loader / 通信 / shell
拡張: UI処理 / BLE処理 / センサ処理 / 制御ロジック