D   A   T   A   W   O   K

Creation: June 06 2020
Modified: February 05 2022

Linux Kernel Build and Busybox System Setup

Building the kernel

For ncurses-based graphical utility

$ make menuconfig

To create a config file absed on the defaults for the host architecture

$ make defconfig

The configuration is stored in the project root in a file named .config.

Config file validation (always run it) before building.

$ make oldconfig

Once the configuration is set the build can be started

$ make -j8 > /dev/null

GCC Version issues

Some old kernel versions can not be easily built using the latest GCC versions.

A simple solution to build with a given GCC version without installing installing it in the host system can be to use docker.

This is the command line I've used to build the kernel 2.6 using gcc 4:

$ docker run -v `pwd`:/build gcc:4 /bin/bash -c 'cd build && make'

Running the kernel

First run

To run the kernel on QEMU

$ qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage

After a while you'll get the following kernel panic message

[    1.568246] VFS: Unable to mount root fs via NFS, trying floppy.
[    1.571268] VFS: Cannot open root device "(null)" or unknown-block(2,0)
[    1.571730] Please append a correct "root=" boot option; here are the available partitions:
[    1.572751] 0b00         1048575 sr0 driver: sr
[    1.573342] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(2,0)
[    1.574196] Pid: 1, comm: swapper Not tainted 2.6.34 #3

In short it means that it cannot find a valid root filesystem.


For fast experiments it is convenient to have a filesystem that we are able to modify quickly. We can put all the stuff we need in a initramfs image.

Initramfs content can be easily populated using the gen_init_cpio utility built within the kernel.

The utility parses a script enumerating the files and directories we want to copy from the host to the initrd filesystem.

For example (file named initrd_script):

dir /bin 0755 0 0
nod /dev/zero 0666 0 0 c 1 5

To generate the initrd image

$ usr/gen_init_cpio initrd_script | gzip > initramfs.img

Let's try to boot by specifying the initramfs as our initrd image to qemu:

$ qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage -initrd initramfs.img

Unfortunately we get another kernel panic, this time due to the missing foundamental executale: init.


Init is the first program run by the kernel. Its main requirement is that it shall never exit.

Let's build a dummy init that just prints hello world forever.

    void main(void)
        while (1) {
            printf("Hello world\n");

We need to build the application as static and with a compiler that is compatible with the one used to build the kernel (we use docker again).

$ docker run -v `pwd`:/build gcc:4 \
  /bin/bash -c 'cd build && gcc -static myinit.c -o myinit'

Modify the initrd_script by adding the init file:

file /init myinit 0755 0 0
dir /bin 0755 0 0
nod /dev/zero 0666 0 0 c 1 5

Rebuild the initrd filesystem and run the kernel.

Dummy Linux

Dummy Linux is the most trivial usable system every.

Its main purpose is to give to the user the opportunity do easily debug some kernel functionality by triggering a particular syscall from the user code.

It is mainly composed of an init program that just spawns a very simple interactive shell

The shell is capable to start other binary files that you can add to the system. The only requirement is that your programs shall be built as static binaries.

The updated initrd script can be downloaded here


From the BusyBox sources root:

$ mkdir build
$ BUILD=`pwd`/build

Create a minimal default config

$ make O=$BUILD defconfig

Edit the config for fine tuning

$ make O=$BUILD menuconfig

From the menu config, enable static linking

Busybox Settings --> Build Options -> Build static binary [X]

Build Busybox

$ cd $BUILD
$ make -j2
$ make install

Build the directory structure of the initramfs

$ mkdir $BUILD/initramfs
$ cd $BUILD/initramfs
$ mkdir -pv {bin,dev,sbin,etc,proc,sys/kernel/debug,usr/{bin,sbin},lib,lib64,mnt/root,root}
$ cp -av $BUILD/_install/* $BUILD/initramfs
$ sudo cp -av /dev/{null,console,tty,sda1} $BUILD/initramfs/dev/

Create a "mini" init as a shell script

mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
echo -e "\nB U S Y  L I N U X\n"
while true; do

And make it executable

$ chmod +x $BUILD/init

Create the initramfs

$ cd $BUILD/initramfs
$ find . | cpio -H newc -o > ../initramfs.cpio
$ cd ..
$ cat initramfs.cpio | gzip > initramfs.img

Then modify the qemu initrd option and start as usual.

If you are building using docker remember to map the volume containing the BusyBox sources and not just the build folder. For example:

$ docker run -v '/home/davxy/busybox':'/home/davxy/busybox' gcc:4 \
  /bin/bash -c 'cd /home/davxy/busybox/build && make -j2'

Proudly self-hosted on a cheap Raspberry Pi