ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

QEMU启动ARM64 Linux内核:从环境搭建到调试的完整实践指南

QEMU启动ARM64 Linux内核:从环境搭建到调试的完整实践指南 1. 从零开始为什么要在QEMU上启动ARM64内核如果你是一名嵌入式开发者、内核爱好者或者正在学习ARM64架构那么“在QEMU上启动一个ARM64 Linux内核”几乎是你的必经之路。这听起来像是一个简单的“Hello World”级别的任务但实际操作起来你会发现它远不止敲几行命令那么简单。它是一把钥匙能帮你打开一扇通往系统底层、硬件模拟和内核调试的大门而无需购买任何真实的ARM64开发板。我最初接触这个任务是为了调试一个在真实硬件上难以复现的内核启动问题。物理板卡调试周期长、成本高而QEMU提供的纯软件模拟环境让我可以随意暂停、单步执行、修改内存甚至“倒带”重放执行流这种灵活性是硬件调试无法比拟的。对于学习而言它意味着你可以亲手“组装”一个完整的虚拟计算机从CPU、内存、到磁盘和网络设备然后看着自己编译的内核从零开始一步步点亮屏幕最终进入命令行。这个过程能让你深刻理解“操作系统启动”这个黑盒子里究竟发生了什么。简单来说在QEMU上启动ARM64内核核心价值在于提供一个完全可控、可重复、低成本的学习与开发沙箱。无论你是想研究内核初始化流程、驱动开发还是为交叉编译的应用构建测试环境这都是最理想的起点。接下来我将带你走完从环境准备、内核编译、根文件系统制作到最终成功启动并进入Shell的完整流程并分享其中每一步我踩过的坑和总结的技巧。2. 环境搭建与工具链选型奠定坚实的基础工欲善其事必先利其器。在开始编译和运行之前我们需要一个稳定且功能齐全的宿主环境并选择正确的工具链。这里的每一个选择都直接影响后续步骤的顺利程度。2.1 宿主系统与QEMU安装我强烈推荐使用Ubuntu 22.04 LTS或Debian 12作为宿主系统。它们的软件源丰富能方便地安装我们所需的所有工具社区支持也最完善。如果你使用Windows或macOS建议通过WSL2Windows或虚拟机macOS安装一个Ubuntu环境以获得原生Linux的开发体验。安装QEMU是整个流程中最简单的一步。在Ubuntu/Debian上只需一条命令sudo apt update sudo apt install qemu-system-arm qemu-utils这条命令会安装用于模拟ARM架构的qemu-system-arm它同时支持32位和64位ARM以及用于处理磁盘镜像的qemu-img工具。安装完成后可以通过qemu-system-aarch64 --version来验证。注意有些教程会建议从源码编译QEMU以获得最新特性。但对于初学者和大多数开发场景使用发行版提供的稳定版本完全足够能避免许多依赖和编译问题。2.2 交叉编译工具链的选择与安装由于我们是在x86_64的电脑上编译ARM64架构的内核和程序必须使用交叉编译工具链。这里有几个主流选择Linaro GCC曾经是ARM架构的事实标准非常稳定。但近年来更新放缓。ARM官方 GNU Toolchain由ARM公司直接维护更新及时对ARM新特性支持最好。发行版提供的交叉编译器如gcc-aarch64-linux-gnu。这是最方便的选择。对于新手和追求稳定性的用户我推荐直接使用发行版提供的工具链因为它与系统集成度最高几乎不会出现库依赖问题。安装命令如下sudo apt install gcc-aarch64-linux-gnu安装后可以通过aarch64-linux-gnu-gcc --version检查是否安装成功。这个工具链包含了编译器、链接器、二进制工具如objdump以及针对ARM64的C库。如果你需要更前沿的编译器特性例如为ARMv8.5-A架构优化可以考虑从ARM官网下载其官方工具链并手动配置路径。但请记住这可能会引入动态库链接的复杂性。在本文的后续部分我们将统一使用aarch64-linux-gnu-这个前缀。2.3 内核源码获取我们需要Linux内核的源代码。获取方式有两种从内核官网下载稳定版本访问 kernel.org 下载一个稳定版本如linux-6.6.x.tar.xz。这种方式获取的代码最“纯净”。通过Git克隆如果你想跟踪最新开发动态或需要某个特定分支可以使用Gitgit clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git # 或者使用更快的国内镜像 git clone https://mirrors.edge.kernel.org/pub/linux/kernel/git/torvalds/linux.git克隆后可以通过git tag查看版本并用git checkout v6.6切换到特定版本。我建议初学者先从稳定版本如6.6.x开始避免主开发分支可能存在的波动。将下载的压缩包解压到一个工作目录我们的所有操作都将在这个目录内进行。3. 配置与编译ARM64 Linux内核拿到源码后我们并不能直接编译。需要告诉编译系统我们的目标是什么架构需要哪些功能不需要哪些功能。这个过程就是内核配置。3.1 内核配置defconfig与menuconfig对于ARM64内核已经为我们提供了一个基础的默认配置defconfig。这是最快的方式# 进入内核源码根目录 cd linux-6.6 # 导出交叉编译环境变量这是关键一步 export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- # 加载ARM64的默认配置 make defconfig执行make defconfig后会在当前目录生成一个.config文件它包含了启动一个最小系统所需的核心配置。但是defconfig非常精简可能缺少我们后续需要的功能比如网络驱动、某些文件系统支持、或者内核模块。因此我们通常需要在此基础上进行定制。使用menuconfig工具make menuconfig这会打开一个基于ncurses的图形化配置界面。在这里你可以像逛菜单一样浏览和修改成千上万个配置选项。对于QEMU启动有几个关键配置我建议你检查或开启使用方向键导航空格键选中[*]为编译进内核[M]为编译成模块[ ]为不编译Device Drivers - Network device support - Virtio network driver确保启用编译进内核或模块。这是QEMU虚拟网卡的高性能驱动。File systems - The Extended 4 (ext4) filesystem启用支持因为我们的根文件系统很可能用ext4。Kernel hacking - Kernel low-level debugging functions如果你需要早期串口调试信息可以在这里配置串口地址。对于QEMU默认的PL011串口在0x9000000。General setup - Initial RAM filesystem and RAM disk (initramfs) support确保启用。我们下一步制作的initramfs需要这个支持。配置完成后保存并退出.config文件会被更新。3.2 内核编译与产物解读配置完成后就可以开始编译了。使用-j参数可以指定并行编译的作业数通常设置为CPU核心数以加快编译速度make -j$(nproc)编译过程可能需要几分钟到十几分钟取决于你的电脑性能。编译成功后在arch/arm64/boot/目录下会生成几个重要文件Image压缩后的内核镜像文件。这是QEMU可以直接引导的、最主要的文件。Image.gz如果配置了更高压缩率可能会生成这个。vmlinux未压缩的、带完整调试信息的ELF可执行文件。主要用于调试例如在GDB中加载符号不能直接用于引导。我们启动所需的核心文件就是arch/arm64/boot/Image。你可以把它复制到一个方便的地方比如工作目录的根文件夹。踩坑记录编译时最常见的错误是缺少依赖。如果遇到关于openssl、libelf或flex/bison的错误请使用sudo apt install libssl-dev libelf-dev flex bison来安装。另一个常见问题是交叉编译环境变量没有正确导出导致编译成了x86内核。务必确认ARCH和CROSS_COMPILE环境变量已设置。4. 构建根文件系统内核的“生存环境”内核启动后需要挂载一个根文件系统rootfs才能加载并运行用户空间的程序比如/bin/bash。没有根文件系统的内核就像有了发动机但没有车轮和座椅的汽车无法提供任何交互服务。我们将创建一个最简单的、基于initramfs的根文件系统。4.1 使用BusyBox制作最小根文件系统initramfs是一个被编译进内核或作为独立镜像加载到内存中的临时根文件系统。我们使用BusyBox来制作它因为BusyBox将许多常用的Unix工具如ls,cat,mkdir,sh集成进了一个单一的二进制文件非常节省空间。第一步下载并编译BusyBox# 下载稳定版BusyBox wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 tar -xjf busybox-1.36.1.tar.bz2 cd busybox-1.36.1和编译内核一样需要配置交叉编译export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu-然后进行配置。BusyBox有一个非常详细的menuconfig但对于最小系统我们只需要确保使用静态链接避免运行时依赖外部库make menuconfig在配置界面中进入Settings - Build static binary (no shared libs)选择为[*](按Y键)。 其他设置可以保持默认。保存退出后开始编译安装make -j$(nproc) make install编译安装后会在_install目录下生成基本的Linux文件系统结构bin,sbin,usr,linuxrc。第二步构建initramfs目录结构我们在一个独立目录例如rootfs中构建完整的文件系统mkdir -p rootfs cd rootfs cp -r /path/to/busybox-1.36.1/_install/* . mkdir -p proc sys dev etc/init.d tmp关键的一步是创建初始化脚本。当内核挂载initramfs后它会尝试执行/init文件。我们来创建它cat init EOF #!/bin/sh # 挂载虚拟文件系统 mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs none /dev # 打印欢迎信息 echo Hello from ARM64 QEMU! # 启动一个shell exec /bin/sh EOF chmod x init这个脚本完成了最基本的初始化挂载proc、sys、dev等虚拟文件系统然后启动一个shshell。exec命令使得shell成为进程1init进程。第三步打包成initramfs镜像现在我们需要把这个rootfs目录打包成内核能识别的格式。有两种常用方式cpio归档最常用cd rootfs find . | cpio -H newc -ov --owner root:root ../initramfs.cpio cd .. gzip -f initramfs.cpio # 生成 initramfs.cpio.gz在内核编译时直接集成推荐用于简单调试 在内核的make menuconfig中General setup - Initramfs source file(s)里直接填写rootfs目录的绝对路径。这样编译出的内核就自带了根文件系统无需额外指定启动参数。这对于最简单的启动测试非常方便。我们采用第一种方式生成独立的initramfs.cpio.gz文件。5. 启动与调试让内核在QEMU中跑起来万事俱备现在可以启动我们的虚拟ARM64机器了。QEMU的命令行参数非常丰富我们从一个最简单的命令开始逐步增加功能。5.1 基础启动命令解析最基本的启动命令如下qemu-system-aarch64 \ -machine virt \ -cpu cortex-a57 \ -smp 2 \ -m 2G \ -kernel ./arch/arm64/boot/Image \ -initrd ./initramfs.cpio.gz \ -append consolettyAMA0 earlyconpl011,0x9000000 root/dev/ram0 rw \ -nographic让我们逐行解析这个命令-machine virt指定机器类型为virt。这是QEMU为虚拟化设计的一个通用平台支持PCI、网络、磁盘等大量虚拟设备非常适合开发和测试。-cpu cortex-a57指定CPU模型为Cortex-A57。你可以换成cortex-a53,cortex-a72或max模拟主机支持的最强特性。-smp 2模拟一个具有2个CPU核心的SMP系统。-m 2G为虚拟机分配2GB内存。-kernel ./arch/arm64/boot/Image指定要引导的内核镜像路径。-initrd ./initramfs.cpio.gz指定初始内存磁盘initramfs镜像。-append “…”传递给内核的命令行参数。这是极其关键的一部分consolettyAMA0告诉内核将控制台输出到ttyAMA0设备即ARM的PL011串口。earlyconpl011,0x9000000启用早期控制台这样在内核解压后、初始化完成前就能看到打印信息对于调试启动故障至关重要。root/dev/ram0 rw指定根设备为/dev/ram0即我们的initramfs所在的内存盘并以读写方式挂载。-nographic禁用图形化输出将所有输出重定向到当前终端标准输入/输出。同时通过快捷键Ctrl-A然后按X可以强制退出QEMU。执行这条命令如果一切顺利你将看到内核解压、启动最后打印出Hello from ARM64 QEMU!并进入BusyBox的shell提示符/ #。恭喜你一个最小的ARM64 Linux系统已经成功运行5.2 进阶添加磁盘与网络支持只有内存文件系统是不够的。要运行更复杂的应用我们需要持久化存储和网络。添加虚拟磁盘首先创建一个空的磁盘镜像文件例如一个1GB的qcow2格式镜像qemu-img create -f qcow2 arm64disk.qcow2 1G-f qcow2格式支持动态分配空间更节省宿主机磁盘。 然后在启动命令中增加磁盘设备-drive filearm64disk.qcow2,formatqcow2,ifnone,idhd0 \ -device virtio-blk-device,drivehd0 \这为虚拟机添加了一个Virtio-BLK块设备。启动后在系统内你可以用fdisk -l看到它可能是/dev/vda然后进行分区、格式化如mkfs.ext4 /dev/vda1、挂载使用。添加虚拟网络为了让虚拟机能够访问外部网络我们添加一个用户模式网络栈User Networking-netdev user,idmynet0,hostfwdtcp::2222-:22 \ -device virtio-net-device,netdevmynet0 \-netdev user创建一个用户模式的网络后端QEMU会内置一个简单的TCP/IP栈并执行NAT让虚拟机访问外网。hostfwdtcp::2222-:22将宿主机的2222端口转发到虚拟机的22端口SSH。这样你可以在宿主机上通过ssh -p 2222 rootlocalhost登录虚拟机。-device virtio-net-device为虚拟机添加一个Virtio网络设备并连接到前面定义的mynet0网络后端。现在一个功能更完整的启动命令看起来像这样qemu-system-aarch64 \ -machine virt \ -cpu cortex-a57 \ -smp 2 -m 2G \ -kernel ./arch/arm64/boot/Image \ -initrd ./initramfs.cpio.gz \ -append consolettyAMA0 earlycon root/dev/ram0 rw \ -drive filearm64disk.qcow2,formatqcow2,ifnone,idhd0 \ -device virtio-blk-device,drivehd0 \ -netdev user,idmynet0,hostfwdtcp::2222-:22 \ -device virtio-net-device,netdevmynet0 \ -nographic5.3 内核调试实战使用GDB连接QEMUQEMU最强大的功能之一是支持GDB调试。这对于分析内核崩溃、理解启动流程、学习内核源码至关重要。首先需要在启动QEMU时开启GDB服务器监听-S \ -gdb tcp::1234-S在启动时冻结CPU等待GDB连接后再开始执行。-gdb tcp::1234在TCP的1234端口启动GDB服务器。然后在另一个终端使用交叉编译工具链中的GDBaarch64-linux-gnu-gdb来连接。关键一步是加载带调试符号的vmlinux文件aarch64-linux-gnu-gdb ./vmlinux (gdb) target remote localhost:1234 (gdb) break start_kernel # 在内核主入口函数处设置断点 (gdb) continue # 继续执行会停在断点处连接成功后你就可以像调试普通程序一样单步执行step/next、查看变量print、查看回溯backtrace了。这对于追踪那些“开机就挂”的诡异问题或者单纯想看看内核初始化是如何一步步进行的是无可替代的工具。调试技巧在内核配置中确保Kernel hacking - Compile-time checks and compiler options - Compile the kernel with debug info是开启的这样vmlinux才会包含完整的调试符号。另外使用layout src命令可以在GDB中打开一个源码窗口使调试过程更加直观。6. 常见问题排查与解决方案即使按照步骤操作你也可能会遇到一些问题。下面是我在多次实践中总结的几个典型问题和解决方法。6.1 内核启动后卡住或无输出这是最常见的问题通常与控制台配置有关。症状QEMU启动后只打印一两行如“Booting Linux on physical CPU 0x0000000000 [0x411fd070]”然后光标闪烁再无输出。排查首先检查QEMU命令中的-append参数。确保console和earlycon参数正确。对于virt机器串口是ttyAMA0早期控制台是pl011,0x9000000。一个字母错误都会导致输出到“虚空”。检查内核配置。执行make menuconfig进入Device Drivers - Character devices - Serial drivers确保ARM AMBA PL011 serial port support被编译进内核[*]而不是模块[M]。尝试在-append参数中增加ignore_loglevel或earlyprintk强制打印所有级别的内核信息。使用-d参数让QEMU输出更多内部日志-d cpu_reset,guest_errors。有时能发现CPU异常或非法指令。6.2 内核Panic无法挂载根文件系统症状内核启动一段时间后打印出“Kernel panic - not syncing: VFS: Unable to mount root fs”或类似错误。排查检查initramfs确认-initrd参数指向的文件路径正确并且文件没有损坏。可以用file initramfs.cpio.gz检查或尝试解压gunzip -c initramfs.cpio.gz | cpio -t列出内容。检查内核命令行确认root参数指定了正确的设备。对于initramfs通常是root/dev/ram0。如果你使用了磁盘可能是root/dev/vda1。检查内核文件系统支持确保内核编译了对应文件系统的支持。对于initramfscpio格式需要CONFIG_BLK_DEV_INITRDy和CONFIG_RD_GZIPy等。对于ext4磁盘需要CONFIG_EXT4_FSy。可以在.config文件中搜索这些配置项。检查init文件确保initramfs根目录下的/init文件存在、可执行并且脚本语法正确。一个简单的测试方法是在-append参数中加入init/bin/sh尝试直接启动shell绕过你的init脚本。6.3 网络功能无法使用症状在虚拟机内无法ping通宿主机或外网如8.8.8.8。排查检查设备识别在虚拟机内执行ip addr或ifconfig -a看是否有eth0这样的网络接口出现。如果没有说明Virtio网络驱动未加载。检查内核驱动确认内核配置中Device Drivers - Network device support - Virtio network driver已启用。如果是编译成模块[M]则需要确保initramfs中包含了virtio_net.ko模块并在/init脚本中insmod它。用户模式网络限制QEMU的用户模式网络-netdev user默认可以访问外网但外部无法直接访问虚拟机除了端口转发。ping宿主机IP可能不通但ping一个外网地址如8.8.8.8应该是可以的。如果不行检查宿主机防火墙是否阻止了QEMU的NAT流量。尝试TAP网络对于更复杂的网络需求如虚拟机需要独立的IP、需要被局域网内其他机器访问可以配置TAP网络但这需要宿主机端的桥接配置步骤更复杂。6.4 性能优化与高级用法建议当基本功能跑通后你可能会关心如何让它更好用。启用KVM加速如果你的宿主机是Linux并且CPU支持虚拟化Intel VT-x或AMD-V可以启用KVM来获得接近原生的性能。将-machine virt改为-machine virt,accelkvm即可。使用-enable-kvm参数旧版本也可以。启用后你会感觉系统响应速度有质的提升。使用图形化界面去掉-nographic参数QEMU会弹出一个图形窗口。你可以配合-cdrom参数加载一个完整的ARM64发行版ISO如Ubuntu Server for ARM进行安装得到一个带有图形界面的完整Linux系统。制作更复杂的根文件系统除了BusyBox你可以使用debootstrap为ARM64构建一个Debian/Ubuntu的根文件系统然后打包进磁盘镜像。这样你就拥有了一个带apt包管理器的完整系统可以安装任何你需要的软件。自动化脚本将编译内核、制作根文件系统、启动QEMU的命令写成一个Shell脚本可以极大提高效率。更进一步可以研究使用buildroot或yocto这类构建系统它们能自动化地生成交叉工具链、内核、根文件系统并打包成完整的SD卡镜像。
返回列表