HomeArchiveBlog


Original contents are licensed under CC BY-NC 4.0. All rights reserved © 2026 Kai.
Back to Archives
Embedded Linux Boot Process

TOBEFILLED

Fri Nov 28 2025
Sat Jan 03 2026
Embedded LinuxBoot ProcessU-BootKernelDevice Tree
On this page
  • Embedded Linux Boot Process
    • 启动阶段
    • 加载 BootROM
    • 加载 FSBL
    • 加载 U-Boot
    • 加载 Linux 内核
    • 切换到根文件系统
    • 启动用户空间

Embedded Linux Boot Process

嵌入式 Linux 和 PC 上的 Linux 在启动的方式上有很多不同. 嵌入式设备上通常会少很多硬件资源, 比如没有独立的 BIOS 芯片 (跑不了UEFI), 架构并非 X86 (没有 ACPI). 因此嵌入式 Linux 的启动过程有很多特别的阶段. 在 Petalinux 的配置中如果弄不明白这些阶段, 可能难以定位错误所在. 本文主要介绍 Xilinx 设备上嵌入式 Linux 的启动过程, 对其他 ARM 架构的嵌入式 Linux 可能有一些参考价值 (因为我没怎么做过其它的 😇 )

启动阶段

嵌入式 Linux 的启动大致可以分为以下几个阶段:

  1. 系统上电 (Power On), 执行 BootROM 代码
  2. BootROM 加载并并执行 FSBL (First Stage Boot Loader)
  3. FSBL 完成设备最基本的初始化, 加载并执行 U-Boot
  4. U-Boot 初始化更多的硬件设备, 加载设备树, 临时文件系统, Linux 内核镜像到内存里.
  5. Linux 内核加载临时文件系统 (initrd) 和必要的驱动模块.
  6. 找到真正的根文件系统, 切换到根文件系统 RootFS.
  7. 执行 init 进程, 启动用户空间的各种服务和应用程序. 到此正式进入用户空间.

看起来流程挺长的, 接下来详细聊聊每个阶段 🤓

加载 BootROM

首先是一个经典的“鸡生蛋”的问题:

系统刚上电之后 CPU 没有任何代码可以执行. 理论上来讲 CPU 需要从内存里面取指令, 但这个时候内存是空的. 而指令都存放在储存设备里面, CPU 不能直接从储存设备取指令.

因此 CPU 在设计的时候会内置一段非常小的 ROM, CPU 上电之后复位会将 PC 指向这段 ROM 的起始地址 (例如 0xFFFF0000), 这段 ROM 就是 BootROM, 出厂时就已经硬性写入 CPU 芯片里面了. 它首先做一些自检工作, 然后检查 Boot 引脚的设置决定从哪一个设备加载下一个阶段的引导 (例如 001 表示从 QSPI Flash 加载, 010 表示从 SD 卡加载). BootROM 里面自带了一些最基本的通信协议的实现, 可以实现最基本的读取功能, 然后从指定的设备中读取下一个引导阶段的代码到内存中, 并跳转执行.

加载 FSBL

BootROM 读取的下一个引导阶段通常是 FSBL (First Stage Boot Loader). FSBL 是一个非常小的引导程序, 它的主要任务是完成系统最基本的初始化工作, 例如初始化系统时钟 (PLL), 初始化 DDR 控制器. FSBL 存在的意义是, BootROM 代码非常受限, 而下一个阶段的引导程序 U-Boot 又比较大 (存在 eMMC/SD 卡等储存设备中), 直接由 BootROM 加载 U-Boot 到内存里面是不现实的 (BootROM 甚至不会初始化 DDR). 因此 FSBL 作为一个中间层, 负责完成 BootROM 和 U-Boot 之间的过渡工作.

一般来说 FSBL 并不存放在 DDR 里面, 它是被 BootROM 直接加载到片上 SRAM (On-Chip Memory) 里面执行的, 这也可以解释为什么必须有 FSBL 这个阶段, 片上 SRAM 的容量非常有限, 只能存极少的代码.

在 Xilinx 的 Zynq/MPSoC 系列上, FSBL 可以在 Vitis 里面生成. 对于 Versal 还有后面的高端器件, FSBL 被整合进了 PLM (Platform Loader and Manager) 模块里面.

在 Petalinux 的构建结果中, FSBL 一般被命名为 fsbl.elf 或者 pmufw.elf, 也可能被打包到 BOOT.BIN 里面.

加载 U-Boot

