22-QEMU 实验总览

22.1 为什么 QEMU 能替代开发板

书上需要的能力 真实开发板 QEMU 对应
启动一个 ARM/x86 内核 烧写 SD 卡/UART -kernel Image -append ...
串口控制台 UART 转 USB -nographic + console=ttyAMA0
字符设备/平台设备 板载外设 platform_device + simple-bus
I²C 总线与芯片 硬件 I²C + EEPROM i2c-stub 、virtio-i2c
SPI 总线与芯片 硬件 SPI + Flash spi-gpio + gpio-sim 、virtio-spi
GPIO 板载 GPIO gpio-sim 、gpio-virtuser、virtio-gpio
中断控制器 GIC/PLIC QEMU virt 内置 GIC/PLIC
网络 板载网卡 virtio-net
块设备 eMMC/NAND virtio-blk、-drive
USB 主机 USB 控制器 -device qemu-xhci
USB 设备(Gadget) USB 从控制器 dummy_hcd + configfs
DMA 板载 DMA dmatest、阅读 dw-dma
时钟/电源管理 PMIC QEMU virt 的 cpufreq/cpuidle 有限支持
内核源码级调试 JTAG -s -S + gdbstub

结论 :除了少数"真机才有"的细节(真实时序、电压、EMC), 书上的每一个知识点都能在 QEMU 上复现。

22.2 环境准备

22.2.1 宿主依赖

bash 复制代码
sudo apt update
sudo apt install -y \
  build-essential git bc bison flex libssl-dev libelf-dev libncurses-dev \
  pahole cpio rsync kmod udev \
  qemu-system-x86 qemu-system-arm qemu-utils \
  gdb gdb-multiarch device-tree-compiler \
  busybox-static e2fsprogs dosfstools \
  i2c-tools spi-tools gpiod libgpiod-dev \
  python3 perl tar xz-utils

版本建议 :QEMU ≥ 8.0(virtio-gpio/virtio-spi 需要较新版本)。

bash 复制代码
qemu-system-x86_64 --version
qemu-system-x86_64 -device help | grep -E 'virtio|i2c|spi|gpio|usb'
qemu-system-aarch64 -machine virt -device help | grep -E 'virtio|spi|gpio'

22.2.2 环境变量(所有实验共用)

bash 复制代码
export KSRC=~works/study/linux-7.2.5
export KBUILD=~works/study/build/linux-7.2.5-x86_64
export KBUILD_ARM=~works/study/build/linux-7.2.5-arm64
export LAB=~works/study/qemu-lab
mkdir -p "$LAB"

注意 :构建产物一律放到源码树之外 (O=),保持源码树干净, 也便于同时保留 x86_64 和 arm64 两套配置。

22.3 编译内核

22.3.1 x86_64(主力实验平台)

bash 复制代码
mkdir -p "$KBUILD"

# 1. 基础配置
make -C "$KSRC" O="$KBUILD" x86_64_defconfig

# 2. 合并 KVM guest 配置(串口/virtio 等)
"$KSRC/scripts/kconfig/merge_config.sh" -O "$KBUILD" \
    "$KBUILD/.config" "$KSRC/kernel/configs/kvm_guest.config"

# 3. 合并调试配置(可选,但强烈建议学习期打开)
"$KSRC/scripts/kconfig/merge_config.sh" -O "$KBUILD" \
    "$KBUILD/.config" "$KSRC/kernel/configs/debug.config"

# 4. 应用并交互调整
make -C "$KSRC" O="$KBUILD" olddefconfig
make -C "$KSRC" O="$KBUILD" menuconfig

# 5. 编译
make -C "$KSRC" O="$KBUILD" -j"$(nproc)"

产物:

文件 说明
$KBUILD/arch/x86/boot/bzImage 可引导内核
$KBUILD/vmlinux 带符号的内核(GDB 用)
$KBUILD/System.map 符号表
$KBUILD/.config 最终配置

22.3.2 arm64(总线/设备树实验平台)

