Last time, we covered how to set up a development environment with the latest kernel using QEMU. But after trying it for a while, I found it wasn’t very convenient.
More often than not, I just need to run a single program (like a unit test), and the full environment built in the previous article makes this process more complicated. This article introduces a simpler approach. A simple script is all it takes to build a complete runtime environment.
initrd
The previous article already mentioned using initrd to boot the operating system. An initrd is a read-only filesystem image that the kernel loads into memory at boot time. Programs can access it like a regular filesystem.
After loading the filesystem, the kernel looks for an available init program inside it. So all we need to do is write a proper init program and package it into an initrd.
busybox
In this from-scratch filesystem-building scenario, using dynamic libraries is too cumbersome. So we’ll use busybox from the host directly to provide basic utilities.
Preparation
First, create a WORK_DIR directory. Files under this path will be packaged into the filesystem image.
WORK_DIR=$(mktemp -d)
trap "rm -rf $WORK_DIR" EXIT
Then find busybox on the host and copy it to $WORK_DIR/bin/busybox.
mkdir -p "$WORK_DIR/bin"
# Copy busybox into the work directory
BUSYBOX_PATH=$(which busybox)
if [ -z "$BUSYBOX_PATH" ]; then
echo "Error: busybox not found in PATH."
exit 1
fi
cp "$BUSYBOX_PATH" "$WORK_DIR/bin/busybox"
Create the $WORK_DIR/init script and make it executable. The kernel will recognize and execute this script during boot.
# Create init script
cat << 'EOF' > "$WORK_DIR/init"
#!/bin/busybox sh
# ...
EOF
chmod +x "$WORK_DIR/init"
The init Script
Now let’s look at the contents of $WORK_DIR/init. First, declare that this script is executed by /bin/busybox sh.
#!/bin/busybox sh
Next, create the usual filesystem paths and mount the three special filesystems: proc, sys, and dev.
# Initialize minimal directories
busybox mkdir -p /etc /proc /root /sbin /sys /usr/bin /usr/sbin
# Mount necessary filesystems
busybox mount -t proc proc /proc
busybox mount -t sysfs sys /sys
busybox mdev -s
Install the tools provided by busybox. This creates symbolic links from tool names to busybox.
# Install busybox applets
busybox --install -s
Set up the user. root user, uid=0, gid=0, no password, home at /root, shell at /bin/sh.
# Create a minimal passwd file
echo root::0:0:root:/root:/bin/sh > /etc/passwd
Bring up the loopback interface.
# Set up loopback interface
hostname localhost
ip link set lo up
Reduce kernel log verbosity. Log in as root. On exit, power off.
# Reduce kernel printk verbosity
echo 5 > /proc/sys/kernel/printk
# Start an interactive shell
login root
# Power off
poweroff -f
Building the initrd Image
Next, build the initrd image.
$FILES defines a series of additional files to place into the image. Note: if you need to run a program, it should be statically linked.
# Copy user-specified files into /root
mkdir -p "$WORK_DIR/root"
for FILE in $FILES; do
BASENAME=$(basename "$FILE")
cp "$FILE" "$WORK_DIR/$DEST_DIR/$BASENAME"
done
Finally, use the cpio command to convert WORK_DIR into an initrd filesystem. All files are owned by root.
# Create the initrd image
OUTPUT_FILE_REAL=$(realpath "$OUTPUT_FILE")
cd "$WORK_DIR"
find . | cpio -o -H newc -R 0:0 | gzip -9 > "$OUTPUT_FILE_REAL"
echo "Created initrd image: $OUTPUT_FILE"
Running
Run with the following command:
qemu-system-x86_64 \
-m 512M \
-kernel /path/to/bzImage \
-initrd /path/to/initrd \
-append "console=ttyS0" \
-nographic
One more plug for my new project Condy :)