This repository is an experimental microkernel project. Kernel code should be the smallest possible layer that provides the necessary primitives for building higher-level services in userspace.
The goal is to have a single codebase that can be built and run on multiple architectures, with a clean separation between reusable kernel logic and platform-specific behavior. The supported targets are x86_64, aarch64, riscv64, and loongarch64.
This project uses Meson to:
- cross-compile the kernel for
x86_64,aarch64,riscv64, andloongarch64 - build a bootable Limine ISO image
- build and run native Criterion tests
The tree is split into layers so code can move toward the least specific place that can own it. The boundaries are meant to describe ownership, not a perfect dependency graph.
include/: public headers for the layer contracts.include/base/,include/core/,include/hal/, andinclude/kernel/mirror the implementation layers.base/: standalone freestanding support code. It currently presents a libc-like API and deliberately leaves some low-level operations to the environment that links it, so the kernel, future userspace, and hosted mocks can provide different implementations. It should not know about boot protocols, CPUs, interrupts, address spaces, or platform devices.core/: reusable, platform-neutral kernel behavior. Code belongs here when it can be expressed in terms of core data structures and HAL contracts rather than a specific boot path, runtime policy, or architecture.kernel/: live-kernel composition. Code belongs here when it turns reusable services into one booted kernel image: boot protocol adaptation, initialization order, kernel-wide policy, and integration between services that should otherwise stay independent.platforms/<platform>/: concrete machine and architecture support. One platform backend is selected per build, and this is where entry code, context-switch details, and HAL implementations live.boot/: Limine configuration template used when producing the bootable ISO.toolchain/: Meson cross files for the supported kernel targets.test/: native Criterion tests plus hosted mocks for dependencies that are supplied by the platform or kernel when the real image is linked.
The top-level Meson file builds the image by collecting base_sources, core_sources, the selected platform_kernel_objects, and kernel/ integration sources into kernel.elf, then packages that ELF into kernel.iso.
The HAL is the main contract between reusable kernel code and platform code. Public HAL interfaces live in include/hal/; core targets those interfaces, while the selected platform supplies the concrete behavior. Supported Meson platforms are pc_x86_64, pc_aarch64, pc_riscv64, and pc_loongarch64.
Physical memory starts with the bootloader memory map. pmm_init() records the direct-map offset, reserves allocator metadata from usable memory, and manages contiguous 4 KiB page runs.
Virtual memory is split into two related layers:
address_spaceininclude/core/vaddr_alloc.htracks ownership of virtual page ranges with a bitmap and stores the HAL paging handle for that space.vmmininclude/core/vmm.htracks allocation records by ID, owns backing pages for mapped allocations, applies protection policy, supports lazy mappings, resolves eligible page faults, and releases metadata/backing during teardown.
The kernel has a global managed virtual window at MM_KERNEL_VMM_BASE with size MM_KERNEL_VMM_SIZE. User processes receive separate address spaces over MM_USER_VMM_BASE and MM_USER_VMM_SIZE, with a null guard at the bottom. New hardware user address spaces inherit the kernel mappings required to enter and leave kernel mode.
The address-transfer helpers validate user ranges, fault in lazy pages when requested, and copy data between kernel and user address spaces without syscalls reaching directly into untrusted pointers.
The scheduler is per-CPU. Each CPU owns a run queue and a permanent idle thread. Regular threads carry a kernel stack, an address-space pointer, priority state, wait queue links, join state, cancellation flags, and an owner kind.
Higher-level thread wrappers add ownership:
kthreadallocates a kernel stack from the kernel VMM window and runs a kernel entry point.uthreadallocates both a user stack in its process address space and a kernel stack in the kernel address space, then uses the HAL userspace contract to build an initial user context.processowns a user address space and links its user threads for lifecycle operations.
The scheduler switches address spaces when the next thread requires a different one, falling back to the kernel address space for idle/kernel-only execution.
Hosted Criterion tests are native executables. They link base or core sources with mocks instead of the real platform backend:
- generic HAL mocks live in
test/mocks/hal/ - generic base mocks live in
test/mocks/base/ - subsystem-specific mocks stay next to the tests that need them, such as
test/vmm/mock_paging.c
This is why core can depend on HAL contracts and still remain testable outside the live kernel. In-kernel selftests under kernel/test/ cover behavior that must run after boot-time memory and scheduler initialization.
On success, the value is the syscall result. On error, the value is a syscall/status-specific diagnostic payload, such as the unsupported syscall number or the index of a problematic argument.
Status values are grouped by range:
0xx: success.SYSCALL_STATUS_OKis0.1xx: caller-side errors. The syscall request was malformed or unsupported, so retrying it unchanged should not work.2xx: kernel-side errors. The request was understood, but the kernel could not complete it because required state or resources were unavailable.
Current codes:
SYSCALL_STATUS_OK(0): the syscall completed successfully.SYSCALL_STATUS_UNKNOWN_SYSCALL(100): the syscall number is not implemented.SYSCALL_STATUS_BAD_ARGUMENT(101): an argument is invalid or cannot be represented by the kernel.SYSCALL_STATUS_FAILED(200): the request is valid, but the kernel operation failed while handling it.SYSCALL_STATUS_UNAVAILABLE(201): the syscall depends on kernel state that is not available yet.
Use syscall_status_is_success(), syscall_status_is_caller_error(), and syscall_status_is_kernel_error() when callers only care about the result class.
Make sure these tools are available on your PATH:
mesonninjaclang,lld,llvm-ar,llvm-stripqemu-system-x86_64qemu-system-aarch64qemu-system-riscv64qemu-system-loongarch64pkg-configgitbashmakexorriso
If you want to build and run the native test targets, you also need Criterion (criterion).
On Windows, the ISO generation step depends on Unix-style tools (bash, make, xorriso). In practice, this project is easiest to build from an MSYS2 or similar shell environment with those tools installed.
For the non-x86_64 targets, QEMU also needs UEFI firmware. The bundled filenames vary by package, so the recommended launch path is scripts/run.sh or scripts/run_qemu.sh, which auto-detect the firmware near the installed QEMU binary or use QEMU_FIRMWARE_DIR if you set it explicitly.
The recommended entry point is the build helper:
bash scripts/build.sh --arch x86_64 --setup
bash scripts/build.sh --all --setup
bash scripts/build.sh --all --setup --build-prefix build-debug
bash scripts/build.sh --arch x86_64 --setup --no-tests
bash scripts/build.sh --arch x86_64 --setup --kernel-selftests
bash scripts/build.sh --arch x86_64 --setup --kernel-selftests --kernel-selftests-autorun
bash scripts/build.sh --arch x86_64 --setup --kernel-selftests-suite vmmWhen no architecture is provided, the helper exits with guidance to use either --arch <arch> or --all.
Use --no-tests if you want a kernel-only configure without native test dependencies installed.
Use --kernel-selftests to compile in-kernel selftest suites into the kernel.
Use --kernel-selftests-autorun to also inject kernel.selftest=1 into the generated image so that booting the ISO runs the in-kernel tests automatically.
Use --kernel-selftests-suite <name> to inject kernel.selftest.suite=<name> into the generated image and boot only that suite. This also enables selftest autorun for the generated image.
Use --builddir <path> for a custom single-architecture build directory.
Use --build-root <path> and/or --build-prefix <name> for per-architecture directories; for example, --build-prefix build-debug produces build-debug-x86_64, build-debug-aarch64, build-debug-riscv64, and build-debug-loongarch64.
If you prefer calling Meson directly, configure the architecture you want to build from the repository root:
meson setup build-x86_64 --cross-file toolchain/x86_64-elf.ini -Dplatform=pc_x86_64
meson setup build-aarch64 --cross-file toolchain/aarch64-elf.ini -Dplatform=pc_aarch64
meson setup build-riscv64 --cross-file toolchain/riscv64-elf.ini -Dplatform=pc_riscv64
meson setup build-loongarch64 --cross-file toolchain/loongarch64-elf.ini -Dplatform=pc_loongarch64If the build directory already exists and you want to reconfigure it:
meson setup build-x86_64 --reconfigure --cross-file toolchain/x86_64-elf.ini -Dplatform=pc_x86_64
meson setup build-aarch64 --reconfigure --cross-file toolchain/aarch64-elf.ini -Dplatform=pc_aarch64
meson setup build-riscv64 --reconfigure --cross-file toolchain/riscv64-elf.ini -Dplatform=pc_riscv64
meson setup build-loongarch64 --reconfigure --cross-file toolchain/loongarch64-elf.ini -Dplatform=pc_loongarch64To configure and compile in one command:
bash scripts/build.sh --arch x86_64
bash scripts/build.sh --arch riscv64 -sc
bash scripts/build.sh --arch x86_64 --builddir build-debug-x86_64
bash scripts/build.sh --arch x86_64 --kernel-selftests --kernel-selftests-autorun
bash scripts/build.sh --arch x86_64 --kernel-selftests-suite vmm
bash scripts/build.sh --all
bash scripts/build.sh --all -scWhen you use --all, the helper runs each architecture in parallel. Setup still happens before compile within each individual target.
To compile only already-configured build directories:
bash scripts/build.sh --arch x86_64 --compile
bash scripts/build.sh --all --compileIf you prefer calling Meson directly, build everything for the configured target:
meson compile -C build-x86_64
meson compile -C build-aarch64
meson compile -C build-riscv64
meson compile -C build-loongarch64This produces:
build-x86_64/kernel/kernel.elfbuild-x86_64/kernel.isobuild-aarch64/kernel/kernel.elfbuild-aarch64/kernel.isobuild-riscv64/kernel/kernel.elfbuild-riscv64/kernel.isobuild-loongarch64/kernel/kernel.elfbuild-loongarch64/kernel.iso
The broader helper is scripts/run.sh. In normal mode it launches QEMU and requires either --arch <arch> or --all. In test mode (--test or -t) it detects the current machine architecture with uname -m and runs the matching native tests.
bash scripts/run.sh --arch x86_64
bash scripts/run.sh --arch aarch64
bash scripts/run.sh --arch riscv64
bash scripts/run.sh --arch loongarch64
bash scripts/run.sh --test
bash scripts/run.sh -t
bash scripts/run.sh --test --builddir build-debug-x86_64
bash scripts/run.sh --kernel-selftest --arch x86_64If you want to launch QEMU for every supported target, the helper does that sequentially in one command:
bash scripts/run.sh --allIf you use a non-standard build directory, override it explicitly for a single-architecture run:
bash scripts/run.sh --arch x86_64 --builddir out/kernel-x86_64
bash scripts/run.sh --kernel-selftest --arch x86_64 --builddir build-debug-x86_64If you use matching per-architecture build directories, select them with the same root/prefix options used by scripts/build.sh:
bash scripts/run.sh --all --build-prefix build-debug
bash scripts/run.sh --arch riscv64 --build-root /tmp/kernel-builds --build-prefix build-debugIf you want to launch QEMU in debug mode, use --debug and optionally --debug-port:
bash scripts/run.sh --arch x86_64 --debug --debug-port 4321If you want a headless non-interactive selftest run that exits with success or failure, use:
bash scripts/build.sh --arch x86_64 --kernel-selftests --kernel-selftests-autorun --no-tests
bash scripts/run.sh --kernel-selftest --arch x86_64 --timeout 45If you only want a specific in-kernel selftest suite:
bash scripts/build.sh --arch x86_64 --kernel-selftests-suite vmm --no-tests
bash scripts/run.sh --kernel-selftest --arch x86_64 --timeout 45If you only want a specific hosted Criterion test suite:
bash scripts/run.sh -t --test-name pmmYou can still use the narrower QEMU-only helper directly if you want:
bash scripts/run_qemu.sh --arch x86_64
bash scripts/run_qemu.sh --arch aarch64
bash scripts/run_qemu.sh --arch riscv64
bash scripts/run_qemu.sh --arch loongarch64
bash scripts/run_qemu.sh --arch x86_64 --builddir build-debug-x86_64The hosted Criterion tests under test/ remain the right place for fast native unit coverage. For checks that must run inside the live kernel after boot-time memory initialization, there is now a small in-kernel selftest runner.
Build-time control uses three options:
--kernel-selftestscompiles the in-kernel selftest suites into the kernel binary--kernel-selftests-autorunwriteskernel.selftest=1into the generated image command line so they run automatically on boot--kernel-selftests-suite <name>writeskernel.selftest.suite=<name>into the generated image command line and limits autorun to that suite
The kernel runs selftests from a selftest-only runner after pmm, vmm, heap, and scheduler initialization, prints per-test results to serial, and emits a final selftest: result: PASS or FAIL marker for automation.
When a suite filter is present, only the matching registered suite is executed.
To add more in-kernel tests:
- define one or more
struct kernel_selftest_caseentries in a new source file underkernel/test/selftests/ - export a
const struct kernel_selftest_suite - register that suite in
kernel/test/selftest.c
Use the assertion macros in kernel/test/selftest.h. For tests that allocate or lock resources, prefer the _GOTO variants so cleanup still runs on failure.
- The default platform is
pc_x86_64. - The available Meson platforms are
pc_x86_64,pc_aarch64,pc_riscv64, andpc_loongarch64. - These platform names describe the kernel target rather than the emulator; the same binaries are intended to be usable on matching hardware, while
scripts/run.shandscripts/run_qemu.shremain QEMU launch helpers. - The test binaries are built natively, while the kernel is cross-compiled with the selected file in
toolchain/. -Dtests=falseskips configuring the native Criterion test targets.scripts/build.sh --no-testsis the helper equivalent.-Dkernel_selftests=truecompiles in-kernel selftest code into the kernel.scripts/build.sh --kernel-selftestsis the helper equivalent.-Dkernel_selftests_autorun=trueinjectskernel.selftest=1into the generated image.scripts/build.sh --kernel-selftests-autorunis the helper equivalent.-Dkernel_selftests_suite=<name>injectskernel.selftest.suite=<name>into the generated image and also causes selftests to autorun for that image.scripts/build.sh --kernel-selftests-suite <name>is the helper equivalent.-Dkernel_boot_debug=trueinjectsloglevel=debuginto the generated image and enables verbose boot diagnostics.scripts/build.sh --kernel-boot-debugis the helper equivalent.scripts/build.shsupports--arch <arch>for one target and--allto configure and/or compile every supported target in one parallel run.scripts/build.sh,scripts/run.sh, andscripts/run_qemu.shsupport--builddir <path>for single-target custom build directories, plus--build-root <path>and--build-prefix <name>for matching per-architecture directory sets.scripts/run.shhas three modes: test mode with--test/-t, kernel selftest mode with--kernel-selftest, and QEMU mode otherwise.- The Limine helper script clones Limine into the build directory the first time the ISO target is built.
- The
virtmachines for non-x86_64targets need explicit edk2 firmware.scripts/run.shandscripts/run_qemu.shlocate the matching firmware image automatically, or you can point one of them at it withQEMU_FIRMWARE_DIR. - The non-
x86_64targets are UEFI-only in this repository; BIOS ISO deployment remainsx86_64-only. - A repo-managed pre-commit hook template lives in
.githooks/pre-commit. Install it into your local.git/hookswithbash scripts/install-hooks.sh. It formats staged*.cand*.hfiles before commit and aborts if one of them is only partially staged.