5 — The kernel, from allnoconfig
Requirements: chapter 0’s packages, Linux 7.3 at /lo/build/linux-7.3, and the initramfs from chapter 4. Use your host compiler here. The kernel doesn’t link against libc, so it doesn’t need the musl toolchain.
Step 1: start from nothing
cd /lo/build/linux-7.3
make ARCH=x86_64 allnoconfig
grep -E '^CONFIG_(64BIT|X86_64)=' .config
Always pass ARCH=x86_64. Without it, allnoconfig answers no to the 64-bit question, and you get a 32-bit base (arch/x86/Kconfig).
Expected: both options are =y.
Step 2: write the fragment
Create /lo/out/lo.fragment. Add a one-line reason to your manifest for every group:
cat > /lo/out/lo.fragment <<'E'
CONFIG_64BIT=y
CONFIG_BINFMT_ELF=y
CONFIG_BINFMT_SCRIPT=y
CONFIG_TTY=y
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_BLK_DEV_INITRD=y
CONFIG_PROC_FS=y
CONFIG_SYSFS=y
CONFIG_TMPFS=y
CONFIG_DEVTMPFS=y
CONFIG_PCI=y
CONFIG_VIRTIO_MENU=y
CONFIG_VIRTIO_PCI=y
CONFIG_BLK_DEV=y
CONFIG_VIRTIO_BLK=y
CONFIG_EXT4_FS=y
CONFIG_EROFS_FS=y
CONFIG_MD=y
CONFIG_BLK_DEV_DM=y
CONFIG_DM_VERITY=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_NET=y
CONFIG_NET_CORE=y
CONFIG_NETDEVICES=y
CONFIG_INET=y
CONFIG_UNIX=y
CONFIG_PACKET=y
CONFIG_VIRTIO_NET=y
CONFIG_VETH=y
CONFIG_CGROUPS=y
CONFIG_MEMCG=y
CONFIG_CGROUP_PIDS=y
CONFIG_NAMESPACES=y
CONFIG_SYSVIPC=y
CONFIG_USER_NS=y
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_FS=y
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
CONFIG_DEBUG_INFO_BTF=y
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENTS=y
CONFIG_UPROBE_EVENTS=y
CONFIG_PERF_EVENTS=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_CGROUP_BPF=y
E
KCONFIG_ALLCONFIG=/lo/out/lo.fragment make ARCH=x86_64 allnoconfig
Decision: build drivers in, with no modules. The kernel image is then the whole kernel, and the module-signing question never arises.
Step 3: check every option survived
This is the most important step in the chapter. Kconfig silently drops options whose dependencies aren’t met:
grep -E '^CONFIG_' /lo/out/lo.fragment | while read -r line; do
grep -qx "$line" .config || echo "DROPPED: $line"
done
Expected: no DROPPED lines.
If some appear: run make ARCH=x86_64 menuconfig, press /, search for the symbol, and read its Depends on: line. Add what’s missing to the fragment.
Step 4: build it
make ARCH=x86_64 -j"$(nproc)" 2>&1 | tee /lo/out/kbuild.log
ls -l arch/x86/boot/bzImage vmlinux
cp arch/x86/boot/bzImage /lo/out/bzImage
Expected: both files exist. A very large vmlinux is normal, because of debug info. bzImage is what boots.
Step 5: boot and read the timeline
qemu-system-x86_64 -m 512M -nographic -no-reboot \
-kernel /lo/out/bzImage -initrd /lo/out/initramfs.cpio.gz \
-append "console=ttyS0 initcall_debug printk.time=1 log_buf_len=4M panic=-1"
initcall_debug logs every kernel init function and how long it took (kernel parameters). In the guest:
dmesg | grep 'initcall .* after' | tail
mount -t tracefs nodev /sys/kernel/tracing
wc -l /sys/kernel/tracing/available_events
Look for: lines of the form “initcall … returned … after … usecs”. Sort on the usecs field, and find your three slowest. The last number shows how many tracepoints this kernel exposes.
To draw it as a picture, save the dmesg output to a file, and on the host run:
perl scripts/bootgraph.pl < dmesg.txt > boot.svg
Step 6: measure the result
grep -c '=y' .config
ls -l vmlinux arch/x86/boot/bzImage
Now compare with the default config, in a separate copy of the tree:
make ARCH=x86_64 defconfig && grep -c '=y' .config
Write down both counts and sizes. These are your numbers.
If it fails
| What you see | Cause | Fix |
|---|---|---|
| Boot prints nothing | No serial console | Check SERIAL_8250_CONSOLE=y and console=ttyS0 |
DEBUG_INFO_BTF dropped, or the link fails on BTF | Missing or old pahole | Install it. The kernel docs list 1.26 |
| “No working init found” | No /init, or BLK_DEV_INITRD dropped | Step 3, and chapter 4’s checks |
| initramfs ignored | RD_GZIP off | Confirm CONFIG_RD_GZIP=y |
| 32-bit build | Missing ARCH=x86_64 | Use it on every make |
“Exec format error” for /init | BINFMT_ELF off | Step 3 |
If you’re really stuck, make ARCH=x86_64 kvm_guest.config adds a working QEMU set, though it brings in more than you need.
Principle: every default is a decision someone made for you
Each enabled option is a surface, a boot delay, or a behaviour someone else chose. Building up from nothing doesn’t make the kernel “better”; it makes every enabled thing yours, with a reason next to it. The same is true of every framework’s defaults in any system.