Project 3: Build and Boot Your First Custom Kernel
Build the Linux kernel from source, create a minimal root filesystem, and boot it in QEMU—with modifications you can observe.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Intermediate |
| Time Estimate | Weekend |
| Language | C (kernel), Shell (scripting) |
| Prerequisites | Projects 1-2, understanding of makefiles |
| Key Topics | Kconfig, Kbuild, QEMU, boot process |
1. Learning Objectives
By completing this project, you will:
- Understand the kernel build system (Kconfig and Kbuild)
- Learn the Linux boot process from firmware to userspace
- Establish a kernel development workflow with QEMU
- Make your first kernel modification and see the results
2. Theoretical Foundation
2.1 Core Concepts
Kconfig: The kernel’s configuration system. It defines thousands of options that control which features are built, as modules or built-in. The .config file stores your choices.
Kbuild: The kernel’s make-based build system. It compiles only what’s needed based on your configuration.
initramfs: An initial RAM filesystem that the kernel uses before mounting the real root filesystem. Contains essential tools needed for early boot.
Boot Process Overview:
┌─────────────────────────────────────────────────────────────┐
│ Power On │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Firmware (BIOS/UEFI) │ │
│ │ - POST (hardware initialization) │ │
│ │ - Find bootable device │ │
│ │ - Load bootloader │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Bootloader (GRUB) │ │
│ │ - Load kernel (bzImage) into memory │ │
│ │ - Load initramfs into memory │ │
│ │ - Jump to kernel entry point │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Kernel │ │
│ │ - Decompress itself │ │
│ │ - Initialize hardware, memory, scheduler │ │
│ │ - Mount initramfs as root │ │
│ │ - Execute /init from initramfs │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ /init (initramfs) │ │
│ │ - Load necessary drivers │ │
│ │ - Mount real root filesystem │ │
│ │ - pivot_root or switch_root to real root │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Init System (systemd) │ │
│ │ - Start services │ │
│ │ - Reach multi-user target │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.2 Why This Matters
You cannot contribute to the kernel without being able to build and test it. This project establishes your development workflow:
- Fast iteration: Make a change, rebuild, reboot in seconds with QEMU
- Safe experimentation: Crash your VM, not your machine
- Debugging capability: GDB integration for stepping through kernel code
2.3 Historical Context
The kernel build system has evolved significantly:
- Early Linux: Simple Makefile
- 2.4: Introduced
make menuconfig - 2.6: Kconfig/Kbuild system introduced
- Modern: Supports thousands of options, cross-compilation, out-of-tree builds
2.4 Common Misconceptions
- “You need a powerful machine” - A modest machine can build the kernel in ~20 minutes; ccache makes rebuilds much faster.
- “You need to install the kernel” - For development, boot directly in QEMU without touching your host.
- “Every change needs a full rebuild” - Incremental builds only recompile changed files.
3. Project Specification
3.1 What You Will Build
A complete kernel development workflow:
- Build scripts for the kernel
- A minimal initramfs with busybox
- QEMU launch scripts with GDB support
- Your first kernel modification (custom version string)
3.2 Functional Requirements
- Build System:
- Script to configure kernel for QEMU
- Script to build kernel with ccache
- Clean rebuild option
- Root Filesystem:
- Minimal initramfs with BusyBox
- Basic shell environment
- Common utilities (ls, cat, mount, etc.)
- QEMU Integration:
- Boot script with serial console
- GDB debugging support
- Shared folder with host (9p)
- Kernel Modification:
- Custom EXTRAVERSION string
- Custom boot message
3.3 Non-Functional Requirements
- Build time under 30 minutes (first build)
- Incremental rebuild under 2 minutes
- Boot time under 5 seconds in QEMU
3.4 Example Usage / Output
$ ./build_kernel.sh
Configuring kernel for QEMU...
Building kernel (this takes a few minutes)...
CC init/main.o
...
Kernel ready at: bzImage
$ ./build_initramfs.sh
Creating minimal initramfs with BusyBox...
Initramfs ready at: initramfs.cpio.gz
$ ./boot_qemu.sh
[ 0.000000] Linux version 6.8.0-MYKERNEL-learning (you@yourhost)
[ 0.000000] Command line: console=ttyS0 nokaslr
[ 0.012345] Hello from my modified kernel startup!
...
[ 0.234567] Run /init as init process
Welcome to your custom kernel!
Kernel version: 6.8.0-MYKERNEL-learning
# uname -a
Linux (none) 6.8.0-MYKERNEL-learning #1 SMP PREEMPT_DYNAMIC ...
# cat /proc/version
Linux version 6.8.0-MYKERNEL-learning (you@yourhost) ...
# dmesg | grep -i "hello"
[ 0.001234] Hello from my modified kernel startup!
3.5 Real World Outcome
You’ll have a working kernel development environment that you’ll use for all subsequent projects. This is the same workflow used by kernel developers worldwide.
4. Solution Architecture
4.1 High-Level Design
┌─────────────────────────────────────────────────────────────┐
│ Development Host │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Kernel │ │ Initramfs │ │ QEMU │ │
│ │ Source │ │ Builder │ │ Launcher │ │
│ │ Tree │ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ make bzImage cpio archive qemu-system │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Artifacts │ │
│ │ - bzImage (kernel) │ │
│ │ - initramfs.cpio.gz │ │
│ │ - vmlinux (for GDB symbols) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
│
│ qemu-system-x86_64
▼
┌─────────────────────────────────────────────────────────────┐
│ QEMU Virtual Machine │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Your Custom Kernel │ │
│ │ + initramfs │ │
│ │ + BusyBox shell │ │
│ └──────────────────────────────────────────────────┘ │
│ ▲ │
│ │ GDB (port 1234) │
└─────────────────────────────────────────────────────────────┘
4.2 Key Components
- Kernel Source: The Linux kernel git repository
- Build Scripts: Configuration and compilation automation
- Initramfs: Minimal root filesystem for booting
- QEMU Scripts: VM launch with various options
4.3 Directory Structure
kernel-dev/
├── linux/ # Kernel source tree
│ └── .config # Your kernel config
├── initramfs/ # Initramfs contents
│ ├── init # Init script
│ ├── bin/ # BusyBox symlinks
│ └── ...
├── scripts/
│ ├── build_kernel.sh # Build the kernel
│ ├── build_initramfs.sh # Create initramfs
│ ├── boot_qemu.sh # Launch QEMU
│ └── debug_kernel.sh # Launch with GDB
├── artifacts/
│ ├── bzImage # Compiled kernel
│ ├── vmlinux # Kernel with symbols
│ └── initramfs.cpio.gz # Compressed initramfs
└── README.md
4.4 Algorithm Overview
Build Process:
- Configure:
make defconfig+ customizations - Build:
make -j$(nproc) bzImage - Result:
arch/x86/boot/bzImage
Initramfs Creation:
- Create directory structure
- Install BusyBox (static)
- Create init script
- Pack:
find . | cpio -o -H newc | gzip > initramfs.cpio.gz
Boot Process:
- QEMU loads kernel and initramfs into VM memory
- Kernel decompresses, initializes
- Kernel extracts initramfs to tmpfs
- Kernel executes /init
- Shell prompt appears
5. Implementation Guide
5.1 Development Environment Setup
# Install required tools
sudo apt install build-essential libncurses-dev bison flex \
libssl-dev libelf-dev bc dwarves git ccache qemu-system-x86
# Get kernel source
git clone --depth 1 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
# Set up ccache for faster rebuilds
export PATH="/usr/lib/ccache:$PATH"
5.2 Project Structure
Create the workspace:
mkdir -p kernel-dev/{scripts,initramfs/{bin,sbin,etc,proc,sys,dev,tmp},artifacts}
cd kernel-dev
5.3 The Core Question You’re Answering
“How does the Linux kernel boot, and how can I efficiently iterate on kernel changes?”
5.4 Concepts You Must Understand First
- What is a kernel image format?
- What’s the difference between vmlinux, bzImage, and vmlinuz?
- Reference: Kernel documentation on boot
- What is initramfs?
- Why do we need an initial root filesystem?
- How does the kernel find and execute /init?
- Reference: “How Linux Works” Chapter 5
- What is QEMU?
- How does hardware virtualization work?
- What is KVM and why does it make QEMU fast?
- Reference: QEMU documentation
5.5 Questions to Guide Your Design
Configuration:
- What kernel options are needed for QEMU boot?
- What drivers should be built-in vs modules?
Initramfs:
- What’s the minimum needed to get a shell?
- How do you create device nodes?
Debugging:
- How do you connect GDB to the kernel?
- What is KASLR and why disable it for debugging?
5.6 Thinking Exercise
Before running any commands, trace the boot process:
- QEMU starts, BIOS/UEFI equivalent runs
- QEMU’s direct kernel boot loads bzImage at 0x100000
- Kernel’s setup code runs in real mode
- Kernel switches to protected mode, then long mode
- Kernel decompresses itself
start_kernel()ininit/main.cruns- Kernel initializes all subsystems
- Kernel mounts initramfs from memory
- Kernel executes
/init - Your shell prompt appears
Question: What would happen if /init doesn’t exist in your initramfs?
5.7 Hints in Layers
Hint 1 - Basic Configuration:
cd linux
make defconfig # Start with defaults
make kvm_guest.config # Add KVM options
scripts/config --enable DEBUG_INFO # Enable debug symbols
Hint 2 - Essential Config Options:
# For QEMU boot
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_9P_FS=y # Share files with host
# For debugging
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
CONFIG_DEBUG_KERNEL=y
# For serial console
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
Hint 3 - Minimal Init Script:
#!/bin/sh
# /init for initramfs
# Mount essential filesystems
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
# Print welcome
echo "Welcome to your custom kernel!"
echo "Kernel version: $(uname -r)"
# Launch shell
exec /bin/sh
Hint 4 - QEMU Command:
qemu-system-x86_64 \
-kernel linux/arch/x86/boot/bzImage \
-initrd artifacts/initramfs.cpio.gz \
-append "console=ttyS0 nokaslr" \
-nographic \
-enable-kvm \
-m 1G \
-s # GDB stub on port 1234
5.8 The Interview Questions They’ll Ask
- “Explain the difference between bzImage, vmlinux, and vmlinuz”
- vmlinux: Raw kernel ELF, with debug symbols
- bzImage: Compressed kernel + bootloader setup code
- vmlinuz: Usually a symlink to bzImage
- “What is the kernel command line and how is it used?”
- Passed by bootloader to kernel
- Controls boot behavior (root=, console=, init=, etc.)
- Available in
/proc/cmdline
- “Why would you use initramfs instead of direct root mount?”
- Root filesystem might need drivers loaded first
- Encrypted root needs unlock before mount
- Network boot needs network stack first
- “What is CONFIG_LOCALVERSION?”
- Appended to kernel version string
- Helps identify custom builds
- Shows in
uname -r
- “How do you debug a kernel crash?”
- Serial console output
- GDB attached to QEMU
- crash dump analysis with
crashutility
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Kernel build system | Linux Kernel Programming | Chapters 2-3 |
| Boot process | How Linux Works | Chapter 5 |
| Kernel configuration | Linux Device Drivers, 3rd Ed | Chapter 2 |
| QEMU/KVM | Various QEMU docs | - |
5.10 Implementation Phases
Phase 1: Build the Kernel (Day 1)
- Clone kernel source
- Configure with
make defconfig - Build with
make -j$(nproc) bzImage - Note the location of bzImage
Phase 2: Create Initramfs (Day 1)
- Download/build static BusyBox
- Create directory structure
- Write init script
- Create cpio archive
Phase 3: Boot in QEMU (Day 1)
- Write QEMU launch script
- Boot and verify shell works
- Test basic commands
Phase 4: Kernel Modifications (Day 2)
- Change EXTRAVERSION in Makefile
- Add printk to start_kernel()
- Rebuild and verify changes
Phase 5: GDB Integration (Day 2)
- Add debug config options
- Launch QEMU with
-s -S - Connect GDB, set breakpoint, continue
5.11 Key Implementation Decisions
Decision 1: What kernel version to use
- Latest stable is fine for learning
- Avoid release candidates (unstable)
Decision 2: How to configure
- Start with defconfig (reasonable defaults)
- Add kvm_guest.config for QEMU
- Manually enable debug options
Decision 3: initramfs vs actual rootfs
- initramfs is simpler for development
- No disk image to manage
- Everything in RAM, fast boot
6. Testing Strategy
Build Tests
# Verify kernel builds successfully
make -j$(nproc) bzImage
[ -f arch/x86/boot/bzImage ] && echo "Kernel build OK"
# Verify vmlinux has symbols (for GDB)
file vmlinux | grep -q "not stripped" && echo "Symbols OK"
Boot Tests
# Test basic boot (timeout if hangs)
timeout 30 qemu-system-x86_64 \
-kernel bzImage -initrd initramfs.cpio.gz \
-append "console=ttyS0" -nographic \
| grep -q "Welcome" && echo "Boot OK"
Modification Tests
# Verify custom version shows
qemu-system-x86_64 ... -append "..." | grep "MYKERNEL"
# Verify custom printk shows
qemu-system-x86_64 ... | grep "Hello from my modified"
7. Common Pitfalls & Debugging
| Problem | Symptom | Root Cause | Fix |
|---|---|---|---|
| Build fails | “elf.h not found” | Missing dev packages | apt install libelf-dev |
| QEMU hangs | Black screen | Missing serial console config | Add console=ttyS0 to cmdline |
| No shell prompt | Kernel panic | No /init in initramfs | Check initramfs creation |
| GDB won’t connect | Connection refused | QEMU not started with -s | Add -s flag |
| Slow boot | Takes 30+ seconds | KVM not enabled | Add -enable-kvm, check /dev/kvm |
Quick Verification
# Check KVM is available
ls -la /dev/kvm
# Check kernel config is correct
grep CONFIG_SERIAL_8250_CONSOLE linux/.config
# Check initramfs has /init
gunzip -c initramfs.cpio.gz | cpio -t | grep "^init$"
8. Extensions & Challenges
Easy Extensions
- Add more utilities: Include
strace,gdbin initramfs - Add shared folder: 9p filesystem for host-guest file sharing
- Multiple consoles: Add virtio-console support
Medium Extensions
- Real root filesystem: Create ext4 image, mount after initramfs
- Network support: Add virtio-net, configure networking
- Module loading: Build some drivers as modules, load dynamically
Hard Extensions
- Cross-compilation: Build for ARM, boot in QEMU ARM
- Custom scheduler: Modify CFS, measure impact
- Kernel live patching: Test kpatch/livepatch
9. Real-World Connections
How Kernel Developers Work
This is the actual workflow:
- Clone kernel, configure, build
- Boot in QEMU for fast iteration
- Once stable, test on real hardware
- Submit patches for review
Continuous Integration
Kernel CI systems like:
- KernelCI: Automated build and boot testing
- Intel 0-day: Tests every patch for regressions
- syzkaller: Fuzzing for bugs
They all use similar QEMU-based testing.
10. Resources
Essential Documentation
Documentation/admin-guide/kernel-parameters.txtDocumentation/kbuild/kconfig.rstDocumentation/dev-tools/gdb-kernel-debugging.rst
Code References
init/main.c- Kernel initializationarch/x86/boot/- x86 boot codeusr/- initramfs building
Online Resources
11. Self-Assessment Checklist
Before moving to the next project, verify:
- I can build the kernel from source
- I understand what Kconfig options control
- I can create a bootable initramfs
- My kernel boots in QEMU with serial console
- I’ve modified the kernel and seen my changes
- I can attach GDB to the running kernel
- I understand the boot process from QEMU to shell
- I know what
nokaslrdoes and why we use it
12. Submission / Completion Criteria
Your project is complete when:
- Kernel builds without errors
- Custom version string appears in
uname -r - Custom boot message appears in dmesg
- Shell is usable (ls, cat, mount work)
- GDB can connect and hit breakpoints
- Scripts are reusable and documented
Verification Commands
# These should all work:
./scripts/build_kernel.sh
./scripts/build_initramfs.sh
./scripts/boot_qemu.sh
# Inside QEMU:
uname -r | grep MYKERNEL
dmesg | grep "Hello from my"
Next Project: P04 - Kernel Module - Write loadable kernel modules to learn kernel programming fundamentals.