3. User Space
The SO3 user space is a small, self-contained set of applications built against
the MUSL C library. Everything lives under so3/usr/.
3.1. The MUSL C library
SO3 uses MUSL as its libc for user applications — a small, clean and
static-friendly implementation well suited to embedded systems. Not every libc
function is enabled; functions are pulled in as the need arises. More complex
facilities (for example full pthreads) are intentionally kept minimal.
Applications are linked statically against MUSL, so each executable is self-contained.
3.2. Build system (CMake)
The user space is built with CMake and the MUSL cross toolchain by the
usr-so3 recipe (meta-usr). The MUSL toolchain (aarch64-linux-musl for
64-bit platforms, arm-linux-musleabihf for 32-bit ones) is produced by
meta-toolchain, so there is no manual toolchain step. Build and (re)deploy the
user space with:
build.sh -x usr-so3 # configure + cross-compile (CMake + MUSL)
deploy.sh -x usr-so3 # repopulate so3/rootfs/rootfs.fat with the apps
deploy.sh bsp-so3 # repack rootfs into the FIT image + write the boot media
The recipe configures and builds under so3/usr/build/ and gathers the
deployable, statically-linked *.elf binaries for the root filesystem. Adding
an application means dropping a C file in so3/usr/src/ and referencing it from
the relevant CMakeLists.txt.
3.3. Applications
The standard applications in so3/usr/src/ include:
Program |
Role |
|---|---|
|
the init process: reads |
|
the interactive shell (the |
|
basic file utilities ( |
|
ICMP ping (exercises the lwIP stack) |
|
simple timing utility |
|
minimal example |
|
API and subsystem demonstrations |
|
LVGL graphical demos (framebuffer builds — see LVGL — Light and Versatile Embedded Graphics Library) |
|
minimal framebuffer test, straight to |
MicroPython |
the MicroPython interpreter (ARM64 — see MicroPython) |
User-space libraries used by the applications live in usr/lib/ (the LVGL
graphics library, logging helpers, and so on).
File timestamps are real: the kernel reads a PL031 real-time clock (QEMU
virt exposes one at 0x09010000, seeded from the host) so gettimeofday /
clock_gettime(CLOCK_REALTIME) return wall-clock time. Newly written files get
the current time (FatFS get_fattime), and touch refreshes an existing
file’s modification time via the utimensat syscall.
3.4. Init and the shell
When the kernel hands over to user space, the root process execve()s
init.elf. Init reads a small commands.ini script and runs it line by
line; a typical script prints a banner and starts the shell:
echo SO3 Init Program :)
shell
The shell then presents a prompt showing the current working directory (e.g.
/ % at the root, /dev % after cd dev) and runs commands by forking
and execve()-ing the corresponding .elf. A bare command name (no
/) is looked up in the root filesystem — all executables live there and
there is no PATH — while a name containing / is resolved against the
current directory. It supports:
Syntax |
Meaning |
|---|---|
|
run |
|
pipeline (any number of stages) |
|
redirect stdout (truncate / append) |
|
redirect stdin from a file |
|
run in the background (reaped on the next prompt) |
|
single quotes (literal) / double quotes (with expansion) |
|
environment variable expansion |
|
builtins |
Operators (| < > >> &) must be surrounded by whitespace. Built-in
setenv NAME VALUE sets a variable (setenv NAME unsets it) and env
lists the environment. cd / pwd change and print the working directory
(see the note below).
When stdin is the console the shell provides interactive line editing: a
HIST_MAX-entry command history (Up / Down arrows, listed by the
history builtin), cursor movement (Left / Right, Home / End) and mid-line
insert/backspace. It does this by briefly switching
the console to raw mode (clearing ICANON/ECHO via the TCSETS ioctl
that console.c implements) for the duration of the line edit,
then restoring canonical mode so spawned programs see a normal cooked terminal.
On non-tty input (e.g. init feeding commands.ini) it falls back to plain
line reading.
Note
Each process has a current working directory (pcb->cwd, default
/, inherited across fork() and preserved across execve()). The
chdir / getcwd syscalls back the cd / pwd builtins, and the
VFS resolves relative paths (and . / ..) against the cwd before
handing an absolute path to the filesystem. With the default cwd / a bare
foo still resolves to /foo, so existing absolute-from-root behaviour
is unchanged.
3.5. Root filesystem
The applications are delivered through a root filesystem. In the default
(ramfs) configuration this is a FAT image, so3/rootfs/rootfs.fat, built by the
rootfs-so3 recipe (meta-rootfs) from the freshly built user binaries.
deploy.sh bsp-so3 packs it into the FIT image and writes it to
the SD-card (User Guide); build.sh bsp-so3 does the whole
build + image in one step. The FAT image can be inspected on the host (e.g. with
mtools) — useful when debugging what actually ended up on the target.