概要
LLEXTを試して、Zephyrの理解を深める。
Zephyr Version 4.4
SDK Version 1.01
を使う
ボードはnrf52840dkを使う。
LLEXTとは
実行中のZephyrのアプリに後からロード・リンクできるELF形式の拡張コード。
llext_load() でロードし、拡張側が LL_EXTENSION_SYMBOL() で公開した関数をベース側から呼ぶ
main.c
↓
ext_file.llext をロード
↓
"start_extension" シンボルを探す
↓
関数ポインタとして呼ぶ
↓
LLEXT側で k_thread_create()
Zephyrのプロジェクト内では
samples/subsys/llext/modules
がサンプル。
今回はLLEXTに慣れるため、add_llext_targetに書いたり、EDKを作ってみたりして、llextファイルを作って、さらにその中でスレッドを使ってみて、機能別のタスクをどんどん切り替える機構を試す。
Zephyrのビルド生成物の理解
Zephyr.elf内のmain()を中心に、どうなっているかを調べる。
$ west build -d sample_button -b nrf52840dk/nrf52840 samples/basic/button/
$ west build -d sample_hello -b nrf52840dk/nrf52840 samples/hello_world
こんな感じで2つのサンプルのビルドを実行する。
ビルドログを見ると、サンプルのmain.cは以下のようにコンパイル・リンクされる。
main.c.obj
↓
libapp.a
↓
zephyr.elf
それぞれのzephyr.elfでmainを探すと、
(.venv) $objdump -t sample_button/zephyr/zephyr.elf | grep main
00000000 l df *ABS* 00000000 main.c
00002bcd l F text 000000bc bg_thread_main
00003031 l F text 00000160 work_queue_main
2000061c l O bss 00000004 announce_remaining
20001440 g O noinit 00000440 z_main_stack
00000e1d g F text 0000003c arch_switch_to_main_thread
20000350 g O bss 00000080 z_main_thread
000004f9 g F text 0000001c main
000024c9 g F text 0000001c nrfx_gppi_domain_conn_alloc
(.venv) $objdump -t sample_hello/zephyr/zephyr.elf | grep main
00000000 l df *ABS* 00000000 main.c
000023dd l F text 000000bc bg_thread_main
20000384 l O bss 00000004 announce_remaining
20000d80 g O noinit 00000440 z_main_stack
00000a21 g F text 0000003c arch_switch_to_main_thread
20000160 g O bss 00000080 z_main_thread
00000101 g F text 00000018 main
00001ddd g F text 0000001c nrfx_gppi_domain_conn_alloc
2つのelfで、main()はアドレスが異なっているとわかる。
これは、Zephyrにおいて、mainがエントリアドレスではなくて、スレッド関数であるため。
Zephyrにおいて、mainは固定アドレスではないことがわかった。
実際のmain()の呼び出しの流れは、
リセット
↓
アーキ依存のスタートアップ
↓
z_cstart()
↓
kernel初期化
↓
mainスレッド生成
↓
main() 呼び出し
となる。
mainのアドレスが固定なら、Zephyrのアプリ部分だけの差し替えができるかと思ったけど、それは無理っぽいので、LLEXTを使った機能切り替えに移る。
スレッドについて
いきなりLLEXTに行く前に、Zephyrのスレッドについて確認する。
k_thread_create(動的生成)
#include <zephyr/kernel.h>
#define STACK_SIZE 1024
#define PRIORITY 5
K_THREAD_STACK_DEFINE(my_stack, STACK_SIZE);
struct k_thread my_thread;
void my_entry(void *p1, void *p2, void *p3)
{
while (1) {
printk("Hello from my thread\n");
k_sleep(K_MSEC(1000));
}
}
void main(void)
{
k_thread_create(&my_thread, my_stack, STACK_SIZE,
my_entry,
NULL, NULL, NULL,
PRIORITY, 0, K_NO_WAIT);
}
ポピュラーな感じ。
K_THREAD_DEFINE(静的生成)
#define STACK_SIZE 1024
#define PRIORITY 5
void my_entry(void *p1, void *p2, void *p3)
{
while (1) {
printk("Static thread\n");
k_sleep(K_MSEC(1000));
}
}
K_THREAD_DEFINE(my_tid, STACK_SIZE,
my_entry,
NULL, NULL, NULL,
PRIORITY, 0, 0);
これはシステム起動時に自動でスレッド生成される。
つまり、main()以外のエントリを持つスレッドを作り、main()と同時に動作できる。
遅延指定や優先度指定が可能。
LLEXT
sampleの実行(Static)
まずは素直にビルドする
$ west build -d sample_llext -b nrf52840dk/nrf52840 samples/subsys/llext/modules/ 2>&1 | tee build.log
ビルドログを見ると、
[14/186] Building C object CMakeFiles/app.dir/src/hello_world_ext.c.obj
[21/186] Building C object CMakeFiles/app.dir/src/main_builtin.c.obj
[22/186] Linking C static library app/libapp.a
なので、libapp.aにこの2つが入っている。
arで見ても、たしかにそうなっている。
$ ar t sample_llext/app/libapp.a
main_builtin.c.obj
hello_world_ext.c.obj
出来上がりのzephyr.elfでhelloを探すと、
$objdump -t sample_llext/zephyr/zephyr.elf | grep hello
00000000 l df *ABS* 00000000 hello_world_ext.c
00008560 l O llext_const_symbol_area 00000008 __llext_sym_hello_world
00000699 g F text 0000000c hello_world
なのでこれは、
samples/subsys/llext/modules/CMakeLists.txt
で、
elseif(CONFIG_HELLO_WORLD_MODE STREQUAL "y")
のビルドが行われて、StaticにLLEXTが組み込まれた。
ボードにwest flashすると、
*** Booting Zephyr OS build v4.4.0-1647-g29d97bdaf61b ***
[00:00:00.255,950] <inf> app: Calling hello world as a builtin
Hello, world, from the main binary!
と、意図したとおりにコールされている様子。
sampleの実行(dynamic)
次はCONFIG_HELLO_WORLD_MODE=mになるようにビルドする。
$ west build -d sample_llext_module -b nrf52840dk/nrf52840 -T sample.llext.modules.module_build samples/subsys/llext/modules/
-Tオプションの引数が、sample.yamlのtestsのsample.llext.modules.module_buildを参照するように指定している。
ビルドできると、
$ls ./sample_llext_module/llext/
hello_world.llext hello_world_ext_debug.elf hello_world_ext_llext_lib.obj
と、外だしでllextとelfが作成される。
$objdump -t sample_llext_module/zephyr/zephyr.elf | grep hello
でhello_worldがないので、静的に組み込まれていないことがわかる。
これをボードにwest flashすると、
*** Booting Zephyr OS build v4.4.0-1647-g29d97bdaf61b ***
[00:00:00.255,889] <inf> app: Calling hello world as a module
[00:00:00.262,176] <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.947,937] <dbg> llext: llext_link: writing relocation type 2 at 0x20002428 with symbol hello_world (0x200)
[00:00:00.959,228] <dbg> elf: arch_elf_relocate: 2 20002428 20002405 hello_world
[00:00:00.967,041] <dbg> llext: llext_export_symbols: sym 0x20002405 name hello_world
[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
[00:00:01.034,454] <err> os: >>> ZEPHYR FATAL ERROR 20: Unknown error on CPU 0
[00:00:01.042,358] <err> os: Current thread: 0x200005b8 (unknown)
[00:00:01.049,133] <err> os: Halting system
と、MPU FAULTが発生した。
prj.confにも
# To build it as an llext, please follow the instructions in the documentation
# of this sample; there are architecture-specific settings that must be set in
# addition to CONFIG_HELLO_WORLD_MODE=m. For example, most ARM targets need to
# either enable CONFIG_USERSPACE (if they support it) or disable MPU/MMU
# features for LLEXT to work correctly.
とあるので、
sample.llext.modules.module_build
の内容を変えないと、正しく動かないのかもしれない。
長くなってきたので、一旦ここまで。
次はこのエラーを調査する。