1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZephyrのLLEXTを試す

1
Last updated at Posted at 2026-05-03

概要

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
の内容を変えないと、正しく動かないのかもしれない。

長くなってきたので、一旦ここまで。
次はこのエラーを調査する。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?