从 QEMU 直接启动到 U-Boot 引导:嵌入式 Linux 启动流程的本质差异

张开发
2026/6/7 16:10:08 15 分钟阅读
从 QEMU 直接启动到 U-Boot 引导:嵌入式 Linux 启动流程的本质差异
目录一、Linux启动流程1BIOS与BootLoader的理解2U-Boot嵌入式领域的BIOSBootLoader结合体二、如何在QEMU上模拟启动流程1官网下载U-Boot2检测工具链完整性3编译U-Boot使之符合vexpress-a9开发板环境4一次失败的启动vexpress-a9与新版U-Boot的兼容性问题5现在的QEMU启动命令与之前有什么差异在学习Linux驱动开发的时候我发现理解设备树、总线架构、子系统等等本身并不是难点但是自己在编写代码的时候却经常出错而且大多数时候是因为运行环境的问题比如这个子系统没裁剪进来、那个依赖的子系统没有开启等等。为了解决这些问题我决定系统的学习一遍Linux裁剪、编译的原理。一、Linux启动流程1BIOS与BootLoader的理解在嵌入式开发中上电启动流程是所有开发的起点也是很多开发者最容易混淆的核心概念。我最开始会把 BIOS、Bootloader、startup 文件、main 函数混为一谈甚至搞不清 STM32 裸机开发和带 Linux 系统的嵌入式开发在启动逻辑上的本质区别。BIOS就类比STM32的startup.s启动文件他只做最基本的初始化比如初始化时钟、堆栈指针等等然后在最后一行跳转到main函数去执行裸机代码。startup.s 的作用完全对应 PC 的 BIOS只做三件事初始化堆栈指针SP初始化中断向量表跳转到main函数全程不初始化任何外设LED、串口、SPI、SD 卡一概不管只做最基础的硬件环境搭建。在STM32中裸机开发中我们是直接在这个main函数中对各种外设初始化并运行的比如我们要使用SD卡于是我们初始化SPI功能。由于STM32是哈弗架构的所以main函数是直接从Flash读取并运行的。而现代操作系统如 Linux的代码量、动态内存需求极大而 Flash 的随机访问速度、带宽远低于内存DDR内存。如果继续沿用裸机的「指令直接从 Flash 取指」的哈佛架构模式会严重拖慢系统运行效率。因此带操作系统的嵌入式系统多为 ARM Cortex-A 系列冯诺依曼架构会将内核镜像、根文件系统从 Flash/SD 卡等存储设备完整搬运到高速 DDR 内存中执行从根本上解决性能瓶颈。这个过程我们称为BootLoader。BootLoader相当于在STM32的裸机开发最后加入一行指令跳转到内存中的操作系统。即BootLoader一方面在裸机环境中做了基本外设的初始化其中最为关键的是把Flash中的操作系统代码拷贝到内核中最后将控制权交给操作系统内核。BootloaderU-Boot核心的「搬运 跳转」环节干三件事初始化必要外设DDR 内存、SD 卡 / EMMC、Flash 等存储设备为系统运行搭建硬件环境搬运系统镜像把 SD 卡 / EMMC 中的 Linux 内核镜像、设备树文件从 Flash 搬运到 DDR 内存中跳转执行跳转到内存中的 Linux 内核入口地址把控制权完全交给操作系统2U-Boot嵌入式领域的BIOSBootLoader结合体在嵌入式 Linux 世界里U-Boot 最主流的 BootLoader 实现。它不是简单的引导程序而是集 BIOS BootLoader 于一身的超级启动固件。所以我们想要进行嵌入式Linux驱动开发首先就得下载U-Boot。不过通常U-Boot官网上的引导启动程序不是很完善只是一个通用模板而具体的开发板厂商会针对自己的板子进行定制化修改。比如你想要使用i.MX6ULL开发板你就去NXP官网下载它对应的U-Boot即可。二、如何在QEMU上模拟启动流程1官网下载U-Boot由于我们使用的是QEMU模拟的vexpress-a9开发板U-Boot是完美支持的所以可以直接在官网下载。如果你使用的是各种厂家真实开发板则需要下载厂家定制的U-BootIndex of /pub/u-boot/在这个链接下可以找到所有版本的U-Boot。将其放到与Linux内核同层级处:解压缩并进入U-Boot的目录中# 解压.bz2格式的源码包 tar -jxvf u-boot-2026.04.tar.bz22检测工具链完整性首先检测一下你的工具链、QEMU是否正常安装好了echo 开始检测嵌入式开发环境 # 1. 检测 ARM 交叉编译工具链 echo -e \n[1/3] 检测 arm-linux-gnueabihf-gcc 工具链... if command -v arm-linux-gnueabihf-gcc /dev/null; then echo ✅ 工具链已安装 arm-linux-gnueabihf-gcc --version | head -n1 else echo ❌ 工具链未安装 或 未加入环境变量 echo 安装命令sudo apt install gcc-arm-linux-gnueabihf fi # 2. 检测 QEMU ARM 模拟器 echo -e \n[2/3] 检测 qemu-system-arm... if command -v qemu-system-arm /dev/null; then echo ✅ QEMU 已安装 qemu-system-arm --version | head -n1 else echo ❌ QEMU 未安装 echo 安装命令sudo apt install qemu-system-arm fi # 3. 检测 make / git 等编译依赖 echo -e \n[3/3] 检测 make、git、gcc 等依赖... if command -v make /dev/null command -v git /dev/null command -v gcc /dev/null; then echo ✅ 所有基础编译工具都正常 else echo ❌ 部分依赖缺失 echo 安装命令sudo apt install git make gcc libncurses-dev bzip2 fi echo -e \n 检测完成 只要输出结果是三个绿色√就是正常的接着往下走。3编译U-Boot使之符合vexpress-a9开发板环境由于U-Boot是全世界所有开发板平台通用的他里面有一堆我们当前开发板不需要的文件我们使用make编译选项只编译出需要的uboot即可。即对这个Makefile进行编译选项的配置、裁剪。#清理防止以前有旧文件影响 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- distclean #加载 vexpress-a9 开发板默认配置QEMU 专用 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- vexpress_ca9x4_defconfig #编译 U-Boot多核加速自动用满 CPU 核心 make V1 ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -j$(nproc)可以看到已经编译成功输出了.config文件。关于这个警告只是说你把设备树这里是U-Boot自己初始化硬件时候用的设备树不是Linux内核中的设备树放到u-boot里面去了这个在现代我们不推荐已经过时了。不过我们对于初学者而言我们不用理会。4一次失败的启动vexpress-a9与新版U-Boot的兼容性问题在命令行中输入以下命令启动QEMU。qemu-system-arm \ -M virt \ -m 512M \ -nographic \ -bios u-boot.bin但是随即发现并无任何输出于是上网查阅资料后得知新版QEMU的vexpress-a9与新版U-Boot的兼容性很差有各种各样乱七八糟的问题网上推荐我使用virt虚拟开发板来平替之前的vexpress-a9。输入以下命令重新xuanzeU-Boot对应开发板并且编译ls configs/qemu_arm_defconfig make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- distclean make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- qemu_arm_defconfig make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -j$(nproc)重新用以下新版命令启动QEMU的U-Bootqemu-system-arm \ -M virt,secureoff \ -m 512M \ -serial stdio \ -bios u-boot.bin \ -net none5现在的QEMU启动命令与之前有什么差异比如我们之前有遇到过GPIO子系统无法正常开启极有可能就是这个启动命令太过简略跳过了U-Boot的一系列初始化工作导致的而且我们编译了多次内核都无法解决可见没有U-Boot时有多难排查问题。前期的 QEMU 启动方式是「为了跑通系统而跳过核心流程的捷径」而基于 U-Boot 的启动方式是「符合行业标准、可排查、可维护、可量产的正规开发流程」。学习嵌入式 Linux必须彻底抛弃捷径从 U-Boot 的完整流程入手这是从「会用」到「懂原理、能开发」的核心转折点。而现在使用U-Boot是工作中标准的开发流程之前那种启动QEMU的方式以后坚决不可取

更多文章