Week 4 — QEMU Deep Dive¶
Goal¶
Run your custom kernel in QEMU with full networking. Learn to iterate fast: edit code → build → boot → test in under a minute. By the end of this week, QEMU will be your primary development environment.
Why This Matters¶
You never test kernel changes on your real system first. QEMU gives you an isolated environment where a kernel panic just means closing a window. For networking work, QEMU also provides virtual network devices that behave like real hardware, so you can test driver and protocol changes without physical NICs.
Install QEMU¶
Verify:
Create a Root Filesystem Disk Image¶
Your kernel needs a root filesystem to boot into. We'll create a Debian-based disk image
using debootstrap:
sudo apt install -y debootstrap
# Create a 4 GB sparse disk image
qemu-img create -f qcow2 ~/qemu-debian.qcow2 4G
# Attach it as a block device
sudo modprobe nbd max_part=8
sudo qemu-nbd --connect=/dev/nbd0 ~/qemu-debian.qcow2
# Partition and format
sudo parted /dev/nbd0 --script mklabel msdos mkpart primary ext4 1MiB 100%
sudo mkfs.ext4 /dev/nbd0p1
# Mount and install Debian
sudo mkdir -p /mnt/qemu-rootfs
sudo mount /dev/nbd0p1 /mnt/qemu-rootfs
sudo debootstrap --variant=minbase trixie /mnt/qemu-rootfs http://deb.debian.org/debian
Configure the rootfs:
# Set up basic networking, fstab, hostname
sudo chroot /mnt/qemu-rootfs bash -c '
echo "qemu-dev" > /etc/hostname
echo "127.0.0.1 localhost qemu-dev" > /etc/hosts
echo "/dev/sda1 / ext4 errors=remount-ro 0 1" > /etc/fstab
# Set root password
echo "root:root" | chpasswd
# Enable serial console for QEMU
systemctl enable serial-getty@ttyS0.service 2>/dev/null || true
# Install minimal networking tools
apt update
apt install -y iproute2 iputils-ping net-tools kmod procps
'
Clean up:
Boot Your Custom Kernel¶
First, install your kernel modules into the rootfs:
cd ~/linux
# Install modules to a temporary directory
make modules_install INSTALL_MOD_PATH=/tmp/kernel-modules
# Copy them into the disk image
sudo qemu-nbd --connect=/dev/nbd0 ~/qemu-debian.qcow2
sudo mount /dev/nbd0p1 /mnt/qemu-rootfs
sudo cp -r /tmp/kernel-modules/lib/modules/* /mnt/qemu-rootfs/lib/modules/
sudo umount /mnt/qemu-rootfs
sudo qemu-nbd --disconnect /dev/nbd0
Now boot:
qemu-system-x86_64 \
-kernel ~/linux/arch/x86/boot/bzImage \
-drive file=~/qemu-debian.qcow2,format=qcow2 \
-append "root=/dev/sda1 rw console=ttyS0" \
-nographic \
-m 2G \
-smp 4 \
-enable-kvm
Flags explained:
-kernel— boot this kernel directly, bypassing GRUB-drive— attach the disk image-append— kernel command line (console=ttyS0redirects output to serial)-nographic— no graphical window, everything on your terminal (essential for SSH)-m 2G— 2 GB of RAM for the guest-smp 4— 4 virtual CPUs-enable-kvm— use hardware virtualization (much faster)
Expect: You'll see kernel boot messages scroll by, then a login prompt.
Login with root / root.
To exit QEMU: Press Ctrl+A then X.
Create a Boot Script¶
You'll run this hundreds of times. Make a script:
cat > ~/boot-kernel.sh << 'EOF'
#!/bin/bash
KERNEL=~/linux/arch/x86/boot/bzImage
ROOTFS=~/qemu-debian.qcow2
APPEND="root=/dev/sda1 rw console=ttyS0 nokaslr"
qemu-system-x86_64 \
-kernel "$KERNEL" \
-drive file="$ROOTFS",format=qcow2 \
-append "$APPEND" \
-nographic \
-m 2G \
-smp 4 \
-enable-kvm \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0 \
"$@"
EOF
chmod +x ~/boot-kernel.sh
New flags:
nokaslr— disables kernel address space layout randomization (needed for GDB)-netdev user,id=net0,hostfwd=tcp::2222-:22— user-mode networking, forwards port 2222 on host to port 22 on guest (SSH)-device virtio-net-pci,netdev=net0— a virtio network card
QEMU Networking Modes¶
QEMU has several networking backends. Understand the tradeoffs:
User mode (SLIRP) — simplest, no root needed¶
The guest gets IP 10.0.2.15, gateway 10.0.2.2, DNS 10.0.2.3. NAT-based — the guest
can reach the internet but the host can't initiate connections to the guest (except via
port forwarding). Good enough for most development.
TAP — full network integration¶
# Create a TAP device (requires root)
sudo ip tuntap add dev tap0 mode tap user $(whoami)
sudo ip addr add 192.168.100.1/24 dev tap0
sudo ip link set tap0 up
# Use it in QEMU
-netdev tap,id=n0,ifname=tap0,script=no,downscript=no \
-device virtio-net-pci,netdev=n0
The guest gets a real interface on a shared network. You can configure routing, bridging, packet capture. Use this when you need to test real networking scenarios.
Socket — connect multiple QEMU VMs¶
# VM 1 - listen
-netdev socket,id=n0,listen=:1234 -device virtio-net-pci,netdev=n0
# VM 2 - connect
-netdev socket,id=n0,connect=127.0.0.1:1234 -device virtio-net-pci,netdev=n0
Creates a virtual cable between two VMs. Useful for testing multi-host protocols.
QEMU Snapshots¶
Save and restore VM state:
# In the QEMU monitor (Ctrl+A, C to enter monitor mode):
savevm my-snapshot # Save state
loadvm my-snapshot # Restore state
info snapshots # List snapshots
From the command line:
# Create a snapshot without running the VM
qemu-img snapshot -c clean-boot ~/qemu-debian.qcow2
# List snapshots
qemu-img snapshot -l ~/qemu-debian.qcow2
# Revert to snapshot
qemu-img snapshot -a clean-boot ~/qemu-debian.qcow2
Use snapshots aggressively. Save a clean-boot snapshot, then you can always revert after a kernel panic or filesystem corruption.
Sharing Files Between Host and Guest¶
Use 9p/virtio-fs to share a directory:
qemu-system-x86_64 \
... \
-virtfs local,path=/home/you/shared,mount_tag=host_share,security_model=mapped-xattr,id=host0
In the guest:
This is great for copying test scripts or kernel modules into the guest without rebuilding the disk image.
The Development Loop¶
Your daily workflow becomes:
1. Edit code in ~/linux/ (host, neovim)
2. make -j$(nproc) # ~seconds for incremental build
3. make modules_install INSTALL_MOD_PATH=/tmp/km
4. Copy modules to rootfs (or use 9p share)
5. ~/boot-kernel.sh # Boot in QEMU
6. Test your changes
7. Ctrl+A, X to exit
8. Repeat
For module-only changes, you can skip rebuilding bzImage and just reload the module in a running guest.
Exercises¶
- Boot the defconfig kernel in QEMU. Verify it reaches a login prompt.
- Inside the guest, run
uname -rand confirm it matches your built kernel. - Set up user-mode networking with port forwarding. Install
openssh-serverin the guest and SSH in from the host viassh -p 2222 root@localhost. - Set up a TAP interface and ping between host and guest.
- Create a clean-boot snapshot. Break something (delete
/bin/bashin the guest). Restore the snapshot. - Set up 9p sharing. Write a test script on the host and run it in the guest.
What's Next¶
Next week we add debugging to this setup — GDB attached to the kernel, printk tracing, and ftrace. This is how you'll diagnose your networking changes when they don't work.