从 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的方式以后坚决不可取!

相关推荐
三思守心2 小时前
从 0 到 1 搭建自动化内容工厂:深度测评楼兰AI及其在全平台发帖中的表现
运维·服务器·自动化
草莓熊Lotso2 小时前
【Linux 线程进阶】进程 vs 线程资源划分 + 线程控制全详解
java·linux·运维·服务器·数据库·c++·mysql
ShineWinsu2 小时前
对于Linux:文件操作以及文件IO的解析
linux·c++·面试·笔试·io·shell·文件操作
ZKNOW甄知科技2 小时前
数智同行:甄知科技2026年Q1季度回顾
运维·服务器·人工智能·科技·程序人生·安全·自动化
-SGlow-2 小时前
Linux相关概念和易错知识点(52)(基于System V的信号量和消息队列)
linux·运维·服务器
jikemaoshiyanshi2 小时前
B2B企业GEO服务商哪家好?深度解析径硕科技(JINGdigital)及其JINGEO产品为何是首选
大数据·运维·人工智能·科技
江畔何人初2 小时前
TCP的三次握手与四次挥手
linux·服务器·网络·网络协议·tcp/ip
跨境麦香鱼2 小时前
Playwright vs Puppeteer:2026自动化任务与爬虫工具如何选?
运维·爬虫·自动化
洒家肉山大魔王2 小时前
PKI/CA X.509证书的基础应用与解读
服务器·https·密码学·数字证书