04 · Running Embedded Linux in QEMU¶
Time to stop reading and start booting. QEMU is a free, open-source machine emulator that can pretend to be a complete ARM board — CPU, RAM, UART, disks — on your ordinary x86 or Apple Silicon laptop. In this module you'll install QEMU, boot a real AArch64 Linux system on an emulated Cortex-A machine, drive it entirely over the serial console (exactly how you talk to a real board's UART), and shut it down cleanly. This QEMU guest is the lab for the rest of Level 1 — modules 5, 6, and the capstone all happen inside it.
Install QEMU¶
qemu-system-arm ships both qemu-system-arm (32-bit) and
qemu-system-aarch64 (64-bit) emulators; qemu-efi-aarch64 provides
the UEFI firmware file /usr/share/qemu-efi-aarch64/QEMU_EFI.fd.
Verify:
Get a Linux image to boot¶
We'll boot Alpine Linux — a tiny (~60 MB), security-oriented distro
whose userland is BusyBox + musl, which makes it a perfect stand-in for
a real embedded rootfs (and a perfect setup for module 5). Alpine publishes
versioned, checksummed images at stable URLs — the virt flavor is built
exactly for VMs:
$ mkdir -p ~/embedded-linux-lab && cd ~/embedded-linux-lab
$ curl -LO https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/aarch64/alpine-virt-3.22.0-aarch64.iso
$ curl -LO https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/aarch64/alpine-virt-3.22.0-aarch64.iso.sha256
Always verify a downloaded image — embedded habit number one:
$ sha256sum -c alpine-virt-3.22.0-aarch64.iso.sha256 # Linux
$ shasum -a 256 -c alpine-virt-3.22.0-aarch64.iso.sha256 # macOS
alpine-virt-3.22.0-aarch64.iso: OK
The expected digest (from the official .sha256 file) is
00b25acf7acc9e5503091c0de657f25a66a93df846a6175664de4ca76d64b6b3.
Boot it¶
What each flag means — this is the vocabulary of every QEMU lab to come:
| Flag | Meaning |
|---|---|
-M virt |
The machine (board) to emulate: virt is QEMU's generic ARM board — UART, virtio devices, no vendor quirks |
-cpu cortex-a53 |
Which core to emulate — A53 is the A55's predecessor, same AArch64 ISA as the i.MX95 |
-smp 2 -m 512M |
2 CPU cores, 512 MB of "DDR" |
-bios ... |
UEFI firmware — the boot-ROM/bootloader stage of module 3, playing the loader role here |
-cdrom ... |
Attach the ISO as a virtual CD |
-nic user,model=virtio-net-pci |
A NAT'd network card — no host config needed |
-nographic |
No window: the guest's serial UART is wired to your terminal — exactly like a real board's serial console |
You'll see the firmware, then GRUB, then a scrolling kernel log — module 3 live in front of you — ending at:
Log in as root — no password (live image). Prove you're on ARM:
localhost:~# uname -m
aarch64
localhost:~# grep -m1 "CPU part" /proc/cpuinfo
CPU part : 0xd03 # 0xd03 = Cortex-A53
localhost:~# free -m | head -2
total used free ...
Mem: 492 50 400 ...
Note the login line says /dev/ttyAMA0 — that's the ARM PL011 UART device.
You are using the serial console, not a screen. On a real i.MX95 board this
same text would arrive over a USB-serial adapter at 115200 baud.
If you get GRUB but then silence
The virt-flavor ISO directs its console to the serial port, so
-nographic just works. If you ever boot an image that goes quiet
after GRUB, press ++e++ at the GRUB menu and append console=ttyAMA0
to the linux line — you're doing bootloader surgery, module-3 style.
Serial-console survival kit¶
With -nographic, your terminal is shared between the guest and QEMU
itself. Three key chords:
| Keys | Effect |
|---|---|
| ++ctrl+a++ then ++x++ | Kill QEMU immediately (emergency exit) |
| ++ctrl+a++ then ++c++ | Toggle the QEMU monitor (type info network, system_powerdown...) — press again to return |
| ++ctrl+a++ then ++h++ | List all Ctrl-A commands |
Shut down cleanly¶
Embedded habit number two: never just kill the power on a writable system. From inside the guest:
QEMU exits and you're back at your host shell. (The live ISO runs from RAM
so it can't be corrupted — but the capstone image will have a writable
disk, so build the habit now.) Ctrl-A x is the fallback if a guest ever
hangs.
Where do 'real' embedded images come from?
Today you booted a distro ISO because it's one download and perfectly
reproducible. Real products don't run distro ISOs — they run purpose-
built images. In module 9 you will build your own bootable kernel +
rootfs from source with Buildroot and boot it with almost the same
QEMU command line (swapping -cdrom for -kernel and a disk image).
Everything you learned here transfers directly.
Cheat sheet¶
| Command / flag | Purpose |
|---|---|
qemu-system-aarch64 |
64-bit ARM system emulator |
-M virt |
Generic ARM virtual board (the standard lab machine) |
-cpu cortex-a53 |
Emulated core (AArch64, i.MX95-adjacent) |
-nographic |
Serial console in your terminal — the embedded way |
-bios QEMU_EFI.fd / edk2-aarch64-code.fd |
AArch64 UEFI firmware (apt: qemu-efi-aarch64) |
-nic user,model=virtio-net-pci |
NAT networking, zero host setup |
sha256sum -c / shasum -a 256 -c |
Verify image integrity before booting |
| ++ctrl+a++ ++x++ / ++ctrl+a++ ++c++ | Kill QEMU / toggle monitor |
poweroff (in guest) |
Clean shutdown |
/dev/ttyAMA0 |
The PL011 serial UART — your console device |
How It Actually Works¶
QEMU's virt machine is not "a fake ARM board" in the sense of imitating a
specific SoC — it's a synthetic platform invented purely so the kernel
has something regular to describe, generated fresh from QEMU's internal
device model each run as a device tree blob and handed to the guest kernel
exactly the way a real bootloader would. That's why -machine virt boots
generic kernels with no board-specific patches: there's no real silicon
underneath to disagree with the description.
Under the hood, QEMU runs your guest one of two fundamentally different
ways depending on host/target match. On matching-architecture hosts (an
Apple Silicon Mac running an AArch64 guest) it uses KVM/HVF hardware
virtualization — guest instructions execute directly on the host CPU with
the hypervisor only trapping privileged operations, which is why this feels
fast. On a mismatched pair (x86 host, ARM guest) QEMU falls back to
TCG (Tiny Code Generator) — it translates each block of guest
instructions into host instructions in software, caches the translation,
and executes that; this is why cross-architecture emulation is measurably
slower and why -cpu max matters: TCG has to know exactly which ARM
instructions to emit code for. The serial console you interact with is
itself emulated hardware (a PL011 UART on virt) wired to your terminal's
stdio — the same UART driver your kernel would use to talk to a real
16550-class chip on physical silicon.
Exercise¶
Boot the guest three more times, changing one thing each time, and write
down what you observe: (1) -smp 4 -m 1024M — confirm the changes from
inside with nproc and free -m; (2) -cpu cortex-a72 — check
CPU part in /proc/cpuinfo again (A72 is 0xd08: you just swapped
microarchitecture without touching the ISA — module 2 made real);
(3) use ++ctrl+a++ ++c++ to enter the QEMU monitor and run
system_powerdown, then quit. Finally, from inside the guest, run
dmesg | grep -i "ttyAMA0" and paste the line that proves the kernel
found the serial console the firmware told it about.