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).
Download the most recent QEMU here. You can download the kernel here.
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
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 https://busybox.net/downloads/busybox-1.29.3.tar.bz2
$> 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-x86
directory. 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:
#!/bin/sh
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 tty1
instead 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.