2. BootROM阶段启动失败定位与串口日志分析

任务目标

在本案例中,我们将使用QEMU模拟一台ARM Versatile Express开发板(vexpress-a9),为其安装U-Boot引导加载程序、Linux内核以及最小根文件系统,最终通过QEMU启动并观察完整的引导流程,为后续案例中分析车载Linux启动过程搭建可重复的实验环境。

环境准备

需要一台x86_64 Linux主机(Ubuntu 22.04+或Debian 12+),安装以下软件包:

sudo apt update
sudo apt install -y qemu-system-arm gcc-arm-linux-gnueabihf build-essential flex bison libssl-dev libncurses-dev u-boot-tools

实现步骤与代码

1. 编译U-Boot

# 下载U-Boot源码(版本2023.04)
wget https://ftp.denx.de/pub/u-boot/u-boot-2023.04.tar.bz2
tar xf u-boot-2023.04.tar.bz2
cd u-boot-2023.04

# 配置并编译vexpress-a9目标
make vexpress_ca9x4_defconfig
make CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)

# 编译产物:u-boot(ELF格式)和 u-boot.bin(裸二进制)
ls -l u-boot.bin

2. 编译Linux内核

# 下载Linux内核源码(版本6.1 LTS)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.76.tar.xz
tar xf linux-6.1.76.tar.xz
cd linux-6.1.76

# 使用vexpress默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig

# 编译内核镜像(仅编译内核,不编译模块以节省时间)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) zImage

# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) dtbs

# 确认产物
ls -l arch/arm/boot/zImage
ls -l arch/arm/boot/dts/vexpress-v2p-ca9.dtb

3. 制作最小根文件系统

# 创建根文件系统目录结构
mkdir -p rootfs/{bin,sbin,etc,proc,sys,dev,lib,usr/bin,usr/sbin}
cd rootfs

# 复制busybox静态二进制(需预先安装busybox-static)
cp /bin/busybox ./bin/busybox
# 创建busybox符号链接
for cmd in sh ls cat mount umount ps echo; do
    ln -sf /bin/busybox bin/$cmd
done

# 创建init脚本
cat > init << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "=== Minimal RootFS Booted ==="
echo "QEMU ARM Linux environment ready"
exec /bin/sh
EOF
chmod +x init

# 打包为ext2镜像(大小32MB)
cd ..
dd if=/dev/zero of=rootfs.img bs=1M count=32
mkfs.ext2 -F rootfs.img
sudo mkdir /mnt/rootfs
sudo mount -o loop rootfs.img /mnt/rootfs
sudo cp -r rootfs/* /mnt/rootfs/
sudo umount /mnt/rootfs

4. 启动QEMU验证

# 启动QEMU,使用U-Boot作为引导程序
qemu-system-arm \
    -M vexpress-a9 \
    -m 256M \
    -kernel u-boot-2023.04/u-boot.bin \
    -nographic \
    -sd rootfs.img \
    -append "console=ttyAMA0"

在U-Boot命令行中依次输入以下命令加载内核和设备树:

# 在U-Boot提示符下执行
# 加载内核到内存地址0x60003000
fatload mmc 0:0 0x60003000 zImage
# 加载设备树到内存地址0x60002000
fatload mmc 0:0 0x60002000 vexpress-v2p-ca9.dtb
# 设置启动参数
setenv bootargs 'console=ttyAMA0 root=/dev/mmcblk0 rw'
# 启动内核
bootz 0x60003000 - 0x60002000

由于SD卡镜像中未包含FAT分区,上述fatload命令会失败。正确做法是将内核和设备树直接编译进U-Boot,或使用TFTP加载。为简化流程,我们改用直接内核启动方式验证根文件系统:

# 退出QEMU(Ctrl+A X),改用直接内核启动
qemu-system-arm \
    -M vexpress-a9 \
    -m 256M \
    -kernel linux-6.1.76/arch/arm/boot/zImage \
    -dtb linux-6.1.76/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
    -sd rootfs.img \
    -append "console=ttyAMA0 root=/dev/mmcblk0 rw" \
    -nographic

验证方法与判定标准

启动后观察串口输出,满足以下条件即判定通过:

  1. 内核启动日志完整输出,无Kernel Panic或无法挂载根文件系统的致命错误;
  2. 出现VFS: Mounted root (ext2 filesystem) on device 179:0.字样,表明根文件系统挂载成功;
  3. init脚本执行后打印=== Minimal RootFS Booted ===,并进入交互式shell;
  4. 在shell中执行ls /proc和ls /sys能看到proc和sysfs已挂载的内容。

U-Boot引导流程说明

U-Boot完整引导需要将内核和设备树放入SD卡的FAT分区。若需验证U-Boot引导,可执行以下额外步骤:

# 创建FAT分区镜像并拷贝内核和设备树
dd if=/dev/zero of=boot.img bs=1M count=16
mkfs.vfat boot.img
sudo mkdir /mnt/boot
sudo mount -o loop boot.img /mnt/boot
sudo cp linux-6.1.76/arch/arm/boot/zImage /mnt/boot/
sudo cp linux-6.1.76/arch/arm/boot/dts/vexpress-v2p-ca9.dtb /mnt/boot/
sudo umount /mnt/boot

# 将FAT镜像和根文件系统镜像合并为SD卡镜像
dd if=/dev/zero of=sd.img bs=1M count=64
# 创建分区表(使用fdisk或sgdisk),第一个分区FAT32,第二个分区ext2
# 然后将boot.img写入第一个分区,rootfs.img写入第二个分区

# 启动QEMU
qemu-system-arm \
    -M vexpress-a9 \
    -m 256M \
    -kernel u-boot-2023.04/u-boot.bin \
    -sd sd.img \
    -nographic \
    -append "console=ttyAMA0"

在U-Boot提示符下执行:

fatload mmc 0:1 0x60003000 zImage
fatload mmc 0:1 0x60002000 vexpress-v2p-ca9.dtb
setenv bootargs 'console=ttyAMA0 root=/dev/mmcblk0p2 rw'
bootz 0x60003000 - 0x60002000

观察U-Boot打印的版本信息、内存初始化、MMC设备识别等日志,随后跳转到内核入口地址,完成引导流程。

常见问题

  • 编译错误:若编译U-Boot或内核时提示缺少头文件,安装libssl-dev和libncurses-dev后重试。
  • QEMU无输出:确认使用-nographic参数且console=ttyAMA0与vexpress-a9的串口配置匹配。
  • 根文件系统挂载失败:检查root=/dev/mmcblk0参数是否正确,以及rootfs.img是否为ext2格式且包含init脚本。