How to create an initramfs after you compile a linux kernel

How to create an initramfs after you compile a linux kernel
Photo by Martin Wettstein / Unsplash

You build an initramfs by putting all the OS-required files in a folder, you convert everything in there to a single .cpio, and finally you gzip that. From your perspective, the initramfs is a gziped cpio file that contains the initial file system that the kernel needs to run. And that includes the /init script.

I strongly recommend you read these two files from the Linux kernel source code, even if you don’t fully understand them:

  • Documentation/early-user-space/README: Why do we need an early user space, what it is, and the different tools to compile it with the kernel.
  • Documentation/filesystems/ramfs-rootfs-initramfs.txt: Explanation of the ram filesystems you will be using while booting.

I wanted to create an initramfs with everything I could need for testing my kernel with qemu without needing to recompile the kernel. For this reason, I didn’t change any option while compiling the kernel.

When we first boot, we need at least some tools to start working. This includes the init process and some tools like ls, mount, mv, etc. To get those user-space tools you can use BusyBox. BusyBox has many useful commands available for just 1.1MB:

acpid, add-shell, addgroup, adduser, adjtimex, arch, arp, arping, ash, awk,
base64, basename, bc, beep, blkdiscard, blkid, blockdev, bootchartd, brctl,
bunzip2, bzcat, bzip2, cal, cat, chat, chattr, chgrp, chmod, chown, chpasswd,
chpst, chroot, chrt, chvt, cksum, clear, cmp, comm, conspy, cp, cpio, crond,
crontab, cryptpw, cttyhack, cut, date, dc, dd, deallocvt, delgroup, deluser,
depmod, devmem, df, dhcprelay, diff, dirname, dmesg, dnsd, dnsdomainname,
dos2unix, dpkg, dpkg-deb, du, dumpkmap, dumpleases, echo, ed, egrep, eject,
env, envdir, envuidgid, ether-wake, expand, expr, factor, fakeidentd,
fallocate, false, fatattr, fbset, fbsplash, fdflush, fdformat, fdisk,
fgconsole, fgrep, find, findfs, flock, fold, free, freeramdisk, fsck,
fsck.minix, fsfreeze, fstrim, fsync, ftpd, ftpget, ftpput, fuser, getopt,
getty, grep, groups, gunzip, gzip, halt, hd, hdparm, head, hexdump, hexedit,
hostid, hostname, httpd, hush, hwclock, i2cdetect, i2cdump, i2cget, i2cset,
i2ctransfer, id, ifconfig, ifdown, ifenslave, ifplugd, ifup, inetd, init,
insmod, install, ionice, iostat, ip, ipaddr, ipcalc, ipcrm, ipcs, iplink,
ipneigh, iproute, iprule, iptunnel, kbd_mode, kill, killall, killall5, klogd,
last, less, link, linux32, linux64, linuxrc, ln, loadfont, loadkmap, logger,
login, logname, logread, losetup, lpd, lpq, lpr, ls, lsattr, lsmod, lsof,
lspci, lsscsi, lsusb, lzcat, lzma, lzop, makedevs, makemime, man, md5sum,
mdev, mesg, microcom, mkdir, mkdosfs, mke2fs, mkfifo, mkfs.ext2, mkfs.minix,
mkfs.vfat, mknod, mkpasswd, mkswap, mktemp, modinfo, modprobe, more, mount,
mountpoint, mpstat, mt, mv, nameif, nanddump, nandwrite, nbd-client, nc,
netstat, nice, nl, nmeter, nohup, nologin, nproc, nsenter, nslookup, ntpd,
nuke, od, openvt, partprobe, passwd, paste, patch, pgrep, pidof, ping, ping6,
pipe_progress, pivot_root, pkill, pmap, popmaildir, poweroff, powertop,
printenv, printf, ps, pscan, pstree, pwd, pwdx, raidautorun, rdate, rdev,
readahead, readlink, readprofile, realpath, reboot, reformime, remove-shell,
renice, reset, resize, resume, rev, rm, rmdir, rmmod,route, rpm, rpm2cpio,
rtcwake, run-init, run-parts, runlevel, runsv, runsvdir, rx, script,
scriptreplay, sed, sendmail, seq, setarch, setconsole, setfattr, setfont,
setkeycodes, setlogcons, setpriv, setserial, setsid, setuidgid, sh, sha1sum,
sha256sum, sha3sum, sha512sum, showkey, shred, shuf, slattach, sleep, smemcap,
softlimit, sort, split, ssl_client, start-stop-daemon, stat, strings, stty,
su, sulogin, sum, sv, svc, svlogd, svok, swapoff, swapon, switch_root, sync,
sysctl, syslogd, tac, tail, tar, taskset, tc, tcpsvd, tee, telnet, telnetd,
test, tftp, tftpd, time, timeout, top, touch, tr, traceroute, traceroute6,
true, truncate, ts, tty, ttysize, tunctl, ubiattach, ubidetach, ubimkvol,
ubirename, ubirmvol, ubirsvol, ubiupdatevol, udhcpc, udhcpc6, udhcpd, udpsvd,
uevent, umount, uname, unexpand, uniq, unix2dos, unlink, unlzma,unshare,
unxz, unzip, uptime, users, usleep, uudecode, uuencode, vconfig, vi, vlock,
volname, w, wall, watch, watchdog, wc, wget, which, who, whoami, whois,
xargs, xxd, xz, xzcat, yes, zcat, zcip

