QEMU 嵌入式Linux环境完整搭建教程
文章摘要: 本文详细介绍了嵌入式Linux系统的完整构建流程,从基础环境配置到最终系统启动的全过程。主要内容包括:U-Boot引导程序的编译与配置修改,Linux内核与设备树的交叉编译,BusyBox根文件系统的构建方法,以及U-Boot启动脚本的编写。文章特别强调了开发环境目录结构的规划建议,并深入解析了各步骤中的关键命令和配置参数,如交叉编译工具链的选择、文件系统软链接处理的注意事项、设备节点
目录与环境规划
为了确保全流程路径一致,强烈建议在宿主机中创建一个统一的工作空间。以下为完整的工程目录预览(此处仅为结构展示,无需手动创建):
/home/cp/Linux/
├── u-boot-2022.04/ # U-Boot 源码目录
├── linux-5.15.148/ # Linux 内核源码目录
├── busybox-1.36.1/ # BusyBox 源码目录
├── rootfs/ # 构建好的根文件系统目录, 用于nfs挂载
├── boot.cmd # U-Boot 启动脚本源文件
└── sdcard.img # 最终生成的虚拟 SD 卡镜像
📌 系统架构与启动流向说明
本环境模拟真实的工业界开发板固化发布流程,系统启动链路如下:
-
QEMU (硬件加电) -> 加载
u-boot(Bootloader) -
U-Boot -> 读取 SD 卡第一分区 (FAT32) -> 解析并执行
boot.scr -
boot.scr -> 将
zImage(内核) 和.dtb(设备树) 载入物理内存 -> 交出控制权 -
Linux Kernel -> 解析设备树 -> 挂载 SD 卡第二分区 (EXT4) -> 执行
init进程
🛠️ 第一步:基础环境依赖安装
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf qemu-system-arm \
build-essential libncurses5-dev bison flex libssl-dev libc6-dev \
u-boot-tools dosfstools parted
【核心依赖解析】
-
gcc-arm-linux-gnueabihf:针对 ARM 32位硬浮点架构的交叉编译器。 -
u-boot-tools:提供mkimage命令,用于将纯文本的启动脚本编译为 U-Boot 可执行的二进制包。 -
dosfstools:提供mkfs.vfat命令,这是格式化 SD 卡第一分区(Boot区)所必须的。 -
parted:提供大容量磁盘的非交互式分区能力,比fdisk更适合脚本化。
🛠️ 第二步:编译 U-Boot (Bootloader)
cd /home/cp/Linux
wget https://ftp.denx.de/pub/u-boot/u-boot-2022.04.tar.bz2
tar -xjvf u-boot-2022.04.tar.bz2
cd u-boot-2022.04
修改底层环境变量以劫持启动逻辑:
nano include/configs/vexpress_common.h
找到 #define CONFIG_EXTRA_ENV_SETTINGS \,覆盖为以下内容:
#define CONFIG_EXTRA_ENV_SETTINGS \
"kernel_addr_r=0x60100000\0" \
"fdt_addr_r=0x60000000\0" \
"bootargs=console=tty0 console=ttyAMA0,38400n8\0" \
BOOTENV \
"console=ttyAMA0,38400n8\0" \
"dram=1024M\0" \
"root=/dev/sda1 rw\0" \
"mtd=armflash:1M@0x800000(uboot),7M@0x1000000(kernel)," \
"24M@0x2000000(initrd)\0" \
"flashargs=setenv bootargs root=${root} console=${console} " \
"mem=${dram} mtdparts=${mtd} mmci.fmax=190000 " \
"devtmpfs.mount=0 vmalloc=256M\0" \
"bootflash=run flashargs; " \
"cp ${ramdisk_addr} ${ramdisk_addr_r} ${maxramdisk}; " \
"bootm ${kernel_addr} ${ramdisk_addr_r}\0" \
/* 新增内容 scriptaddr */ \
"scriptaddr=0x62000000\0" \
"bootcmd=echo Loading boot.scr...; load mmc 0:1 ${scriptaddr} boot.scr; source ${scriptaddr}\0" \
"fdtfile=" CONFIG_DEFAULT_FDT_FILE "\0"
【宏定义解析】
-
scriptaddr=0x62000000:在 RAM 中划出一块绝对安全的物理地址,用于存放马上要从 SD 卡读取的boot.scr脚本。 -
load mmc 0:1:底层命令,指示 U-Boot 驱动第 0 号 MMC 设备(SD卡)的第 1 个分区,将其内容读入内存。 -
source:执行该内存地址中的脚本。
配置uboot链接地址为0x67800000:(不改也行)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_ca9x4_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

