I wanted to see how small a bootable Linux system can be. Not Alpine-minimal. Actually minimal. Result: ~900KB kernel, a shell in x86-64 assembly using raw syscalls, under 9MB RAM, boots in under a second.
make tinyconfig gets you a kernel smaller than allnoconfig.
No networking, no loadable modules, no SMP. The bzImage came out around 900KB.
A distro kernel is 10-15MB compressed.
I added serial console, initramfs, and CONFIG_PRINTK on top of tinyconfig.
Debugging without printk is miserable. Each option I turned on, the binary grew.
Good way to see what each config option costs in bytes.
BusyBox is the standard answer for minimal userspace. I skipped it initially and wrote a shell directly in x86-64 assembly. No glibc, no musl, no libc. Direct syscalls.
; write(1, msg, len) mov rax, 1 mov rdi, 1 mov rsi, msg mov rdx, msg_len syscall ; read(0, buf, 256) xor eax, eax xor edi, edi mov rsi, buf mov edx, 256 syscall
It handles echo, ls (via getdents64),
cat (via open/read/write),
and exec (via execve). Not POSIX. Enough to poke around.
The annoying parts: getdents64 returns variable-length records, so you
walk the buffer manually. execve needs a null-terminated argv array.
Small things libc does for you that you don't think about until they're gone.
After the assembly shell worked, I added BusyBox (static, musl) and Lua. BusyBox is ~1MB for a real shell plus coreutils. Lua builds with no dependencies.
Full system — kernel, initramfs, BusyBox, Lua, assembly shell — under 9MB RAM.
Boots in under a second on QEMU. No systemd, no init system. Kernel loads initramfs,
runs /init, you get a prompt.
The kernel itself is small and fast. The weight in a typical Linux system comes from everything above it. Useful to see that directly rather than just assume it.