1. 为什么我们需要QEMU模拟ARM开发环境?
如果你对嵌入式开发或者Linux内核感兴趣,可能早就听说过“开发板”这个词。传统的学习路径是:买一块真实的ARM开发板,比如树莓派或者友善之臂的板子,然后连接串口、编译内核、烧写镜像……这个过程虽然正统,但对初学者来说,门槛不低。一块板子几百上千元,接线复杂,每次修改代码都要经历漫长的编译-烧写-重启循环,效率不高,还容易因为硬件问题卡住,打击学习热情。
我自己在早些年就是这么过来的,没少对着板子上的指示灯发呆。后来接触到QEMU,感觉像是打开了一扇新世界的大门。QEMU是一个功能强大的开源机器模拟器,它能在你的x86电脑上,虚拟出另一个完整的计算机系统,包括CPU、内存、各种外设。对于学习来说,这意味着你可以用软件“凭空”造出一块ARM开发板,所有操作都在电脑上完成,编译完内核秒级启动,调试起来也无比方便。
这次,我们就来手把手搭建一个ARM32的模拟开发环境。我们会选用ARM官方设计的Versatile Express(简称vexpress) 开发板作为模拟目标,它架构清晰,文档丰富,非常适合学习。整个流程就像搭积木:准备好交叉编译器(让x86电脑能生成ARM代码的“翻译官”)、编译一个精简的Linux内核、制作一个微型根文件系统(用BusyBox),最后用QEMU把它们组装起来,一键启动。整个过程完全在Ubuntu这类Linux系统下完成,不需要任何实体硬件。
无论你是想学习Linux内核启动流程、驱动开发,还是单纯想了解ARM架构,这个模拟环境都是一个绝佳的、零成本的起点。下面,我们就从安装“积木”开始。
2. 搭建基础工作环境:安装QEMU与交叉编译器
工欲善其事,必先利其器。我们首先需要安装两个核心工具:模拟器QEMU和交叉编译器。
2.1 安装并初探QEMU
在Ubuntu或Debian系统上,安装QEMU非常简单,一条命令搞定:
sudo apt update
sudo apt install qemu-system-arm
安装完成后,可以查一下版本,确认安装成功:
qemu-system-arm --version
我实测的环境是QEMU 4.2.1,版本不同可能会有细微差别,但基本操作一致。
QEMU的强大之处在于它支持海量的硬件模型。我们可以看看它都能模拟哪些ARM开发板:
qemu-system-arm -M help
你会看到一个长长的列表,从经典的vexpress-a9到树莓派raspi2都在其中。这里我们重点关注vexpress-a9和vexpress-a15,它们是我们本次实验的主角。你还可以指定查看某个板子支持哪种CPU:
qemu-system-arm -M vexpress-a9 --cpu help
这个命令会列出该板卡能模拟的所有CPU型号,比如Cortex-A9、Cortex-A7等。这就像你去电脑城配电脑,先选主板(-M),再选插在主板上的CPU(-cpu),非常直观。
2.2 理解并安装ARM交叉编译工具链
我们的宿主机(Host)是x86_64架构的电脑,而目标机(Target)是ARM架构。直接用宿主机上的GCC编译出来的程序,是x86的指令,ARM CPU根本看不懂。这就需要交叉编译工具链——一套运行在x86上,但能生成ARM指令代码的编译工具。
对于ARM 32位系统,常见的交叉编译器有:
- arm-linux-gnueabi:针对软浮点(soft-float)的ARM EABI。
- arm-linux-gnueabihf:针对硬浮点(hard-float)的ARM EABI,性能更好,需要硬件支持。
我们选择安装通用的arm-linux-gnueabi:
sudo apt install gcc-arm-linux-gnueabi
安装后验证一下:
arm-linux-gnueabi-gcc --version
如果能看到版本信息,说明这个“翻译官”已经就位了。它以后就会扮演关键角色,把我们写的C代码“翻译”成ARM能执行的机器码。
3. 编译ARM32 Linux内核:制作系统的“大脑”
内核是操作系统的核心,负责管理硬件、内存和进程。我们将为vexpress开发板编译一个定制内核。
首先,创建一个工作目录并下载内核源码。这里以当时稳定的长期支持版本linux-5.19为例(你可以替换为更新的版本):
mkdir -p ~/qemu_arm32
cd ~/qemu_arm32
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.19.tar.xz
tar -xf linux-5.19.tar.xz
cd linux-5.19
接下来是关键的配置和编译步骤。为了让编译产生的文件不污染源代码目录,我们使用O=build参数指定输出目录。
第一步:导入默认配置。 内核有成千上万个配置项,从头配置非常复杂。幸运的是,内核为每种支持的硬件都提供了默认配置文件(defconfig)。对于vexpress板子,我们使用vexpress_defconfig。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- O=build vexpress_defconfig
这个命令会读取arch/arm/configs/vexpress_defconfig文件,在build目录下生成一个基础的.config文件。
第二步:开始编译。 使用-j参数指定并行编译的线程数,可以大幅加快编译速度(数字根据你CPU的核心数来定,比如8核可以用-j8)。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- O=build -j$(nproc)
编译过程需要一些时间,你可以泡杯茶休息一下。如果一切顺利,最终会看到Kernel: arch/arm/boot/zImage is ready的成功提示。
编译产物解读:
build/vmlinux:原始的、未经压缩的ELF格式内核文件,包含完整的调试符号,主要用于调试。build/arch/arm/boot/zImage:这才是我们需要的内核镜像。它是vmlinux经过压缩和打包后形成的,可以被BootLoader(或QEMU直接)加载并启动。build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb:设备树二进制文件(DTB)。它描述了vexpress-a9这块板子上CPU、内存、外设等硬件的具体布局和连接方式。内核启动时需要读取这个文件来正确初始化硬件。你可以用ls build/arch/arm/boot/dts/vexpress*.dtb看到为不同vexpress子板生成的多个dtb文件。
你可以用file命令查看它们的属性,确认是32位ARM架构:
file build/arch/arm/boot/zImage
# 输出应包含“ARM”字样
4. 使用BusyBox制作initramfs:打造微型根文件系统
内核启动后,需要挂载一个根文件系统(rootfs),才能运行用户空间的程序,比如提供一个命令行shell给我们操作。我们使用BusyBox来制作一个极度精简的根文件系统,并打包成initramfs(一种内存文件系统)的形式,这样QEMU启动时可以直接加载到内存里,不需要额外的磁盘镜像,非常方便。
BusyBox被称为“嵌入式Linux的瑞士军刀”,它把很多常用的Unix工具(如ls, cp, mount, sh)打包进一个单一的可执行文件,通过创建符号链接来提供各种命令,体积非常小。
首先,下载并解压BusyBox源码:
cd ~/qemu_arm32
wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
tar -xf busybox-1.35.0.tar.bz2
cd busybox-1.35.0
配置BusyBox: 我们需要进行两项关键配置。
- 进入配置菜单:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig - 在图形界面中,找到以下选项并修改:
- Settings -> Build Options -> [*] Build static binary (no shared libs):将其选中。这意味着BusyBox会被静态编译,不依赖任何动态库,可以独立运行,简化了根文件系统的制作。
- Shells -> [ ] Job control:将其取消选中。如果不这样做,在QEMU启动后的shell里可能会看到“can‘t access tty; job control turned off”的警告,虽然不影响使用,但去掉更清爽。
配置完成后,保存退出。
编译并安装:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install
安装完成后,会在当前目录下生成_install目录,里面就是我们的微型根文件系统的雏形。
制作initramfs镜像: 现在我们需要完善这个根文件系统,并把它打包。
cd _install
# 创建一些必要的目录
mkdir -p proc sys dev tmp etc
# 创建并编写init脚本,这是系统启动后第一个运行的用户态程序
cat > init << 'EOF'
#!/bin/sh
# 挂载虚拟文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
echo ""
echo "Hello from ARM32 Linux!"
echo "System booted successfully."
echo ""
# 启动一个shell
exec /bin/sh
EOF
# 给init脚本添加执行权限
chmod +x init
# 将当前目录下的所有文件打包成cpio格式,并用gzip压缩
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
现在,initramfs.cpio.gz文件就是我们制作好的、包含BusyBox和启动脚本的内存根文件系统镜像了。
5. 启动!用QEMU模拟vexpress-a9开发板
所有部件都已就绪,是时候让我们的虚拟开发板上电运行了!我们将以无图形界面(串口控制台) 的方式启动,这对于服务器操作和日志查看来说是最清晰的方式。
回到工作目录,执行以下命令:
cd ~/qemu_arm32
qemu-system-arm \
-M vexpress-a9 \
-cpu cortex-a9 \
-smp 4 \
-m 1G \
-nographic \
-kernel ./linux-5.19/build/arch/arm/boot/zImage \
-dtb ./linux-5.19/build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-initrd ./busybox-1.35.0/initramfs.cpio.gz \
-append "init=/init console=ttyAMA0"
参数逐行详解:
-M vexpress-a9:指定模拟的机器类型为vexpress-a9开发板。-cpu cortex-a9:指定模拟的CPU为Cortex-A9核心。-smp 4:模拟一个4核的SMP系统。vexpress-a9最多支持4核。-m 1G:为虚拟机分配1GB内存。vexpress-a9最大支持1GB。-nographic:禁用图形界面,将所有输出重定向到当前命令行终端。-kernel:指定要启动的内核镜像zImage的路径。-dtb:指定设备树二进制文件vexpress-v2p-ca9.dtb的路径。-initrd:指定初始内存磁盘(即我们的initramfs)的路径。-append:传递给内核的命令行参数。init=/init告诉内核,用户空间的第一个程序是根文件系统里的/init脚本;console=ttyAMA0指定内核控制台输出到虚拟的串口设备ttyAMA0,这与-nographic选项配合使用。
按下回车后,你会看到内核启动日志飞速滚动,最后出现我们init脚本里写的欢迎信息,并得到一个/ #的shell提示符。恭喜你,一个完整的ARM32 Linux系统已经在你的电脑里跑起来了!
在模拟系统里验证一下:
# 查看CPU信息,确认是4核Cortex-A9
cat /proc/cpuinfo
# 查看内存,确认是1G
free -h
# 查看内核启动参数
cat /proc/cmdline
# 运行一些BusyBox命令
ls /
uname -a
退出QEMU: 在QEMU的控制台中,先按下Ctrl + A,然后松开再按X,即可退出模拟器。
6. 进阶:模拟更强大的vexpress-a15与图形化启动
vexpress-a9是经典的Cortex-A9平台,而vexpress-a15则代表了更高性能的Cortex-A15核心。用QEMU启动它同样简单,只需修改几个参数。
6.1 字符界面启动vexpress-a15
命令与启动a9类似,主要修改机器类型、CPU、核心数、内存和对应的DTB文件:
qemu-system-arm \
-M vexpress-a15 \
-cpu cortex-a15 \
-smp 2 \
-m 2G \
-nographic \
-kernel ./linux-5.19/build/arch/arm/boot/zImage \
-dtb ./linux-5.19/build/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb \
-initrd ./busybox-1.35.0/initramfs.cpio.gz \
-append "init=/init console=ttyAMA0"
注意,vexpress-a15最多支持2个CPU核心和2GB内存,参数不要超过这个限制。
6.2 图形化界面启动
如果你不想总对着黑底白字的终端,QEMU也可以启动一个图形窗口来显示虚拟机的“屏幕”。去掉-nographic参数,并调整控制台参数即可。
方式一:纯图形窗口,日志和Shell也在窗口内。
qemu-system-arm \
-M vexpress-a15 \
-cpu cortex-a15 \
-smp 2 \
-m 2G \
-kernel ./linux-5.19/build/arch/arm/boot/zImage \
-dtb ./linux-5.19/build/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb \
-initrd ./busybox-1.35.0/initramfs.cpio.gz \
-append "init=/init"
运行后会出现一个QEMU窗口,内核启动日志和最终的Shell都会在这个窗口里。
方式二:混合模式,图形窗口显示“屏幕”,但串口日志输出到宿主终端。 这对于调试非常有用。
qemu-system-arm \
-M vexpress-a15 \
-cpu cortex-a15 \
-smp 2 \
-m 2G \
-serial mon:stdio \
-kernel ./linux-5.19/build/arch/arm/boot/zImage \
-dtb ./linux-5.19/build/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb \
-initrd ./busybox-1.35.0/initramfs.cpio.gz \
-append "init=/init console=ttyAMA0"
这个命令会弹出一个图形窗口(可能是一片空白,因为我们的BusyBox initramfs没有图形界面),同时你启动它的那个终端会变成串口控制台,显示内核日志并提供Shell。要退出,你需要在终端里按Ctrl+A, X,而不是关闭图形窗口。
7. 排错与实用技巧:让环境更顺手
搭建过程中难免会遇到一些小问题,这里分享几个我踩过的坑和解决办法。
1. 音频相关警告
启动时如果看到类似 pulseaudio: set_sink_input_volume() failed 的警告,这通常是QEMU旧版本尝试初始化音频设备时的问题。完全可以忽略,不影响系统运行。如果你用的是较新版本的QEMU(如7.x以上),通常就不会出现这个警告了。
2. 编译BusyBox时的库依赖提示
编译BusyBox时,可能会输出 Library m is needed, can‘t exclude it (yet) 这样的信息。这是在检查静态链接的库依赖,属于正常过程,只要最后编译安装成功,就不用理会。
3. 制作一个一键启动脚本
每次输入一长串QEMU命令太麻烦。我们可以创建一个Shell脚本,比如叫run_vexpress_a9.sh:
#!/bin/bash
QEMU_CMD="qemu-system-arm \
-M vexpress-a9 \
-cpu cortex-a9 \
-smp 4 \
-m 1G \
-nographic \
-kernel $(pwd)/linux-5.19/build/arch/arm/boot/zImage \
-dtb $(pwd)/linux-5.19/build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-initrd $(pwd)/busybox-1.35.0/initramfs.cpio.gz \
-append \"init=/init console=ttyAMA0\""
echo "Starting vexpress-a9..."
eval $QEMU_CMD
给脚本加上执行权限chmod +x run_vexpress_a9.sh,以后只需要./run_vexpress_a9.sh就能启动了。
4. 内核调试准备(可选)
如果你想深入内核启动过程,可以进行内核调试。这需要在编译内核时开启调试信息。在配置内核时(make ... menuconfig),确保以下选项被启用:
Kernel hacking -> Compile-time checks and compiler options -> [*] Compile the kernel with debug infoKernel hacking -> [*] KGDB: kernel debugger
使用QEMU启动时,加上-S -s参数:
-S:在启动时冻结CPU,等待调试器连接。-s:是-gdb tcp::1234的简写,在TCP的1234端口开启一个GDB服务器。
然后在另一个终端,使用gdb-multiarch(需要安装)或交叉编译工具链自带的GDB连接上去,就可以进行源码级的内核调试了。这打开了学习内核源码的又一扇大门。
整个流程走下来,你会发现用QEMU搭建学习环境,最大的好处就是快和安全。编译错了?改一下配置重新编译,一分钟就好。启动参数不对?调整脚本再运行,瞬间就能看到结果。这种即时反馈的乐趣,是玩实体开发板很难比拟的。希望这个详细的指南能帮你顺利跨入ARM和Linux内核的世界,后面无论是学习驱动、文件系统,还是研究系统调度,这个亲手搭建的环境都将是你最好的实验场。

2753

被折叠的 条评论
为什么被折叠?



