a barebones initramfs

previously, a minimal linux kernel was compiled and booted with qemu, only to kernel panic. it wants an init! we can give it one with an initramfs.

the initramfs and rootfs

as implied by the name, an initramfs is a filesystem (containing an init) loaded by linux into memory. its intended purpose is to mount the real root filesystem, or rootfs, on disk. for example, this could involve loading the appropriate filesystem drivers, or the kernel modules necessary for encrypted lvm. when the rootfs's / is mounted, the real init is executed as pid 1 and life begins in userspace beyond the initramfs stage ("early" userspace).

the rootfs is typically installed on disk by means of a bootable live image, which provides utilities to customize the rootfs (e.g. set the timezone), partition disks, install a second stage bootloader1, and more.

some linux distributions get quite creative, and operate entirely in memory if their rootfs is small enough, at which point the line between initramfs and rootfs is blurred. as for optional persistence, there might be some special files on the removable media which also supplies the kernel + initramfs. also, some live cds and rescue linuxes have the option to load the entire rootfs into memory for additional snappiness.

anyways, to keep things simple for now, let's just remain in memory, and drop the user to a shell. we aren't yet concerned with building a rootfs right now, much less a full installation environment in a live cd.

initramfs init

at first, i considered statically compiling a small shell like dash and using that as the initramfs init, but then it occured to me that later down the line a proper initramfs would need utilities like mount and modprobe. the popular, easy solution is busybox (there is also klibc, which i may consider in the future).

a minimal /init that meets our requirements:

    #!/bin/sh

    printf %s\\n "hello!"
    exec /bin/sh

busybox can be statically compiled to support this init and nothing more with make allnoconfig and then enabling:

  • static build
  • ash with printf builtin

also, enabling coreutils ls is not necessary for an initramfs, but it would be nice to have that working once we shell in. semantically, an initramfs isn't interactive, and eventually we'll be moving such facilities to a rootfs.

again, alpine linux in docker is a nice compilation environment. make gcc musl-dev linux-headers are the necessary packages, and ncurses-dev for menuconfig.

building the initramfs

after making busybox, a make install will install it to _install/. if you copy _install/{bin,sbin,usr} to a fresh directory then add the init script, it is again quite painless in alpine (just install the xz package) to build the initramfs:

    chmod u+x init          && \
    busybox find             | \
    busybox cpio -ov -H newc | \
    xz --check=crc32 > ../initramfs.cpio.xz

now boot the kernel with it:

    qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.xz

1 as with all technology, there are caveats, and one worth mentioning here is if the kernel is compiled with efistub support, then a second stage bootloader is not necessary on uefi firmwares. well, some uefi firmwares - the implementation on my xps 13 9343 apparently cannot pass kernel command-lines, which is a total deal breaker, and so i have to load refind as second stage. but i digress.