FlashOS

AArch64 bare-metal kernel for the Raspberry Pi 4B and QEMU -M virt

CI Coverage Version Zig 0.16.0 aarch64-elf License

Documentation · Setup · Port · Versioning · Changelog · License

English · Deutsch

---

FlashOS booting on a Raspberry Pi into the fsh shell

> The boot above is a captured serial console of FlashOS booting on real > Raspberry Pi 4B hardware to the `login:` prompt; the trailing `fsh` > session — `help`, `ls`, and `sysinfo` — replays the shell's real output at > a readable cadence, before a final `reboot` loops the demo back to the boot. ## About FlashOS is a bare-metal AArch64 kernel that boots on Raspberry Pi 4B hardware and under QEMU. The kernel core is written in [Flash](https://github.com/ajhahnde/Flash) — a systems language that transpiles to Zig — with the boot path, exception vectors, and context switch in AArch64 assembly. The build is driven entirely by `build.zig`, which transpiles the `.flash` modules through a pinned `flashc`. The current release ships with a complete uniprocessor process lifecycle (`fork`, `exec`, `exit`, `wait`, `kill`), leak-free across stress cycles, exercised by an in-kernel `[TEST]/[PASS]/[FAIL]` harness and a host-side unit test suite. ## Specifications | | | | :--------------------- | :------------------------------------------------------------------------------------------ | | **Hardware** | Raspberry Pi 4 Model B (BCM2711) | | **Architecture** | AArch64 (ARMv8-A) | | **Languages** | Flash (transpiled to Zig) + AArch64 assembly | | **Toolchain** | `flashc` (pinned) + Zig 0.16.0 +`aarch64-elf` binutils | | **Targets** | RPi 4B hardware,`qemu-system-aarch64 -M raspi4b`, _and_ `qemu-system-aarch64 -M virt` | ## Features - **Two-stage boot.** EL3 armstub configures the GIC and `eret`s into the kernel at EL1 (Pi). On QEMU `-M virt`, `boot.S` does the EL3→EL1 drop itself. - **Dual-target build.** `-Dboard=rpi4b` or `-Dboard=virt` switches the per-board driver bag (`uart`, `gpio`, `timer`, `irq`), the linker script, and the boot quirks at comptime. - **Four-level MMU.** Identity map for early bring-up, linear-high map for the kernel, demand-allocated user pages with per-region flags (text RX, data/heap/stack RW+UXN). - **Priority round-robin scheduler** with timer-driven preemption. - **Process lifecycle.** `fork` / `exec` / `exit` / `wait` / `kill`, zombie reap path, leak-free across stress cycles. - **ELF64 loader.** `sys_execve` resolves a path through the VFS, streams each PT_LOAD segment into a freshly built address space with the right permissions, and eagerly maps the top stack page before copying the argv block onto the new user stack. - **Userland mini-libc (`flibc`).** SVC wrappers, `printf` over `sys_writeConsole`, bump allocator over `brk` / `sbrk`, `fork` / `wait` / `exit` / `execve`. Linked into ELF demos by the build, kept under `user_space/lib/flibc/`. - **Heap via `sys_brk` / `sys_sbrk`.** Pages are demand-allocated by the page-fault path inside `[HEAP_BASE, brk)`; shrinks unmap and free. - **Region-aware page-fault dispatch.** `do_data_abort` classifies by user VA region (heap / stack / stack-guard / text / wild) and panics-and-zombies on out-of-region access; the parent's `sys_wait` reaps the offender so the harness keeps running. - **Stack guard.** A 1-page unmapped region below the legal stack range turns runaway recursion into a `[KERN] stack overflow` diagnostic instead of memory corruption. - **Unified file descriptors.** A single tagged `fds` table per task (`console` / `pipe` / `file`) behind one `read` / `write` / `close` / `dup2` ABI; fd 0/1/2 are pre-installed console slots, `fork` inherits the table and `execve` preserves it, so a shell can hand a child redirected stdio. Anonymous pipes (`sys_pipe`) ride the same table. - **Interactive shell (`fsh`).** A userland REPL at `/bin/fsh` over a mini-libc (`flibc`): a `readline` line editor with TAB completion (double-TAB lists candidates), a tokenizer with a single `|` pipe stage, in-process built-ins (`cd` / `pwd` / `exit` / `logout` / `help` / `free` / `whoami` / `reboot`), a Unix-style `#`/`$` privilege prompt, and `fork` + `execvp` (`/bin/` resolution) for externals — plus `/bin/echo`, `/bin/cat`, `/bin/ls` (the stateless `sys_readdir` consumer), `/bin/grep` (literal line search), `/bin/cp` / `/bin/mv` / `/bin/rm` (FAT32 file management over the create/unlink/rename syscalls), `/bin/meminfo`, `/bin/forkbomb` (a capped leak probe), `/bin/sysinfo` (a key/value system summary), `/bin/cpuinfo` (CPU temperature + clock), `/bin/uptime` (time since boot), `/bin/less` (a full-screen pager), `/bin/edit` (a full-screen text editor), `/bin/clear` (a screen wipe), and `/bin/passwd`. Reads `/etc/fshrc` at startup; `sys_chdir` gives each task a working directory. The coreutils use fixed-size stack/static buffers; the userland heap (`brk`/`sbrk` behind flibc's bump `malloc`) has its first consumer in `/bin/edit`'s growable buffer. - **Process identity, login & permissions.** Every task carries real + effective uid/gid (inherited across `fork`, preserved across `execve`) behind a `getuid`/`setuid`-family ABI, and every file carries mode/uid/gid metadata enforced at the open/write/exec syscall boundary (`-EACCES`, root bypasses). Boot runs `/bin/login` as a session supervisor: the kernel verifies the password with PBKDF2-HMAC-SHA256 + a constant-time compare (`sys_authenticate` — the KDF never leaves the kernel), then login forks a child that drops privilege and execs the user's shell; `exit` returns to the `login:` prompt. Passwords live in a writable `/mnt/shadow` on the SD card (protected to `0600 root:root` by a FAT32 permission overlay, with the read-only initramfs seed as the always-bootable fallback) and are changed with `passwd` / `sys_passwd` — fresh kernel-minted salt, splice-safe in-place rewrite. Password echo is suppressed through `SYS_SET_CONSOLE_MODE`. The seed accounts use fixed public salts (build reproducibility); rotated records get random salts. - **Syscalls** dispatched via `svc` and an indexed table — see [Documentation §5](DOCUMENTATION.md#5-syscalls--exceptions). - **USB-C gadget console.** The Pi's USB-C port enumerates as a CDC-ACM serial device (BCM2711 DWC2 OTG — Full-Speed, polled, slave/PIO): a single C-to-C cable to a Mac carries both power and the interactive `fsh` console (`/dev/tty.usbmodem…`, no driver install). User/shell output switches to USB when enumerated and falls back to the Mini-UART otherwise. - **Two UARTs.** Mini-UART (UART1) for the console fallback + kernel diagnostics, dedicated PL011 for an out-of-band trace channel. - **Kernel symbol table** generated by a two-pass `populate-syms` step and consumed by the function-entry tracer (runtime intact, but currently inert — Zig has no `-fpatchable-function-entry=2` equivalent yet). - **In-kernel test harness** (`[TEST]/[PASS]/[FAIL]` + tally, 30 scenarios) plus a host-side `zig build test` suite (468 host tests across 41 modules). ## Quick start Install the toolchain: ```bash brew install zig aarch64-elf-binutils qemu ``` FlashOS's source modules are written in [Flash](https://github.com/ajhahnde/Flash) and transpiled to Zig at build time by `flashc`. Build the pinned compiler once — `build.zig` looks for it at `~/Flash/zig-out/bin/flashc-stage1` by default (override with `-Dflashc=`): ```bash git clone https://github.com/ajhahnde/Flash.git ~/Flash git -C ~/Flash checkout "$(grep -oE '[0-9a-f]{40}' flash-toolchain.lock)" ( cd ~/Flash && zig build stage1 ) # → ~/Flash/zig-out/bin/flashc-stage1 ``` Build everything for the Pi (`kernel8.img` + `armstub8.bin` land in `zig-out/`): ```bash zig build # default: -Dboard=rpi4b ``` Or build for QEMU `-M virt` (no armstub): ```bash zig build -Dboard=virt ``` Run the kernel under QEMU: ```bash zig build -Dboard=rpi4b run # raspi4b machine (Pi 4 model) ``` ```bash zig build -Dboard=virt run-virt # generic ARMv8 virt machine ``` Run host-side unit tests (page allocator + ELF parser): ```bash zig build test ``` For the full hardware flow (two-pass build with symbol-table population and an interactive `deploy` prompt): ```bash ./build.sh ``` See [Setup](SETUP.md) for the SD-card layout, firmware files, and serial-console setup. ## Build steps | Step | What it does | | :------------------------------------- | :------------------------------------------------------------- | | `zig build` (or `-Dboard=rpi4b`) | Default — Pi:`kernel8.img` + `armstub8.bin` | | `zig build -Dboard=virt` | virt:`kernel8.img` only (no armstub) | | `zig build kernel` | Kernel image only | | `zig build armstub` (rpi4b only) | Armstub only | | `zig build populate-syms` | Regenerate `src/symbol_area.S` from the linked ELF | | `zig build deploy` (rpi4b only) | Copy artefacts + RPi firmware to `$SD_BOOT` | | `zig build -Dboard=rpi4b run` | Boot under `qemu-system-aarch64 -M raspi4b` | | `zig build -Dboard=virt run-virt` | Boot under `qemu-system-aarch64 -M virt` | | `zig build -Dboard=virt test-virt` | Boot virt, watchdog asserts the boot reaches the fsh prompt | | `zig build -Dboard=rpi4b test-rpi4b` | Boot raspi4b, watchdog asserts the boot reaches the fsh prompt | | `zig build -Dboard=virt iso` | Build a GRUB-EFI rescue ISO (virt only) | | `zig build test` | Host-side unit tests (468 tests, 41 modules) | | `zig build clean` | Remove `.zig-cache/` and `zig-out/` | The default optimisation mode is `ReleaseSmall`. Override with `-Doptimize=ReleaseSafe` (or `Debug`, `ReleaseFast`). ## Repository layout ```text src/ kernel core (Flash + AArch64 assembly) src/board// per-board driver bag (rpi4b / virt) + linker script user_space/ PID 1 image + in-kernel test harness user_space/lib/flibc/ userland mini-libc for ELF demos lib/ shared kernel↔user constants (syscall IDs) tools/ hand-rolled ELF demos (hello, stackbomb, flibc_demo) tests/ host-side unit tests armstub/ EL3 → EL1 bootstrap shim (Pi only) scripts/ symbol-table generation, iso, QEMU test watchdog, Pi-baseline verifier assets/ logo and visual assets build.zig the only build entry point build.sh two-pass build orchestrator + deploy prompt flash-toolchain.lock pinned flashc revision (Flash→Zig transpiler) config.txt RPi 4 firmware configuration ``` A deeper walk-through of each subsystem is in [Documentation](DOCUMENTATION.md). ## Versioning `v[MAJOR].[MINOR].[PATCH]`. Per-tag notes live on the [releases page](https://github.com/ajhahnde/FlashOS/releases). ## AI assistance The prose docs in this repo (README, DOCUMENTATION, CHANGELOG, PORT) are LLM-drafted under my review. They're kept honest by the build, not by trust: the OS is verified by booting it, not by describing it. - Boots to a login shell on QEMU `virt` and Raspberry Pi 4B from the same kernel ABI - `-Dboot-selftest=true` runs the in-kernel `[TEST]` harness at PID 1 before the login prompt — process, filesystem, memory-fault, and device scenarios, each bracketed by free-page checkpoints to surface leaks - The kernel is written in Flash and transpiled to Zig via the sibling `flashc` compiler — pinned in `flash-toolchain.lock` If a doc claims a subsystem works, the boot path is what exercises it. The docs are also kept current by an automated drift check that keeps the contract values quoted across them — version, boot-contract numbers, ABI constants — in sync with the live tree, so a stale copy is caught rather than shipped. Source code (`src/*.flash`, the Zig drivers, the AArch64 assembly) is authored by me. ## License Apache License, Version 2.0. See [License](LICENSE.md). ## See also - **[Flash](https://github.com/ajhahnde/Flash)** — a systems language and Zig transpiler. - **[eeco](https://github.com/ajhahnde/eeco)** — self-maintaining workflow ecosystem. - **[the-way-out](https://github.com/ajhahnde/the-way-out)** — top-down pixel-art escape-room shooter. - **[Theria](https://github.com/ajhahnde/Theria)** — 2.5D MOBA built in Godot 4. --- [Next: Documentation →](DOCUMENTATION.md)