Project 3 Preliminary: QEMU and the Linux Kernel


This assignment is meant to get you comfortable with customizing, compiling, and booting your own qemu installation and guest Linux kernel. This will come in handy in project 3. Essentially what you'll be doing here is building and testing your own little Linux distro.

Your goal here is to download the QEMU full system emulator from source, download a recent version of the Linux kernel from source, and boot Linux on QEMU. You can do this in whatever environment you want (your machine, a VM, etc. I recommend a VM as you may have to install some dependencies).

Getting the Code

Download the most recent QEMU here. You can download the kernel here.

Your Task

Your first goal will be to build both codebases. For QEMU, you should just follow the instructions on the webpage. The Linux kernel build process is a bit more involved. It will require you to build both a kernel and a user-space environment (which will be loaded into a RAM-based filesystem called an initramfs). You can then boot these on QEMU.


The configuration utilities for the Linux kernel and for BusyBox require a package called ncurses. You should install the devel version of this package on whatever system you're using. We'll also need the static version of glibc installed. For example, on Fedora, I would run:

    $> sudo dnf install ncurses-devel glibc-static

Building a user-space enironment (initramfs)

You'll be using BusyBox to build your environment. BusyBox will allow you to build a packaged up environment that includes a minimal set of user-space programs (namely, the shell and binutils) needed for a basic working OS.

First, you'll need to build a BusyBox-based static binary which we can pass on to QEMU and which the Linux kernel will use when it transfers control to userspace. You can get the latest busybox as and unpack it as follows:

    $> wget
    $> tar pvzf busybox-1.29.3.tar.bz2
    $> cd busybox-1.29.3

You now need to configure it for the options we want. The main thing is that we need to have all these programs compiled into a single static binary so that we don't have to worry about libraries etc. We will create a separate output directory to store the generated image.

    $> mkdir -pv obj/bb-x86
    $> make O=obj/bb-x86 defconfig

This will make a default configuration, and instructs the build toolchain to put its output in the bj/bb-x86directory. We now need to modify that config for the static binary option:

    $> make O=obj/bb-x86 menuconfig
This will bring up a menu in the terminal which will ask you for options. Use the forward slash (/) key to search for the word static. You should see some option like "Build BusyBox as a static binary". Select that option, save, and exit.

Now, we just have to build BusyBox:

    $> cd obj/bb-x86
    $> make
    $> make install

We now have a basic BusyBox binary, but we have to package it in a small filesystem with a directory structure which Linux can use wholesale. This is the initramfs. We can do that as follows:

    $> cd ../..
    $> mkdir -p initramfs/bb-x86
    $> cd initramfs/bb-x86
    $> mkdir -pv {bin,sbin,etc,proc,sys,usr/{bin,sbin}}
    $> cp -av ../../obj/bb-x86/_install/* .
This gives us the basic directory structure that we need. The one thing we're missing this point is the entry program, i.e. the first user program that the kernel will run once it boots. This is typically called init. We can just make a shell script that will act as our init program (in the current directory, initramfs/bb-x86). Open up a file with a test editor called init with the following contents:
	mount -t proc none /proc
	mount -t sysfs none /sys
	mount -t devtmpfs devtmpfs /dev
	echo -e "\nBoot took $(cut -d ' ' -f1 /proc/uptime) seconds\n"
	cat <<!

	Boot took $(cut -d' ' -f1 /proc/uptime) seconds

	   ____ ____    ____   __  ____  
	  / ___/ ___|  | ___| / /_|___ \ 
	 | |   \___ \  |___ \| '_ \ __) |
	 | |___ ___) |  ___) | (_) / __/ 
	  \____|____/  |____/ \___/_____|

	Welcome to CS 562 Tiny Linux

	setsid /bin/sh -c "exec /bin/sh </dev/ttyS0 >/dev/ttyS0 2>&1"
Note the last line here puts a terminal on the serial console. If you want to have a normal login shell you can use tty1instead of ttyS0. If we don't have this line at all, you'd see something like "Job control disabled" once Linux boots. Save this file and quit. Now change the permissions of this file to be executable:
    $> chmod +x init
We're good to go now. We just need to package this initramfs up in a compressed format that Linux expects (it wants a gzipped CPIO archive):
    $> find . -print0 \
       | cpio --null -ov --format=newc \
       | gzip -9 > ../../obj/initramfs-bb-x86.cpio.gz
We can now pass this file to QEMU using the initrd argument at the command line.


Here, I am giving you less instruction, but you should download the latest Linux kernel version, configure it however you like (I recommend using a default configuration), and build it. What you'll end up with is a bzImage which you can also pass to QEMU directly.


Once you've built QEMU (following the instructions on their webpage), you can now run this kernel and initramfs using the x86 full system emulator that it provides. You should run it like this:

    $> /path/to/qemu-system-x86_64 \
       -kernel /path/to/my/kerneldir/arch/x86_64/boot/bzImage \
       -initrd /path/to/my/busyboxdir/obj/initramfs-bb-x86.cpio.gz \
       -nographic -append "console=ttyS0"
This will boot Linux in QEMU in non-graphical mode and print its output to your terminal. The "console=ttyS0" line is attaching an argument to the kernel's command line which tells it it should print the console to the first serial port (which QEMU dumps directly out to the screen). To quit out of QEMU, first hit Ctrl+a followed by c, which will take you to the QEMU monitor shell. You can then hit q to quit.


For this homework you should send me a screencast of you booting your kernel into your BusyBox shell at the command line (please use asciinema). This is due by next Thursday, Nov. 8.