QEMU + ARMv8 环境搭建:从零开始构建嵌入式开发环境

📅 发布时间:2026/7/12 21:50:03 👁️ 浏览次数:
QEMU + ARMv8 环境搭建:从零开始构建嵌入式开发环境
1. 为什么你需要一个ARMv8模拟环境如果你刚开始接触嵌入式开发尤其是ARM架构可能会觉得有点无从下手。买一块真实的开发板价格不菲而且每次烧录、调试都挺麻烦万一操作失误还有“变砖”的风险。我刚开始学那会儿就烧坏过两块板子心疼了好久。后来我发现其实在电脑上就能搭建一个功能完整的ARM开发环境这就是我们今天要聊的QEMU。简单来说QEMU是一个开源的“虚拟机”和“模拟器”。它厉害的地方在于能让你在x86的电脑上模拟运行ARM、MIPS、RISC-V等各种架构的处理器。对于学习ARMv8也就是AArch6464位ARM来说这简直是神器。你不用花一分钱买硬件就能编写、编译、运行甚至调试你的ARM汇编或C语言程序体验从底层寄存器操作到操作系统引导的完整流程。这个环境特别适合几类朋友一是嵌入式开发的初学者想低成本入门ARM架构二是做系统软件、驱动或内核开发的工程师需要在特定架构上测试代码三是学生或研究者进行计算机体系结构相关的实验。整个过程就像在电脑里“虚拟”出了一块功能强大的ARM开发板随开随用安全又方便。2. 搭建前的准备工作安装系统与基础工具工欲善其事必先利其器。搭建环境的第一步是准备好你的“工作台”。我强烈推荐使用Ubuntu Linux系统无论是物理机安装还是用虚拟机比如VirtualBox或VMware都可以。Ubuntu对开发者非常友好软件包丰富能省去很多折腾的麻烦。我这里以Ubuntu 20.04或22.04为例其他Linux发行版命令可能略有不同。打开你的终端我们首先更新软件包列表然后安装一系列编译和开发所需的工具。别被这一长串命令吓到它们都是构建环境的基础砖瓦。sudo apt update -y sudo apt install -y net-tools libncurses5-dev libssl-dev build-essential openssl qemu-system-arm libncurses5-dev gcc-aarch64-linux-gnu git bison flex bc vim universal-ctags cscope cmake python3-dev gdb-multiarch openjdk-13-jre trace-cmd kernelshark bpfcc-tools cppcheck docker docker.io libelf-dev gcc-7 xterm curl clang exuberant-ctags python-is-python3运行完上面这个命令你的系统就已经装备了强大的“武器库”。我来简单解释几个关键工具是干嘛的build-essential, gcc-7, clang这是C/C编译器的全家桶用于编译本机x86程序。gcc-aarch64-linux-gnu这是交叉编译工具链的核心它能在你的x86电脑上生成运行在ARMv8AArch64架构上的可执行文件。没有它你就没法为ARM编译代码。qemu-system-arm这是Ubuntu仓库里的QEMU软件包。我们先装上它但为了获得最新特性和更好的兼容性后面我们通常会选择自己编译更新的版本。gdb-multiarch这是一个支持多种处理器架构的调试器。有了它你才能调试ARM程序。git, cmake代码管理和构建工具后续下载源码和编译时会用到。libncurses5-dev, libssl-dev, libelf-dev这些是开发库编译一些软件比如内核、QEMU本身时需要它们提供的头文件和链接库。安装完基础工具后我们还可以为后续可能编译Linux内核做一些准备执行下面的命令可以安装内核构建依赖sudo apt build-dep linux-image-generic这一步不是必须的但做了之后如果你未来想自己编译ARM Linux内核会减少很多缺少依赖的报错。好了基础平台已经搭建完毕接下来我们要准备主角——QEMU了。3. 获取与编译最新版QEMU系统仓库里的QEMU版本可能比较旧。为了获得最好的体验和对ARMv8最完善的支持我习惯从官网下载源码自己编译。自己编译听起来复杂其实步骤很固定而且能让你更了解这个工具。放心我会带你一步步走。首先我们找一个干净的目录比如在家目录下创建一个armv8_workspace文件夹然后进入。mkdir -p ~/armv8_workspace cd ~/armv8_workspace接着我们去QEMU官网下载源码。你可以访问 https://www.qemu.org/download/ 查看最新版本。假设我们下载一个较新的稳定版比如7.2.0。使用wget命令下载压缩包。wget https://download.qemu.org/qemu-7.2.0.tar.xz下载完成后解压源码包tar xvJf qemu-7.2.0.tar.xz cd qemu-7.2.0在编译QEMU之前还需要安装几个它依赖的特定开发包sudo apt-get install -y zlib1g zlib1g-dev libglib2.0-dev libpixman-1-devzlib1g-dev提供压缩库支持。libglib2.0-devQEMU大量使用的通用工具库非常重要。libpixman-1-dev像素处理库与图形显示相关。现在进入解压后的QEMU目录运行配置脚本。我们这里进行一个针对ARM架构的简化配置让它专注于模拟ARM设备这样编译速度会快很多。./configure --target-listaarch64-softmmu --audio-drv-list解释一下参数--target-listaarch64-softmmu指定我们只编译针对AArch64即ARMv8系统模拟的目标。softmmu表示全系统模拟可以模拟整个计算机包括CPU、内存、外设与之相对的是linux-user模式仅模拟Linux程序运行。--audio-drv-list这里设为空因为我们做嵌入式开发通常不需要音频支持可以省略相关编译。配置脚本会检查你的系统环境并生成对应的编译规则Makefile。如果看到类似 “Install prefix /usr/local” 这样的输出没有红色的错误信息就说明配置成功了。接下来就是编译和安装这步比较耗时取决于你的电脑CPU性能可能需要十几到几十分钟。喝杯咖啡等待一下吧。make -j$(nproc) sudo make installmake -j$(nproc)-j参数表示并行编译$(nproc)会自动获取你电脑的CPU核心数用所有核心来编译速度最快。sudo make install将编译好的QEMU程序安装到系统目录默认是/usr/local/bin。安装完成后验证一下是否成功。打开一个新的终端或者回到命令行输入qemu-system-aarch64 --version你应该能看到类似 “QEMU emulator version 7.2.0” 的输出。恭喜你一个功能强大的ARMv8模拟器已经就位了4. 选择你的“虚拟开发板”有了QEMU就像有了一个“虚拟硬件生成器”它可以模拟很多种真实的ARM开发板。这对于学习来说非常棒因为你可以体验不同板子的特性。我们怎么知道QEMU支持哪些板子呢很简单用下面这个命令查看qemu-system-aarch64 -M help这个命令会列出一长串支持的机器Machine类型比如raspi3b树莓派3B、virtQEMU自定义的通用虚拟平台、vexpress-a9ARM Versatile Express开发板等等。对于ARMv8初学者我主要推荐两个选择1.raspi3b树莓派3B模型这是一个非常流行的选择。它模拟的是广为人知的树莓派3BCPU是4核的Cortex-A53ARMv8-A。它的外设模拟相对简单文档和社区资源丰富特别适合入门运行裸机程序或学习U-Boot、Linux内核移植。2.virtQEMU通用虚拟平台这是QEMU团队自己设计和维护的一个“虚拟”开发板。它不模仿任何具体实物但包含了QEMU支持的最新一代虚拟化硬件比如GICv3中断控制器、virtio磁盘和网络设备等。它的优点是功能强大、性能好并且是QEMU开发和测试的首要平台兼容性最有保障。如果你想深入研究ARM虚拟化、或者想运行一个完整的Linux发行版virt平台通常是首选。为了后续实验我们还需要安装调试工具确保之前安装的gdb-multiarch可用sudo apt-get install -y gdb-multiarch你可以把它理解为一个“万能”的GDB通过配置可以调试各种架构的程序包括我们即将要运行的ARMv8程序。5. 运行你的第一个ARMv8程序环境准备好了“开发板”也选好了是时候让我们的ARM世界动起来了。我们从最简单的开始运行一个编译好的ARM裸机二进制文件通常是一个.bin文件。假设你已经用aarch64-linux-gnu-gcc交叉编译器编译好了一个名为hello.bin的简单程序。运行它的QEMU命令非常直接qemu-system-aarch64 -machine raspi3b -kernel hello.bin -nographic让我拆解一下这个命令-machine raspi3b指定使用树莓派3B的硬件模型。-kernel hello.bin告诉QEMU将hello.bin这个文件作为“内核”加载到内存的特定地址通常是ARM处理器的复位向量地址并开始执行。-nographic这个参数很重要它表示不使用图形化窗口所有输出都重定向到当前终端。对于嵌入式裸机程序它们通常通过串口UART输出信息-nographic模式会将QEMU模拟的串口连接到你的终端上。程序运行的任何printf如果链接了标准库或对串口的直接操作都会打印在你的终端里。运行后你可能会看到一些输出或者程序直接执行完毕退出。如果没有-nographicQEMU会弹出一个独立的图形窗口但对于没有图形界面的裸机程序那个窗口通常是黑的所以我们用-nographic更方便。如果你想使用功能更强大的virt平台命令会稍微复杂一点因为它可以配置更多参数qemu-system-aarch64 -cpu cortex-a57 -smp 1 -m 512M -machine virt,gic-version3 -nographic -kernel hello.bin这里多了几个参数-cpu cortex-a57指定模拟的CPU型号为Cortex-A57。-smp 1模拟1个CPU核心Symmetric Multi-Processing。-m 512M为虚拟机分配512MB的内存。gic-version3指定使用GICv3中断控制器这是ARMv8的现代中断控制器。通过这种方式你就可以快速验证编译出的ARM程序是否能正确运行。这是学习底层开发的第一步也是调试的基础。6. 使用GDB进行源码级调试只会运行程序还不够调试才是开发中的常态。QEMU与GDB的配合堪称完美可以实现源码级别的单步调试这对于理解程序执行流程、查找bug至关重要。下面我以调试一个名为benos.elf的ELF格式文件包含调试信息为例展示完整的流程。第一步启动QEMU并等待调试器连接我们需要让QEMU在启动后暂停并打开一个调试端口。这需要用到-S和-s参数。qemu-system-aarch64 -machine raspi3b -kernel benos.elf -S -s -nographic-S表示在启动时冻结CPU不让程序立刻执行直到调试器GDB发出继续运行的命令。-s是-gdb tcp::1234的简写表示在TCP的1234端口监听GDB的连接。执行这个命令后终端看起来会卡住因为虚拟机已经创建但CPU被冻结了。这就对了它在等待调试器。第二步启动GDB并连接打开另一个终端窗口进入你的项目目录。首先启动gdb-multiarch并加载带调试信息的ELF文件。gdb-multiarch benos.elfGDB启动后你会看到(gdb)提示符。接下来让它连接到正在等待的QEMU虚拟机(gdb) target remote localhost:1234如果连接成功你会看到类似 “Remote debugging using localhost:1234” 的提示。现在GDB和QEMU已经建立了桥梁。第三步设置断点并开始调试连接后我们可以设置断点。比如我们想在程序的入口函数_start处停下(gdb) b _start (gdb) cb _start是在_start符号处设置断点。c是continue的缩写让被冻结的CPU开始执行直到遇到断点。执行c后程序会运行并在_start处停下。这时你就可以使用各种GDB命令进行调试了layout src切换到显示源代码和汇编的图形化布局TUI模式非常直观。si/ni单步执行一条汇编指令si会步入函数调用ni会越过。s/n单步执行一行C源代码需要编译时加-g参数。info registers或i r查看所有寄存器的当前值。p /x $x0以十六进制打印通用寄存器x0的值。x /10i $pc查看当前程序计数器$pc位置开始的10条汇编指令。调试完成后在GDB中输入q退出GDB会询问是否退出确认即可。QEMU那边也会随之关闭。这个过程一开始可能觉得有点繁琐但熟练之后你会发现它是探索ARM体系结构和调试复杂问题的利器。你可以观察每一条指令对寄存器的影响深入理解中断、内存映射等核心机制。7. 构建更复杂的开发环境内核与文件系统前面的例子是运行一个简单的裸机程序。但在真实的嵌入式开发中我们更多是跟操作系统打交道比如运行一个完整的Linux系统。这就需要我们准备两样东西内核镜像和根文件系统。准备Linux内核我们可以为ARMv8编译一个Linux内核。首先获取内核源码以稳定版5.10为例cd ~/armv8_workspace git clone --depth1 -b v5.10 https://github.com/torvalds/linux.git linux-5.10 cd linux-5.10配置内核。对于virt平台有一个默认配置make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- defconfig如果你需要特定的驱动或功能可以使用菜单界面进行配置make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- menuconfig然后开始编译内核镜像make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc) Image编译完成后内核镜像文件位于arch/arm64/boot/Image。制作根文件系统根文件系统包含了Linux启动后所需的库、工具和配置文件。一个简单的方法是使用BusyBox。这里步骤较多简述流程下载并编译BusyBox配置为静态链接。创建一个目录如rootfs按照Linux目录结构放置BusyBox生成的文件、必要的设备节点如/dev/console和初始化脚本如/init。使用genext2fs或dd和mkfs.ext4命令将这个目录制作成一个ext4格式的磁盘镜像文件例如rootfs.ext4。使用QEMU启动完整Linux系统有了内核和文件系统镜像我们就可以启动一个完整的ARM Linux了。对于virt平台一个典型的启动命令如下qemu-system-aarch64 -cpu cortex-a57 -smp 2 -m 1G -machine virt,gic-version3 -nographic \ -kernel ~/armv8_workspace/linux-5.10/arch/arm64/boot/Image \ -drive filerootfs.ext4,formatraw,ifvirtio \ -append root/dev/vda rw consolettyAMA0这个命令做了几件事指定了CPU、内存和机器类型。通过-kernel加载编译好的Linux内核。通过-drive添加一个虚拟硬盘里面是我们的根文件系统镜像使用高效的virtio接口。通过-append传递内核启动参数告诉内核根设备是第一个virtio磁盘/dev/vda并以读写方式挂载控制台是ttyAMA0即串口。执行后你会看到内核启动日志在终端滚动最后出现登录提示符如果BusyBox配置了登录。一个完整的ARM64 Linux系统就在你的电脑里运行起来了8. 常见问题与排错指南在搭建和使用的过程中你难免会遇到一些问题。这里我总结几个我踩过的“坑”和解决办法希望能帮你快速排雷。1. 编译QEMU时出现 “ERROR: glib-2.48 gthread-2.0 is required to compile QEMU” 之类的错误。这通常是因为依赖库没装全或者版本不对。请确保你已经正确安装了libglib2.0-dev。如果还是不行可以尝试安装更高版本的glib开发包或者查看QEMU源码目录下的README文件获取最准确的依赖要求。2. 运行qemu-system-aarch64提示命令未找到。如果你是自己编译安装的默认安装路径是/usr/local/bin请确认该目录在你的PATH环境变量中。也可以使用绝对路径/usr/local/bin/qemu-system-aarch64来运行。如果是用apt安装的包名提供的命令通常是qemu-system-aarch64。3. GDB连接QEMU失败提示 “Connection refused”。首先确认QEMU启动时是否加上了-S -s参数。其次检查GDB连接命令中的端口号是否一致默认是1234。最后确保两个终端都在同一台机器上没有防火墙规则阻止本地回环地址localhost的连接。4. 程序在QEMU中运行无输出或者立即退出。对于裸机程序首先要确认你的程序是否正确初始化了串口并且输出到了正确的UART设备地址上。不同的开发板模型其串口内存映射地址是不同的。例如树莓派3B的Mini UART地址是0x3F215040PL011 UART是0x3F201000而virt平台的PL011地址通常是0x09000000。你需要查阅对应板子的技术手册或QEMU源码来确定。其次检查交叉编译工具链是否正确程序是否链接到了正确的启动地址。5. 使用-nographic启动后想退出QEMU怎么办在-nographic模式下QEMU会捕获你的键盘输入。要退出先按下CtrlA松开后再按X键即Ctrl-A X。也可以先按CtrlA再按C进入QEMU监视器然后输入quit命令退出。6. 如何让QEMU虚拟机访问主机网络最简单的网络配置是使用“用户模式网络”User Networking它允许虚拟机访问外部网络但外部无法直接访问虚拟机。在启动命令中添加-netdev user,idmynet -device virtio-net-device,netdevmynet即可。这样虚拟机内部就可以使用dhcp获取IP并ping通外网了。遇到问题别慌张多查看终端输出的错误信息善用搜索引擎记得用英文关键词如 “qemu raspi3b uart not working”大部分常见问题都有解决方案。嵌入式开发本身就是一个不断解决问题的过程每解决一个坑你的功力就增长一分。