I’m listing all those programs because for some reason in the past I thought that all those basic programs would be available for me after compiling the kernel via some sort of magic. But they are not. You need to compile/download them first separately.

Another thing I realized is that you need to build your own initramfs. I mean, you need to choose which software you want when your kernel starts. I used this script to create my initramfs:

#!/bin/bash

ARCH="x86_64"
BB_VER="1.31.0"

# Dirs
mkdir -p root
cd root
mkdir -p bin dev etc lib mnt proc sbin sys tmp var
cd -

# Utils
if [ ! -f "root/bin/busybox" ]; then
    curl -L "https://www.busybox.net/downloads/binaries/${BB_VER}-defconfig-multiarch-musl/busybox-${ARCH}" >root/bin/busybox
fi
cd root/bin
chmod +x busybox
ln -s busybox mount
ln -s busybox sh
cd -

# Init process

cat >>root/init << EOF
#!/bin/busybox sh
/bin/busybox --install -s /bin
mount -t devtmpfs  devtmpfs  /dev
mount -t proc      proc      /proc
mount -t sysfs     sysfs     /sys
mount -t tmpfs     tmpfs     /tmp
setsid cttyhack sh
exec /bin/sh
EOF
chmod +x root/init

# initramfs creation

cd root
find . | cpio -ov --format=newc | gzip -9 >../initramfs
cd -

This script will output an initramfs containing an /init bash script, some directories, and the BusyBox binary. The basic directories are created bin, dev, etc, lib, mnt, proc, sbin, sys, tmp, var.

When BusyBox is downloaded I created also two symbolic links for mount and sh, all the commands that I will be using in the init script. The init script installs all the others required symbolic links for all the commands I listed before while talking about BusyBox. Also, it creates the basic file systems so we can start playing around.

In my case, I used a simple bash script, but you may want to use user/gen_init_cpio (is in the linux kernel source tree) for that. It’s an already-made script for creating  initramfs from a file that describes the file system structure. It can be called like this:

$ usr/gen_init_cpio descriptor | gzip >initramfs

And the descriptor may look like this:

file /init my-init.sh 07555 0 0
dir /bin 0755 0 0
nod /dev/zero 0666 0 0 c 1 5
file /bin/busybox /bin/busybox 0755 0 0

To learn more about the syntax just call the command without any arguments: usr/gen_init_cpio.