相比前两个组件, U-Boot 的功能就强大很多了. U-Boot 负责初始化更多的硬件设备, 比如外置储存设备 (SD/eMMC/Flash), 网络设备 (Ethernet/WiFi), 串口等等. 它提供了一个命令行界面, 可以通过串口终端进行交互, 比如设置环境变量, 打印启动参数, 手动加载内核镜像, 选择启动方式等等. 它是一个完整的引导加载程序, 带了很多硬件的驱动.

初始化硬件之后 U-Boot 会把 Linux 内核镜像, 设备树 (Device Tree), 临时文件系统 (initrd) 等内容加载到内存中, 并将控制权交给 Linux 内核.

内核的原始存放位置可以很多, 比如 SD 卡, eMMC, QSPI Flash, 甚至是网络加载 (TFTP). U-Boot 通过环境变量来配置内核镜像和设备树的存放位置, 以及加载到内存中的地址. 所以在 Petalinux 配置中, 你可以看到很多类似 "Device Tree Offset", "Kernel Load Address" 之类的配置项, 这些都是配置 U-Boot 的行为的.

在 Petalinux 的构建结果中, U-Boot 一般被命名为 u-boot.elf, 也可能被打包到 BOOT.BIN 里面.

加载 Linux 内核

Linux 内核被 U-Boot 加载到内存中之后, 内核开始执行它的初始化过程. 内核首先会解压缩自身 (如果是压缩的内核镜像), 然后读取设备树 (对于 ARM 设备是必须的). Linux 根据设备树了解各种硬件设备的存在和配置, 并加载相应的, 内核中自带的驱动模块.

接着内核会挂在临时文件系统 initrd 或者 initramfs (如果有的话), 为什么要有这个临时文件系统呢? 一些硬件设备的驱动可能没有被编译在 Linux 内核里面, 必须通过模块的方式加载, 例如 NVMe 驱动. 假设我们的操作系统的根文件系统放在 NVMe 设备上, 由于内核本身不带 NVMe 驱动 (nvme.ko), 而 NVMe 驱动又放在根文件系统里面, 这就又是一个“鸡生蛋”的问题了. 因此内核需要一个临时的文件系统, 里面包含必要的驱动模块, 以便内核能够访问根文件系统所在的设备.

initrd 还是目前更常见的嵌入式方案, 而 initramfs 则是一个更新的方案, 它直接将临时文件系统打包进内核镜像里面(cpio格式), 启动时内核会直接解包到内存中使用, 不需要单独加载一个文件系统镜像, 它更多用在 PC 上的 Linux 启动中.

切换到根文件系统

内核加载完临时文件系统之后, 会尝试找到真正的根文件系统 (RootFS). 首先第一个问题是, Linux 怎么知道应该从哪个设备找根文件系统呢? 这是通过内核启动参数传递的, 而这是在 U-Boot 里面被传递给内核的. U-Boot 里面有一个环境变量 bootargs, 里面包含了 root=/dev/mmcblk0p2 这样的参数, 它告诉内核根文件系统在 /dev/mmcblk0p2 这个设备上 (SD 卡的第二个分区).

根文件系统也就是所谓的 / 目录, 它包含了操作系统需要的所有文件和目录结构, 例如 /bin, /etc, /usr 等等. 它可以被放在硬盘分区, NFS 网络文件系统各种地方.

内核找到根文件系统之后, 会将根文件系统挂载为 /, 并卸载临时文件系统 initrd. 现在内核已经完全切换到真正的根文件系统上了.

启动用户空间

内核切换到根文件系统之后, 它会寻找并执行第一个用户空间进程 (PID 1), 这个进程通常是 /sbin/init 或者 /init. 这个进程负责启动用户空间的各种服务和应用程序, 它是所有用户空间进程的祖先进程 (如果学过 OS 应该有所了解). 常见的几种 init 系统有 SysVinit, systemd, BusyBox init 等等.

SysVinit 是最传统的 init 系统, 它通过一系列的脚本来启动各种服务. 比如在 /etc/rc.d/ 目录下有很多启动脚本, init 根据脚本的命名顺序来启动服务, 缺点就是慢, 因为脚本是串行的, 而且管理起来不方便. systemd 就是更现代的 init 系统, 基本上所有的 PC 端 Linux 发行版现在都用 systemd 了. 它管理起来很方便, 但缺点是有点太庞大了.

到这里, 嵌入式 Linux 的启动过程就完成了, 系统进入了用户空间, 可以开始运行各种应用程序了.