Boot Naked Linux · ... and another thing ... Open Original Page Boot Naked Linux · ... and another thing ...
... and another thing ...
About me: Nick Moore
Interesting Links
Everything (by date)
Everything (by tag)
Forest for the trees
Boot Naked Linux
Unknown Unknowns in MAVE Data
Yeast Thoughts
Quantized Counting Considerations
Optimistic Event Driven Simulation / Mixed Mode Simulation
The Children Yearn for the Mines
PyConAU 2025 Melbourne - The Primordial Code
Floating Ducks
Look Mum No Pixels: a Mini Vector Display
All content Copyright © 2009-2025 Nick Moore unless otherwise noted 2026-05-19
c
/
linux
Starting up a Linux kernel to host one single process rather than a full on operating system ... and doing it in less than a second! When I was a kid, computers weren't coddled and
left running 24/7, when you were done with them you switched them off, and
when you wanted them again you just switched them on and within a second or
so they'd be loading whatever was in their disk drive. There was a brief moment in the early 2000s where the newly introduced
SSDs made booting quick but as always the tech industry has taken up the
slack until even a 16 core monster with a fast SSD still takes a minute
to get its feet under it. So I wanted to try an alternative.
Keep the Linux kernel but strip away everything else I could.
Here goes ... well not quite here goes nothing, but here goes a lot less. UPDATE: As per long tradition, while trying to fix a couple of details
I found "Building a tiny Linux from scratch"
which does most of what I do here but in Rust and a year ago,
so that's worth a look too. The first thing a Linux system does is run an "init" program of one sort
or another which loads all the other processes and configurations and stuff.
There's nothing too special about this program, it's just a regular
executable or script, and there have been a few different approaches taken
over the years in any case. So we can write a new one in C ... here's init.c: #include <stdio.h>
#include <stdlib.h>
#include <sys/reboot.h> int main(int argc, char **argv) {
fprintf(stderr, "Hello from init.c!");
reboot(RB_POWER_OFF);
} All it does is print a message, and then reboot the computer. If our init process exits the kernel panics, so instead of busy waiting1
or sleeping forever or whatever, we use reboot(RB_POWER_OFF) to shut the
virtual machine down in an orderly manner. Modern Linux supports
quite a complex multi-stage process.
There's a lot of
resources
out there, and a lot of them are 20 years out of date, and
there have been changes
but here's a summary of how I think it works right now,
as of 2026 and Linux 6.8 or thereabouts: A boot loader runs with a kernel and a dummy filesystem called 'initrd'. The kernel attempt to unpack the 'initrd' file into an 'initramfs', a
root filesystem in RAM which allows initialization tools to run.
it looks for a file called /init (or whatever is specified in the
rdinit= kernel parameter) if it exists, it runs it and that process takes over initialization. Otherwise, it falls back to:
mount the root partition specified by the root= kernel parameter mount the devtmpfs filesystem at /dev run /init (or whatever is specified in the init= kernel
parameter) from that. Otherwise, or if the init process exits, it kernel panics. Most modern distros use the first branch: quite a large initrd filesystem is
provided so that modules and firmware can get loaded before attempting to
boot the real filesystem.
The initrd file which my PC boots from is 73MB and according to lsintramfs
it contains 2163 files! There's a few examples of how to
construct a filesystem with a
replacement for init
but I wanted to go even simpler and replace the entire initrd. If we compile our example code statically, eg: containing all the libraries it
needs, we can make our own initrd with only one file in it: gcc -static init.c -o init echo 'init' | cpio -o --format=newc | gzip -c > initrd cpio is a very weird and ancient program with a command line
which makes tar look user-friendly.
But let's not worry about the details for now. I will note that, later, if you get a kernel message: Initramfs unpacking failed: no cpio magic ... it means that either the cpio format or the compression or something
similar isn't compatible with your kernel. The kernel will attempt to
continue, but a later error message like: check access for rdinit=/init failed: -2, ignoring ... means that either the initramfs didn't happen or your binary is
in the wrong place (-2 is -ENOENT which means file not found).
You might also get a message about incompatible architectures.
This message happens pretty early in the boot process, which attempts to
continue anyway, so you'll have to look back carefully.
Whereas if you see: Trying to unpack rootfs image as initramfs... ... and then nothing else, that's a good sign. It never logs that it was
successful until much later when it should hopefully say: Run /init as init process Incidentally, if you're looking to pack many files into a cpio archive,
you want something along the lines of: (cd $SOURCE_DIR; find . | cpio -o -H newc) | gzip -c > $OUTPUT_FILE The whole thing about piping in a list of files probably seems
perverse but cpio is older than tar, older than shell filename
globbing so perhaps we can forgive it Getting this going on real hardware would involve an irritating
amount of USB key swapping,
so I'm using QEMU to make a virtual system
to experiment with. QEMU lets you boot from just
a kernel and a filesystem image
on the command line. For now, rather than proper QEMU I'm using KVM to run a virtualized
system. For full emulation, you can also run these examples with
qemu-system-x86_64, or whatever the appropriate emulator is for
your system. It's slightly slower but otherwise works the same. First we need a kernel, I'm just using the current kernel
from my machine but the /boot/vmlinuz file is readable by root only so first
we make a copy of it in our working directory and change its ownership: sudo cp /boot/vmlinuz .
sudo chown $USER:$GROUP vmlinuz If you can be bothered building your own
cut-down kernel
that'd be even better. We now have our two binary files, the kernel vmlinuz and
the init image initrd which contains only our program
init.
So we can boot our system with: kvm -m 1G -nographic -kernel vmlinuz \
-initrd initrd -append "console=ttyS0" The -nographic and -append "console=ttyS0" options give us a terminal console
to monitor stderr on rather than popping up a graphical console. When the kernel starts up, it unpacks our initrd into a ram disk, and
runs our init binary: [ 0.000000] Linux version 6.8.0-111-generic (buildd@lcy02-amd64-088)
[ 0.000000] Command line: console=ttyS0
[ 0.489390] Trying to unpack rootfs image as initramfs...
[ 0.805419] Run /init as init process
Hello from init.c!
[ 0.807535] reboot: Power down Even if we don't want any filesystems, we might want some permanent
storage. But at the time our /init runs, we haven't mounted a root filesystem yet,
so there's no devices available!
Devices are made available using a kernel mechanism called "devtmpfs" so the
first thing we have to do is activate that. We can mount
devtmpfs from our C program using mount("devtmpfs", "/dev", "devtmpfs", 0, NULL). QEMU can present a host file as a block device on the guest using
the -hda option, the file will appear to the guest as /dev/sda, so let's
modify our code from before to mount the devtmpfs, open /dev/sda
and read the first few bytes from that file: #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/reboot.h>
#include <arpa/inet.h> int main(int argc, char **argv) {
fprintf(stderr, "Hello from init.c!\n");
mount("devtmpfs", "/dev", "devtmpfs", 0, NULL); int fd = open("/dev/sda", O_RDWR);
uint32_t buffer[2];
read(fd, buffer, sizeof(buffer));
close(fd); fprintf(stderr, "Read %08x %08x\n",
ntohl(buffer[0]), ntohl(buffer[1])); reboot(RB_POWER_OFF);
} Links | Open - ... and another thing ... | | Open - About me: Nick Moore | | Open - Interesting Links | | Open - Everything (by date) | | Open - Everything (by tag) | | Open - Forest for the trees | | Open - Boot Naked Linux | | Open - Unknown Unknowns in MAVE Data | | Open - Yeast Thoughts | | Open - Quantized Counting Considerations |
Browse another page: |