What it looks like inside
From power-on to the prompt – and who talks to whom.
Startup
nb – the bootloader
A small UEFI application (one C file, no libc). It queries the screen (GOP), reads
\nullos\nk.elf from the boot partition, puts the parts of the kernel at their addresses, fetches the
memory map, says goodbye to UEFI and jumps into the kernel. The kernel gets everything important in
one structure: memory, screen, ACPI.
nk – the kernel
Portable C, everything machine-specific in a layer of its own – for x86_64, ARM64 and RISC-V 64. It runs in the
upper half of the address space, programs get the whole lower half. What it can do:
- Processes:
spawn, fork, exec, wait, signals (Ctrl+C too), pipes.
- Network: its own TCP/IP stack, IPv6 and IPv4 on equal footing, over virtio-net and e1000. Plus Unix sockets.
- Multiple CPUs: all cores (up to 16) run, programs at the same time; inside the kernel there's only ever one CPU for now (one big lock).
- Scheduler: threads round-robin, 10 ms slices, one queue per CPU, 100 Hz timer.
- Memory: separate page tables per process, write and execute protection, SMEP/SMAP, stack grows on demand.
- Randomness: ChaCha20 generator, fed from RDSEED/RDRAND, virtio-rng and interrupt timings.
- Programs: static ELF64, also position-independent (PIE).
- Rights: users with
rwx, and every program may only do what its ID card says – more in Security.
- Disks: SATA (AHCI), NVMe, USB sticks and virtio-blk, all as
/dev/sdX; USB keyboard and mouse via xHCI. More: Drivers.
- Files: a virtual file system with mounting;
/ is a RAM file system from init.img, /dev shows devices like on Linux (sda, sda1 …), FAT can be mounted from a disk or image file (read-only).
- Console: screen with its own line font plus serial port, line editing, the important ANSI sequences.
- Crashes readable: panic with names, registers and call chain.
Writing to disks, finer locks in the kernel: coming soon Which hardware works: Drivers.
The layers
System calls
Small on purpose: 85 calls – 16 for sockets, plus mounting, devices, network settings, users and ID card, setting the clock, process and disk statistics, file locks and pseudo-terminals. All are implemented and tested, fixed in a contract with a header to go with it. On x86_64 via syscall, number in rax, arguments
like on Linux. Errors come back as a negative number. Numbers are never redefined, only added.
Programs are static ELF64 files, with no libraries at runtime. Later a
fast ring for many requests at once will be added.
Tools are written against a small ABI
So that hundreds of tools could be built at the same time while the kernel isn't finished yet,
they're all written only against a short list of C functions: strings, memory, files,
folders, time, processes. The same source runs on the Mac and unchanged on NULLos.
libc – its own C library
Written from scratch (musl and PDCLib only as reading material). A portable part – strings, malloc,
stdio, printf, time, math – and below it a thin layer of about 70 functions to the kernel. There are two of those:
one for NULLos and one for macOS that gets by entirely without Apple's libc (system calls directly).
- printf and strtod compute exactly and output byte for byte the same as the macOS libc – tested with
5000 random numbers.
- Math in the style of fdlibm: at most 1–3 units off in the last place, measured.
- Error messages in German.
- The proof of the pudding: 226 tools pass their tests with this libc on the Mac – without a single function from the macOS libc.
- Still missing: locales other than “C”, wide characters, threads.
cc – its own C compiler
A C11 compiler in C11, complete with its own assembler and its own ELF linker. The big moments:
- It compiles itself. cc → cc2 → cc3, and cc2 and cc3 produce exactly the same thing.
- It compiles all the tools, and their tests run green.
- It builds all of NULLos – kernel, libc and 280 tools –, and that boots in QEMU, all tests green.
- It runs on NULLos itself and compiles itself there once more: cc2 is created on NULLos and compiles
correctly.
- The ELF objects from its assembler match clang's byte for byte.
Code generation exists for x86_64, ARM64 and RISC-V 64 (the last two tested in QEMU). RISC-V 32 for the small ESP32s and Xtensa are to follow. coming soon