So far in this free embedded Linux course we have talked about how a board gets its own machine identity inside the kernel, either through a device tree or, on older platforms, through a dedicated board file. Knowing that a board is described somewhere in the source tree is only half the story. This lecture closes the loop: how do you actually turn that description into a running kernel on real (or emulated) hardware, and how do you tell whether the boot succeeded or failed?
We will build a kernel with a target defconfig, understand the image formats a build can produce, and walk through the bootloader hand-off that gets your kernel banner onto the console. Everything here is reproducible on a laptop using QEMU, so there is no need for physical hardware to follow along.
zImage / uImage / Image
machine ID
U-Boot bootm/bootz
free linux kernel development course
What You Will Learn
- How a board’s default configuration (defconfig) turns into a bootable kernel image
- The difference between zImage, uImage, and the newer Image/Image.gz formats
- How a bootloader like U-Boot hands control to the kernel on non-device-tree boards
- How to read the first lines of kernel boot output to confirm the correct board was detected
- A reproducible way to build and boot a kernel entirely inside QEMU
Prerequisites
- A basic understanding of Kconfig and defconfig files (covered earlier in this free embedded systems course)
- Familiarity with cross-compiling using ARCH and CROSS_COMPILE, from the Kbuild lecture
- A Linux host with an ARM cross-compiler and QEMU installed
From Defconfig To A Bootable Image
Every supported board ships a default configuration file under arch/<architecture>/configs/. Running the matching defconfig target seeds .config with sane defaults for that platform, which you can then adjust with menuconfig if needed.
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) zImage
The build produces a compressed kernel image whose exact name depends on the architecture and bootloader convention. On most modern targets you will meet one of three names:
| Image Type | Typical Architecture | Notes |
|---|---|---|
| zImage | 32-bit ARM | Self-decompressing image, loaded directly by most bootloaders |
| uImage | 32-bit ARM, legacy targets | zImage wrapped with a U-Boot header via mkimage |
| Image / Image.gz | 64-bit ARM (arm64) | Raw or gzip-compressed kernel image, used with EFI or U-Boot’s booti |
If your bootloader expects a uImage, wrap the zImage with the standard header-adding tool, telling it the load and entry addresses your board’s memory map expects.
$ mkimage -A arm -O linux -T kernel -C none \
-a 0x80008000 -e 0x80008000 \
-n "Board Kernel" -d arch/arm/boot/zImage uImage
→
.config
→
zImage
→
mkimage (if needed)
→
Bootloader Hand-off
How The Bootloader Finds The Right Board
Boards that describe themselves with a device tree pass a pointer to the DTB and the kernel walks it to identify the hardware; that path was covered in the previous lecture. Older, non-device-tree boards instead rely on a numeric machine ID that the bootloader passes in a CPU register before jumping into the kernel. The kernel compares that number against the machine IDs it was built to support and only continues booting if it finds a match.
On U-Boot this number traditionally lived in a generated header, kept in sync with the kernel’s own machine ID table, and boards were built with a matching CONFIG_MACH_TYPE-style setting so the two trees agreed on the same number. In modern kernels this legacy, non-device-tree path only survives for a shrinking set of older SoCs — nearly every actively maintained board today boots through a device tree, and that is the approach you should default to for new hardware.
Verifying A Successful Boot
Whichever path a board takes, the first handful of console lines tell you immediately whether the hand-off worked. You are looking for three things: a kernel version banner confirming the correct build reached the board, a CPU identification line, and a line naming the machine or device tree that was matched.
Let’s prove this end to end without any physical board, using QEMU’s built-in Versatile Express model:
$ qemu-system-arm -M vexpress-a9 -smp 1 -m 512M \
-kernel arch/arm/boot/zImage \
-dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-append "console=ttyAMA0,115200 root=/dev/ram rdinit=/bin/sh" \
-nographic
Expected first lines on the console:
Booting Linux on physical CPU 0x0
Linux version 6.x.x (ep-build@host) ...
CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7)
Machine model: ARM Versatile Express
Seeing the version banner and a machine model line here plays exactly the same role as the Machine: line on a legacy non-device-tree board — it is the kernel telling you it recognised the hardware it was handed.
Common Mistakes And Troubleshooting
- Boot hangs immediately after “Starting kernel…”: almost always a mismatched machine ID or a DTB that does not match the board — the kernel never got past the hardware-identification check.
- “Unable to mount root fs” after a clean version banner: the board was recognised correctly; the problem lies in the root filesystem stage, not the porting stage — a topic for the next chapter.
- Wrong image type loaded: a bootloader expecting a uImage that is handed a raw zImage (or vice versa) will typically refuse to load it or produce garbage output; double-check the wrapping step.
- Forgetting console= in bootargs: the kernel may boot perfectly but appear completely silent because no console device was specified.
Best Practices
- Prefer device tree over legacy machine-ID boards for anything new — it is the actively maintained path upstream.
- Use
make savedefconfigafter tweaking a config to keep the committed defconfig minimal and reviewable. - Script your build invocation (ARCH, CROSS_COMPILE, defconfig name, image target) so it is reproducible by teammates and CI.
- Keep the load/entry addresses used with
mkimageunder version control alongside your board’s memory map documentation.
Security Considerations
If your bootloader supports verified boot, sign the kernel image and enforce signature checking before bootm/booti loads it — an unsigned image accepted from any boot medium is a common attack surface on embedded devices that ship with physical port access.
Summary And Key Takeaways
- A defconfig plus a source tree build produces a zImage, uImage, or Image depending on architecture and bootloader convention.
- Non-device-tree boards are matched by a numeric machine ID agreed between kernel and bootloader; device tree boards are matched by a compatible string instead, and are the preferred path today.
- The first console lines after “Starting kernel…” are your fastest diagnostic signal for whether porting succeeded.
- QEMU lets you rehearse this entire flow without physical hardware.
FAQ
What is the difference between zImage and uImage?
A zImage is a self-decompressing raw kernel image. A uImage is the same payload wrapped with a small U-Boot header that records load address, entry point, and image type so U-Boot’s bootm command can load it directly.
Do 64-bit ARM boards still use zImage?
No, arm64 uses Image or Image.gz, loaded with U-Boot’s booti command or via UEFI, not the 32-bit zImage/uImage format.
Why did my board hang right after the bootloader handed off?
The most common cause is a mismatched machine ID (legacy boards) or an incorrect/missing device tree blob (modern boards) — the kernel could not identify the hardware it was given.
Is the legacy machine-ID approach still relevant to learn?
It is worth understanding conceptually since some older, still-deployed embedded products use it, but for any new board bring-up, device tree is the actively maintained and recommended approach.
Can I test board porting concepts without real hardware?
Yes — QEMU’s machine models (like vexpress-a9) let you build, boot, and inspect console output exactly as you would on real silicon.
What does a clean version banner actually confirm?
It confirms the bootloader successfully loaded and jumped into your kernel image, and that the kernel’s early hardware-identification check passed.
Where do load and entry addresses for mkimage come from?
They come from your board’s memory map — typically the start of RAM plus a small offset defined by the bootloader’s own conventions for that SoC family.
Continue The Free Linux Kernel Development Course
Next, we look at what the kernel still needs after a successful boot: a root filesystem.
