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

目录

一、Linux启动流程

(1)BIOS与BootLoader的理解

(2)U-Boot:嵌入式领域的BIOS+BootLoader结合体

二、如何在QEMU上模拟启动流程?

(1)官网下载U-Boot

(2)检测工具链完整性

(3)编译U-Boot,使之符合vexpress-a9开发板环境

(4)一次失败的启动:vexpress-a9与新版U-Boot的兼容性问题

(5)现在的QEMU启动命令与之前有什么差异?


在学习Linux驱动开发的时候,我发现理解设备树、总线架构、子系统等等本身并不是难点,但是自己在编写代码的时候却经常出错,而且大多数时候是因为运行环境的问题,比如这个子系统没裁剪进来、那个依赖的子系统没有开启等等。为了解决这些问题,我决定系统的学习一遍Linux裁剪、编译的原理。

一、Linux启动流程

(1)BIOS与BootLoader的理解

在嵌入式开发中,上电启动流程是所有开发的起点,也是很多开发者最容易混淆的核心概念。我最开始会把 BIOS、Bootloader、startup 文件、main 函数混为一谈,甚至搞不清 STM32 裸机开发和带 Linux 系统的嵌入式开发在启动逻辑上的本质区别。

BIOS就类比STM32的startup.s启动文件,他只做最基本的初始化,比如初始化时钟、堆栈指针等等,然后在最后一行跳转到main函数去执行裸机代码。

startup.s 的作用:完全对应 PC 的 BIOS,只做三件事:

  1. 初始化堆栈指针(SP)
  2. 初始化中断向量表
  3. 跳转到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中的操作系统代码拷贝到内核中,最后将控制权交给操作系统内核。

Bootloader(U-Boot):核心的「搬运 + 跳转」环节,干三件事:

  • 初始化必要外设:DDR 内存、SD 卡 / EMMC、Flash 等存储设备,为系统运行搭建硬件环境
  • 搬运系统镜像:把 SD 卡 / EMMC 中的 Linux 内核镜像、设备树文件,从 Flash 搬运到 DDR 内存中
  • 跳转执行:跳转到内存中的 Linux 内核入口地址,把控制权完全交给操作系统

(2)U-Boot:嵌入式领域的BIOS+BootLoader结合体

在嵌入式 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-Boot)

Index of /pub/u-boot/在这个链接下可以找到所有版本的U-Boot。将其放到与Linux内核同层级处:

解压缩并进入U-Boot的目录中:

cpp 复制代码
# 解压.bz2格式的源码包
tar -jxvf u-boot-2026.04.tar.bz2

(2)检测工具链完整性

首先检测一下你的工具链、QEMU是否正常安装好了:

cpp 复制代码
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进行编译选项的配置、裁剪。

cpp 复制代码
#清理,防止以前有旧文件影响
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean

#加载 vexpress-a9 开发板默认配置(QEMU 专用)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_ca9x4_defconfig

#编译 U-Boot(多核加速,自动用满 CPU 核心)
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)

可以看到已经编译成功,输出了.config文件。关于这个警告只是说:你把设备树(这里是U-Boot自己初始化硬件时候用的设备树,不是Linux内核中的设备树)放到u-boot里面去了,这个在现代我们不推荐,已经过时了。不过我们对于初学者而言,我们不用理会。

(4)一次失败的启动:vexpress-a9与新版U-Boot的兼容性问题

在命令行中输入以下命令,启动QEMU。

cpp 复制代码
qemu-system-arm \
  -M virt \
  -m 512M \
  -nographic \
  -bios u-boot.bin

但是随即发现并无任何输出,于是上网查阅资料后得知:新版QEMU的vexpress-a9与新版U-Boot的兼容性很差,有各种各样乱七八糟的问题,网上推荐我使用virt虚拟开发板,来平替之前的vexpress-a9。

输入以下命令,重新xuanzeU-Boot对应开发板,并且编译:

cpp 复制代码
ls configs/qemu_arm_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- qemu_arm_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)

重新用以下新版命令,启动QEMU的U-Boot:

cpp 复制代码
qemu-system-arm \
  -M virt,secure=off \
  -m 512M \
  -serial stdio \
  -bios u-boot.bin \
  -net none

(5)现在的QEMU启动命令与之前有什么差异?

比如我们之前有遇到过GPIO子系统无法正常开启,极有可能就是这个启动命令太过简略,跳过了U-Boot的一系列初始化工作导致的,而且我们编译了多次内核都无法解决,可见没有U-Boot时有多难排查问题。

前期的 QEMU 启动方式,是「为了跑通系统而跳过核心流程的捷径」;而基于 U-Boot 的启动方式,是「符合行业标准、可排查、可维护、可量产的正规开发流程」。学习嵌入式 Linux,必须彻底抛弃捷径,从 U-Boot 的完整流程入手,这是从「会用」到「懂原理、能开发」的核心转折点。

而现在使用U-Boot是工作中标准的开发流程,之前那种启动QEMU的方式以后坚决不可取!

相关推荐
爱签AI电子合同1 小时前
电子合同上手成本怎么测?易用性维度专项测评
服务器·人工智能·智能合约·企业微信
aixingkong9213 小时前
Nvidia为例 XPU的Multi-Host、Socket Direct 、GPUDirect、RDMA 技术点分析
服务器·网络·硬件架构·硬件工程
雯宝6 小时前
mnt_init 函数
linux
律宏阔11 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔12 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴12 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千12 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei3712 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei3712 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei3712 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化