Linkable loadable extensions EDK
Overview
This sample demonstrates how to use the Zephyr LLEXT EDK (Extension Development Kit). It is composed of one Zephyr application, which provides APIs for the extensions that it loads. The provided API is a simple publish/subscribe system, based on Zbus, which extensions use to communicate with each other.
The application is composed of a subscriber thread, which listens for events
published and republishes them via Zbus to the extensions that are
subscribers. There are four extensions, which are loaded by the application and
run in different contexts. Extensions ext1
, ext2
and ext3
run in
userspace, each demonstrating different application and Zephyr API usage, such as
semaphores, spawning threads to listen for events or simply publishing or
subscribing to events. Extension kext1
runs in a kernel thread, albeit similar
to ext3
.
The application also creates different memory domains for each extension, thus providing some level of isolation - although the kernel one still has access to all of Zephyr kernel.
Note that the kernel extension is only available when the EDK is built with
the CONFIG_LLEXT_EDK_USERSPACE_ONLY
option disabled.
The application is built using the Zephyr build system. The EDK is built using
the Zephyr build system as well, via llext-edk
target. The EDK is then
extracted and the extensions are built using CMake.
Finally, the way the application loads the extensions is by including them during build time, which is not really practical. This sample is about the EDK providing the ability to build extensions independently from the application. One could build the extensions in different directories, not related to the Zephyr application - even on different machines, using only the EDK. At the limit, one could even imagine a scenario where the extensions are built by different teams, using the EDK provided by the application developer.
Building the EDK
To build the EDK, use the llext-edk
target. For example:
west build -b qemu_cortex_r5 -p=always samples/subsys/llext/edk/app
west build -t llext-edk
Copy the EDK to some place and extract it:
mkdir /tmp/edk
cp build/zephyr/llext-edk.tar.xz /tmp/edk
cd /tmp/edk
tar -xf llext-edk.tar.xz
Then set LLEXT_EDK_INSTALL_DIR
to the extracted directory:
export LLEXT_EDK_INSTALL_DIR=/tmp/edk/llext-edk
This variable is used by the extensions to find the EDK.
Building the extensions
The ZEPHYR_SDK_INSTALL_DIR
environment variable is used by the
extensions to find the Zephyr SDK, so you need to ensure it’s properly set:
export ZEPHYR_SDK_INSTALL_DIR=</path/to/zephyr-sdk>
To build the extensions, in the ext1
, ext2
, ext3
and kext1
directories:
cmake -B build
make -C build
Alternatively, you can set the LLEXT_EDK_INSTALL_DIR
directly in the
CMake invocation:
cmake -B build -DLLEXT_EDK_INSTALL_DIR=/tmp/edk/llext-edk
make -C build
Building the application
Now, build the application, including the extensions, and run it:
west build -b qemu_cortex_r5 -p=always samples/subsys/llext/edk/app
west build -t run
You should see something like:
[app]Subscriber thread [0x20b28] started.
[app]Loading extension [kext1].
[app]Thread 0x20840 created to run extension [kext1], at privileged mode.
[k-ext1]Waiting sem
[app]Thread [0x222a0] registered event [0x223c0]
[k-ext1]Waiting event
[app]Loading extension [ext1].
[app]Thread 0x20a30 created to run extension [ext1], at userspace.
[app]Thread [0x20a30] registered event [0x26060]
[ext1]Waiting event
[app]Loading extension [ext2].
[app]Thread 0x20938 created to run extension [ext2], at userspace.
[ext2]Publishing tick
[app][subscriber_thread]Got channel tick_chan
[ext1]Got event, reading channel
[ext1]Read val: 0
[ext1]Waiting event
[k-ext1]Got event, giving sem
[k-ext1]Got sem, reading channel
[k-ext1]Read val: 0
[k-ext1]Waiting sem
(...)