bash 复制代码
mkdir -p "$KBUILD_ARM"
make -C "$KSRC" O="$KBUILD_ARM" ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- virt.config
make -C "$KSRC" O="$KBUILD_ARM" ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
make -C "$KSRC" O="$KBUILD_ARM" ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j"$(nproc)" Image dtbs

内核自带 arch/arm64/configs/virt.config ,专为 QEMU virt 机器准备, 是最省事的起点。

产物 :$KBUILD_ARM/arch/arm64/boot/Image、.../dts/.../*.dtb。

22.3.3 必开的实验配置项

bash 复制代码
# 基础
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_TMPFS=y
CONFIG_PROC_FS=y
CONFIG_SYSFS=y
CONFIG_DEBUG_FS=y
CONFIG_CONFIGFS_FS=y

# 调试
CONFIG_DYNAMIC_DEBUG=y
CONFIG_FTRACE=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
CONFIG_MAGIC_SYSRQ=y

# 文件系统共享
CONFIG_9P_FS=y
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y

# 虚拟设备
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_CONSOLE=y
CONFIG_VIRTIO_INPUT=y

# 总线实验
CONFIG_I2C=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_STUB=m
CONFIG_EEPROM_AT24=m
CONFIG_REGMAP_I2C=y
CONFIG_SPI=y
CONFIG_SPI_SPIDEV=y
CONFIG_SPI_GPIO=y
CONFIG_GPIO_SIM=m
CONFIG_GPIO_VIRTUSER=m

# USB 实验
CONFIG_USB_GADGET=y
CONFIG_USB_DUMMY_HCD=y
CONFIG_USB_CONFIGFS=y
CONFIG_USB_CONFIGFS_ACM=y
CONFIG_USB_CONFIGFS_MASS_STORAGE=y
CONFIG_USB_CONFIGFS_F_FS=y
CONFIG_USB_MON=y

22.4 制作 initramfs(最简 rootfs)

initramfs 是最简单、最可靠的 QEMU rootfs:一个 cpio 归档,内核启动后解到 tmpfs。

bash 复制代码
mkdir -p "$LAB/initramfs"/{bin,sbin,etc,proc,sys,dev,tmp,lib,root}
cd "$LAB/initramfs"

# 用静态 busybox 提供所有基本命令
cp /bin/busybox bin/
for cmd in sh ls cat mount umount echo dmesg modprobe insmod rmmod \
           mkdir mknod sleep ps ifconfig ip mdev find grep head tail \
           printf dd hexdump vi; do
    ln -sf busybox "bin/$cmd"
done

cat > init <<'EOF'
#!/bin/sh
mount -t proc  none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev 2>/dev/null
mount -t tmpfs none /tmp
mount -t debugfs none /sys/kernel/debug 2>/dev/null
mount -t configfs none /sys/kernel/config 2>/dev/null
echo "=== initramfs booted: $(uname -r) ==="
exec /bin/sh
EOF
chmod +x init

find . -print0 | cpio --null -ov --format=newc | gzip -9 > "$LAB/initramfs.cpio.gz"

验证:

bash 复制代码
qemu-system-x86_64 -kernel "$KBUILD/arch/x86/boot/bzImage" \
  -initrd "$LAB/initramfs.cpio.gz" \
  -append "console=ttyS0 rdinit=/init" \
  -nographic -no-reboot

22.5 启动脚本(x86_64)

bash 复制代码
cat > "$LAB/run-x86.sh" <<'EOF'
#!/bin/bash
set -e
KSRC=~works/study/linux-7.2.5
KBUILD=~works/study/build/linux-7.2.5-x86_64
LAB=~works/study/qemu-lab
DISK=$LAB/disk.img

[ -f "$DISK" ] || truncate -s 256M "$DISK"

qemu-system-x86_64 \
  -m 1G -smp 2 \
  -kernel "$KBUILD/arch/x86/boot/bzImage" \
  -initrd "$LAB/initramfs.cpio.gz" \
  -append "console=ttyS0 root=/dev/ram rdinit=/init loglevel=7" \
  -drive file="$DISK",format=raw,if=virtio \
  -netdev user,id=n0 -device virtio-net-pci,netdev=n0 \
  -device qemu-xhci,id=xhci \
  -device usb-kbd,bus=xhci.0 \
  -nographic -no-reboot "$@"
EOF
chmod +x "$LAB/run-x86.sh"

常用附加参数(按需加到脚本或命令行):

参数 用途
-s -S 开放 gdbstub 并在启动时暂停(第 21 章)
-smp 4 4 核(SMP/热插拔实验)
-device virtio-gpio-pci 虚拟 GPIO(若 QEMU 支持)
-device virtio-i2c-pci 虚拟 I²C
-device usb-storage,... USB 存储
-virtfs local,path=$SHARE,mount_tag=host0,security_model=none 9p 共享目录
-dtb my.dtb 自定义设备树(arm64)

22.6 启动脚本(arm64 / 设备树)

bash 复制代码
cat > "$LAB/run-arm64.sh" <<'EOF'
#!/bin/bash
set -e
KSRC=~works/study/linux-7.2.5
KBUILD=~works/study/build/linux-7.2.5-arm64
LAB=~works/study/qemu-lab

qemu-system-aarch64 \
  -machine virt,dumpdtb="$LAB/virt.dtb" \
  -cpu cortex-a57 -smp 2 -m 1G \
  -kernel "$KBUILD/arch/arm64/boot/Image" \
  -initrd "$LAB/initramfs.cpio.gz" \
  -append "console=ttyAMA0 rdinit=/init loglevel=7" \
  -netdev user,id=n0 -device virtio-net-pci,netdev=n0 \
  -nographic -no-reboot "$@"
EOF
chmod +x "$LAB/run-arm64.sh"

# 反编译 QEMU 生成的设备树(第 18 章实验)
dtc -I dtb -O dts -o "$LAB/virt.dts" "$LAB/virt.dtb"

22.7 知识点 → QEMU 手段总表

章 知识点 QEMU 手段 关键命令/文件
1 Hello World 模块 任意 guest insmod hello.ko、dmesg
1 LED 驱动 gpio-sim / gpio-leds /sys/class/leds/
2 硬件基础 QEMU 参数 -m、-smp、info mtree
3 内核编译 O= 构建 make -C $KSRC O=$KBUILD
4 内核模块 外部模块 make -f $KDIR/Makefile M=$PWD
5 设备文件/udev devtmpfs /dev/、mdev
6 字符设备 任意 guest cdev、/dev/globalmem
7 并发控制 -smp 4 mutex/spinlock
8 阻塞/非阻塞 IO 任意 guest poll/epoll
9 异步通知/AIO 任意 guest SIGIO、io_uring
10 中断与时钟 QEMU 定时器 /proc/interrupts
11 内存与 IO ioremap、DMA /proc/iomem
12 驱动架构 platform/simple-bus /sys/bus/platform/
13 块设备 virtio-blk -drive if=virtio
14 网络设备 virtio-net ip link、ping
15 I²C i2c-stub 、virtio-i2c i2cdetect、/dev/i2c-0
16 USB qemu-xhci + dummy_hcd lsusb、configfs
17 SPI spi-gpio+gpio-sim /dev/spidev0.0
18 设备树 -machine virt,dumpdtb= /proc/device-tree/
19 电源管理 QEMU cpufreq/cpuidle /sys/.../cpufreq/
20 芯片级移植 QEMU virt clk_summary、gpioinfo
21 调试 -s -S + gdbstub hbreak、lx-dmesg

22.8 完整实验:从零到 I²C 读写

bash 复制代码
# ==== 宿主机 ====
export KSRC=~works/study/linux-7.2.5
export KBUILD=~works/study/build/linux-7.2.5-x86_64
export LAB=~works/study/qemu-lab

# 1. 编译内核(见 22.3.1)
# 2. 制作 initramfs(见 22.4)
# 3. 启动(见 22.5)
"$LAB/run-x86.sh"

# ==== guest 内 ====
modprobe i2c-stub chip_addr=0x50
modprobe i2c-dev
i2cdetect -l
i2cdetect -y 0
i2cset -y 0 0x50 0x00 0x11
i2cget -y 0 0x50 0x00
dmesg | tail

22.9 故障排查

症状 原因 解决
无任何输出 控制台名错 x86 用 ttyS0,arm64 用 ttyAMA0
VFS: Unable to mount root 无 rootfs 加 -initrd 和 rdinit=/init
Kernel panic - not syncing: No init found init 路径错 检查 rdinit= 与 initramfs 里的 init
模块 insmod 报 Invalid module format 内核与模块版本/配置不一致 用同一 KBUILD 编译模块
modprobe 找不到模块 无 /lib/modules 用 insmod 绝对路径,或建 /lib/modules/$(uname -r)/
i2cdetect 报无设备 i2c-stub 未加载/总线号错 lsmod、i2cdetect -l
gpio-sim 无芯片 configfs 未挂/未 live mount -t configfs,echo 1 > live
GDB 断点无效 KASLR 加 nokaslr,用 hbreak
GDB 符号不对 用了错误的 vmlinux 必须用同一 KBUILD 的 vmlinux
dummy_hcd 无 /sys/class/udc 配置未开 CONFIG_USB_DUMMY_HCD=y
QEMU 无 virtio-i2c QEMU 版本旧 降级到 i2c-stub 方案
编译缺 pahole BTF 需要 apt install pahole
bison/flex 缺失 配置解析需要 apt install bison flex
磁盘空间不足 构建产物大 只保留一套配置,或 make clean

调试启动问题的三板斧:

  1. 加 earlycon(第 20.5 节)看最早输出。
  2. 加 loglevel=7 ignore_loglevel 看全部日志。
  3. 用 -s -S + GDB 单步(第 21.9 节)。

22.10 本章实验清单

实验 目的 关键观察
编译 x86_64 内核 掌握构建 bzImage/vmlinux
编译 arm64 内核 掌握交叉编译 Image/dtb
制作 initramfs 掌握 rootfs guest 进入 shell
启动脚本 掌握 QEMU 参数 uname -r 正确
9p 共享目录 掌握文件交换 /mnt/host 可见
I²C 全流程 综合实验 i2cget 读到数据
gpio-sim 综合实验 gpioinfo 有芯片
dummy_hcd 综合实验 /sys/class/udc/
GDB 调试内核 综合实验 断点命中

22.11 总结

  1. QEMU 可以替代开发板完成本书全部知识点,只有极少数真机细节例外。
  2. 统一用 O= 外部构建,保持源码树干净,并可同时保留多套配置。
  3. initramfs 是最简可靠的 rootfs:一个 cpio 归档 + 静态 busybox。
  4. x86_64 用 x86_64_defconfig + kvm_guest.config;arm64 用 virt.config。
  5. i2c-stub 、gpio-sim 、spi-gpio 、dummy_hcd 是四条"凭空造硬件"的关键路径。
  6. -s -S + gdbstub 让内核调试达到源码级单步的水平。
  7. 排错三板斧:earlycon、loglevel=7、GDB 单步。
  8. 本章的启动脚本和配置片段可直接复制使用,是所有后续实验的基础设施。
相关推荐
Snasph3 小时前
13-Linux 块设备驱动
linux 驱动学习
Snasph5 小时前
15- Linux I2C 核心、总线与设备驱动
linux 驱动学习
Snasph5 小时前
附录-API 速查与术语表
linux 驱动学习
Snasph6 小时前
11-内存与 I/O 访问
linux 驱动学习
Snasph7 小时前
14-Linux 网络设备驱动
linux 驱动学习
Snasph8 小时前
18-ARM Linux 设备树
linux 驱动学习
Snasph9 小时前
20-Linux 芯片级移植及底层驱动
linux 驱动学习
Snasph9 小时前
12- Linux 设备驱动的软件架构思想
linux 驱动学习
Snasph10 小时前
23-从 Linux 4.0 到 7.2.5 的迁移
linux 驱动学习