Setup for linux kernel dev using qemu

I could not think of a better title, I’m sorry.

These are notes how to create a kernel which can be booted in qemu and pass a drive to it which has loadable kernel modules.

The idea

I wanted to play around with the linux kernel and write a minimal filesystem inside the kernel tree. I wanted to boot the kernel in a QEMU, as I do not want to mess with my running kernel.

Here are the steps I did (on a debian VM I run on my universitys infrastructure).

Setup

sudo apt-get install build-essential bc git gcc
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
make x86_64_defconfig
make kvmconfig
make menuconfig

The last step brings up the kernel configuration interface, which I used to enable SATA (Device Drivers -> Serial ATA and Parallel ATA drivers (libata) -> Generic ATA support) support which is needed to be able to mount a device into the qemu instance, where we can put things to play around with in the VM.

I then did a make -j 16 (if you have not 8 cores, use a lower number).

While the kernel compiled, I created a busybox initramfs. I used this blog article for how to do it. I’ll dump in the commands from the blog article, for detailed information, go and read it, it’s a really good article. I didn’t do the speed-optimizing things, as the QEMU boots fast enough for me (~3 seconds).

(I used the latest busybox, but I don’t see why it should work with the one from the article, which is about two versions old)

curl http://busybox.net/downloads/busybox-1.23.2.tar.bz2 | tar xjf -
cd busybox-1.23.2
mkdir -pv /tmp/obj/busybox-x86
make O=/tmp/obj/busybox-x86 defconfig
make O=/tmp/obj/busybox-x86 menuconfig # See below why
cd /tmp/obj/busybox-x86
make -j 16

# it does not install in your system, so you can execute this without fear
make install

mkdir -p /tmp/initramfs/x86-busybox
cd /tmp/initramfs/x86-busybox
mkdir -pv {bin,sbin,etc,proc,sys,usr/{bin,sbin}}
cp -av /tmp/obj/busybox-x86/_install/* .

# now edit the init script
vim init

You have to enable busybox build as static binary (with the menuconfig part).

The init script I use:

#!/bin/sh

echo "INIT SCRIPT"

mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug

mkdir /tmp
mount -t tmpfs none /tmp

mdev -s # We need this to find /dev/sda later

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"

exec /bin/sh

The init script boots into a /bin/sh. We can now build the initramfs with it:

chmod +x init
find . -print0 \
    | cpio --null -ov --format=newc \
    | gzip -9 > /tmp/initramfs-busybox-x86.cpio.gz

Try it out (1)

Now we can try it out, whether the kernel boots and we get out shell:

# In the kernel tree:
qemu-system-x86_64 \
    -kernel arch/x86_64/boot/bzImage \
    -initrd /tmp/initramfs-busybox-x86.cpio.gz \
    -nographic -append "console=ttyS0"

We should see some output like this:

[...]
[    7.170702] Freeing unused kernel memory: 1200K (ffffffff81f55000 - ffffffff82081000)
[    7.171539] Write protecting the kernel read-only data: 14336k
[    7.177328] Freeing unused kernel memory: 816K (ffff880001934000 - ffff880001a00000)
[    7.219360] Freeing unused kernel memory: 1116K (ffff880001ce9000 - ffff880001e00000)
INIT SCRIPT
mount: mounting none on /sys/kernel/config failed: No such file or directory
mount: mounting none on /tmp failed: No such file or directory

Boot took 7.33 seconds

/bin/sh: can't access tty; job control turned off
/ # [    7.630681] input: ImExPS/2 BYD TouchPad as /devices/platform/i8042/serio1/input/input3

/ #

(Yes, the output is slightly messed up… but hey! We just booted a linux kernel in a QEMU!

Load the kernel module

We can now create a kernel module in the linux kernel tree:

# In the kernel tree
git checkout -b my-module
mkdir fs/iamcoolfs/ # because I like filesystems
cat <<EOS > ./fs/iamcoolfs/Kconfig
config COOL_FS
        tristate "COOLFS support"
        help
          Private FS for playing and learning FS programming

          This will definitively eat everything you put into it.

EOS
cat <<EOS > ./fs/iamcoolfs/Makefile
#
# Makefile for the linux mb-filesystem routines.
#

obj-$(CONFIG_MB_FS) += coolfs.o
EOS
cat <<EOS > ./fs/iamcoolfs/coolfs.c
#include <linux/init.h>
#include <linux/module.h>

static int __init coolfs_init(void)
{
        pr_debug("cool module loaded\n");
        return 0;
}

static void __exit coolfs_fini(void)
{
        pr_debug("cool module unloaded\n");
}

module_init(coolfs_init);
module_exit(coolfs_fini);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("<YOUR NAME GOES HERE>");

EOS

This creates all the files we need for a minimal module. Because I like filesystems and I really want to implement a minimal filesystem, I chose to create the files in ./fs, but I guess it shouldn’t matter.

Make sure you adapt the fs/Kconfig file to find your new module! Put

source "fs/coolfs/Kconfig"

into it.

Now you can execute make menuconfig and find the configuration option for your new module. We can also simply make M=fs/coolfs to build the module. Now we create a (ext2) disk where we can cp the module to, so we can mount that disk into our QEMU and use the module there:

dd if=/dev/zero of=/tmp/disk bs=4M count=100
sudo mkfs.ext2 /tmp/disk # you could also try ext4, for example
mkdir /tmp/disk-mount
sudo mount /tmp/disk /tmp/disk-mount
cp fs/coolfs/coolfs.ko /tmp/disk-mount/
sudo umount /tmp/disk-mount

Try it out (2)

And when we boot our QEMU now with -hda /tmp/disk, we can find /dev/sda in the booted instance and mount that:

# In the booted VM
mkdir sda
mount /dev/sda ./sda
ls sda

Happy FS dev!