编译执行:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
🛠️ 第三步:编译 Linux 内核与设备树
cd /home/cp/Linux
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.148.tar.xz
tar -xf linux-5.15.148.tar.xz
cd linux-5.15.148
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs modules -j$(nproc)
【命令解析】
-
zImage:生成自解压的内核压缩包,体积小,符合嵌入式存储受限的需求。 -
dtbs:编译 Device Tree Blob,将人类可读的.dts硬件描述文本编译为机器码。 -
modules:编译内核配置中被标记为<M>的可动态加载驱动模块。
🛠️ 第四步:构建根文件系统 (Rootfs)
cd /home/cp/Linux
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xf busybox-1.36.1.tar.bz2 && cd busybox-1.36.1
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
进入
Settings-> 勾选Build static binary (no shared libs)。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- install -j$(nproc)
mkdir -p ../rootfs
cp -r _install/* ../rootfs/
cd ../rootfs
mkdir -p dev etc/init.d lib proc sys tmp root var mnt
# 拷贝交叉编译器动态库
sudo cp -P /usr/arm-linux-gnueabihf/lib/* lib/
【命令解析】
cp -P:这是构建文件系统最容易踩坑的地方。-P参数强制拷贝操作保留软链接 (Symlinks)。如果不加-P,所有软链接会被解析为实体文件,不仅会导致文件系统体积暴增,还会破坏 C 语言运行库的内部依赖结构,导致编译的应用程序在开发板上报Not found错误。
配置系统初始化脚本:
cat << 'EOF' > etc/inittab
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
EOF
cat << 'EOF' > etc/init.d/rcS
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s
EOF
chmod 755 etc/init.d/rcS
【脚本解析】
-
devtmpfs:内核提供的一种机制,可以在/dev目录下自动动态生成当前系统识别到的硬件设备节点文件。 -
mdev -s:BusyBox 提供的 udev 缩减版,配合 hotplug 机制,用于在系统运行时响应硬件的热插拔事件。
🛠️ 第五步:制作 U-Boot 启动脚本 (boot.scr)
cd /home/cp/Linux
cat << 'EOF' > boot.cmd
setenv bootargs 'console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rw rootwait'
echo "Loading Kernel and DTB from FAT32 Boot Partition..."
fatload mmc 0:1 0x60008000 zImage
fatload mmc 0:1 0x61000000 vexpress-v2p-ca9.dtb
bootz 0x60008000 - 0x61000000
EOF
mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n "Real Env Boot Script" -d boot.cmd boot.scr
【命令解析】
-
rootwait:关键参数。指示内核在启动时无限期挂起等待,直到/dev/mmcblk0p2(SD卡) 硬件驱动初始化完成并就绪。若缺失此参数,内核在启动初期可能因找不到磁盘而直接 Panic。 -
mkimage:-
-A arm -O linux:指定目标架构与操作系统。 -
-T script -C none:指定类型为脚本文件,不使用数据压缩。 -
-d boot.cmd boot.scr:将文本boot.cmd包装上 64 字节的 U-Boot 识别头,输出为boot.scr。
-
🛠️ 第六步:制作双分区虚拟 SD 卡镜像
dd if=/dev/zero of=sdcard.img bs=1M count=512
parted -s sdcard.img mklabel msdos
parted -s sdcard.img mkpart primary fat32 1MiB 100MiB
parted -s sdcard.img set 1 boot on
parted -s sdcard.img mkpart primary ext4 100MiB 100%
【命令解析】
-
dd:以字节级别直接操作系统磁盘,此处生成 512MB 的全零文件。 -
parted -s:以非交互模式 (script) 执行分区。-
mklabel msdos:创建传统的 MBR (Master Boot Record) 分区表结构。 -
1MiB:强制第一个分区从 1MB 地址开始,以保证磁道对齐,提升磁盘 I/O 性能。
-
# 挂载镜像为回环设备
sudo losetup -Pf sdcard.img
# 查看被分配的 loop 设备编号 (假设输出为 loop0)
lsblk | grep loop
# 格式化分区
sudo mkfs.vfat -F 32 -n "BOOT" /dev/loop0p1
sudo mkfs.ext4 -L "ROOTFS" /dev/loop0p2
# 拷贝数据
mkdir -p /tmp/mnt_boot /tmp/mnt_rootfs
sudo mount /dev/loop0p1 /tmp/mnt_boot
sudo mount /dev/loop0p2 /tmp/mnt_rootfs
sudo cp linux-5.15.148/arch/arm/boot/zImage /tmp/mnt_boot/
sudo cp linux-5.15.148/arch/arm/boot/dts/vexpress-v2p-ca9.dtb /tmp/mnt_boot/
sudo cp boot.scr /tmp/mnt_boot/
sudo cp -a rootfs/* /tmp/mnt_rootfs/
# 同步并清理
sync
sudo umount /tmp/mnt_boot /tmp/mnt_rootfs
sudo losetup -d /dev/loop0
【命令解析】
-
losetup -Pf:-f表示自动寻找第一个空闲的 loop 设备;-P强制内核重新扫描该镜像的分区表,自动生成/dev/loopXp1和/dev/loopXp2子设备节点。 -
cp -a:归档拷贝模式。等同于-dpR,递归拷贝目录,并严格保留所有文件的读写执行权限、属主信息及软链接。(在构建根文件系统时至关重要)。
🛠️ 第七步:启动 QEMU 仿真环境
cat << 'EOF' > run_qemu.sh
#!/bin/bash
# 彻底关闭 QEMU 的音频输出,消除 ALSA 报错刷屏
export QEMU_AUDIO_DRV=none
sudo qemu-system-arm \
-M vexpress-a9 \
-m 1024M \
-kernel u-boot-2022.04/u-boot \
-nographic \
-drive if=sd,format=raw,file=sdcard.img
EOF
chmod +x run_qemu.sh
./run_qemu.sh
【命令解析】
-
-drive if=sd,format=raw,file=sdcard.img:-drive是 QEMU 标准的磁盘挂载指令。-
if=sd:指定接口类型为 SD 总线。 -
format=raw:显式声明镜像格式为原生裸数据块,可避免 QEMU 在启动时因格式推断失败而抛出警告信息。
-
如果需要退出qemu,只需要在终端执行ctrl+A,然后紧接着输入x即可。
更多推荐




所有评论(0)