Linux嵌入式x86/ARM中的Bootloader基本概念与启动流程解析
1 概述
引导加载程序Bootloader 是嵌入式系统与计算机系统上电启动过程中最先运行的可定制代码段,其核心职责包含多个固定执行环节:
- 首先完成处理器与核心外设的基础硬件初始化操作,包括时钟树配置、供电模式设定、外部存储器接口初始化等系统运行必需的底层配置。
- 随后从指定的启动源中加载核心的可执行二进制文件,这类文件通常为操作系统内核镜像,启动源包括flash、SD 卡、eMMC、以太网网络或 USB 存储设备等非易失性存储载体。对于经过压缩处理的内核镜像,引导加载程序需要在加载完成后执行解压缩操作,将镜像恢复为可执行状态。
- 最终将系统执行权转交给解压完成的内核镜像,完成系统启动的引导环节。
在基础启动功能之外,当前主流的引导加载程序均会提供交互式操作界面,包括命令行 Shell 与图形化菜单两种形式。通过该界面可以手动选择待启动的操作系统镜像、调整启动参数、从存储或网络中读取与写入数据、检测物理内存状态、执行硬件外设诊断与寄存器读写操作。
如有专业词汇翻译不当、内容理解有误的地方,还望指正。
2 x86 平台启动流程
2.1 传统 BIOS 启动
2005 至 2006 年之前设计生产的 x86 架构计算机平台,均采用 BIOS 作为系统固件,BIOS 全称 Basic Input Output System,即基本输入输出系统。BIOS属于硬件平台的固有组成部分,由主板厂商采用闭源方式开发,用户无法进行源码级修改,仅能通过有限的选项进行参数调整。BIOS 的核心功能是实现硬件上电启动流程,同时提供一组运行时服务接口,但这类接口在现代操作系统中几乎不会被调用。BIOS镜像常见于存储在主板独立的闪存芯片中,与用户可访问的硬盘、U 盘等存储设备相互隔离,不存在物理地址重叠。
在 x86 传统启动规范中,具备可引导属性的存储设备,其物理地址的第一个扇区被定义为主引导记录,即 MBR,Master Boot Record。该扇区内部包含三个关键部分,分别是磁盘分区表、磁盘签名信息以及引导加载程序可执行代码。BIOS 完成硬件自检与内存初始化后,会自动读取该扇区数据,并将其中的引导代码加载至内存指定位置执行。
受限于 MBR 中引导代码的字节长度限制,无法存放完整功能的引导加载程序,因此 x86 平台的引导程序必须采用两级加载结构:
- 第一阶段引导程序即 Stage1,体积严格控制在限制字节以内,仅实现存储介质识别与第二阶段引导程序加载功能。
- 第二阶段引导程序即 Stage2,由 Stage1 从存储设备的固定地址加载执行,该程序体积不受严格限制,具备完整的文件系统解析能力,可以直接从文件系统中读取内核镜像与启动配置文件。
2.1.1 启动顺序与存储布局
传统 BIOS 启动遵循固定的硬件执行流程:
- 第一步由主板上的 BIOS 固件从内置闪存中运行,执行硬件自检并完成物理内存的初始化配置,确保内存可正常读写。
- 第二步从可引导存储设备的 MBR 区域加载 Stage1 引导程序至内存,处理器跳转执行该段代码。
- 第三步由 Stage1 程序加载存储设备中预设地址的 Stage2 引导程序,该程序运行后提供完整的交互与文件系统操作能力。
- 第四步 Stage2 引导程序从文件系统中读取 Linux 内核镜像至内存,完成启动参数传递后跳转执行内核代码。
在存储设备物理布局上,0 号扇区即 MBR 扇区,存放分区表与 Stage1 引导代码,该区域不归属任何文件系统管理。后续的连续物理扇区用于存放 Stage2 引导程序,同样不纳入文件系统管理范围。磁盘分区完成后,每个分区内部可存放 Linux 内核镜像、根文件系统与系统配置数据,形成完整的可启动系统结构。
2.2 UEFI 启动
2005 至 2006 年之后,x86 平台逐步采用 UEFI 替代传统 BIOS,UEFI 全称 Unified Extensible Firmware Interface,即统一可扩展固件接口。UEFI 重新定义了操作系统与系统固件之间的交互规范,不仅负责硬件启动与初始化,还为操作系统提供运行时服务、电源管理与硬件配置接口。UEFI 固件同样存储在主板独立闪存中,与用户存储设备相互隔离,具备模块化与可扩展的设计特性。
UEFI 启动流程不再依赖 MBR 扇区,而是采用专用的 EFI 系统分区,该分区为独立的物理分区,采用 FAT32 文件系统格式。在 MBR 分区表中,该分区类型标识为 0xEF,在 GPT 分区表中使用专属的全局唯一标识符进行区分。UEFI 固件会自动扫描磁盘中的 EFI 系统分区,加载分区内预设路径的 EFI 格式可执行文件,该文件可以是完整的引导加载程序,也可以是内置 EFI 启动支持的 Linux 内核镜像。
2.2.1 启动顺序与存储布局
- UEFI 启动的第一步为固件从主板闪存运行,完成硬件初始化与内存配置,同时枚举所有存储设备。
- 第二步固件识别 EFI 系统分区,解析 FAT32 文件系统并加载指定路径的 EFI 可执行文件,该文件通常为 GRUB 等引导加载程序。
- 第三步引导加载程序运行后,从普通分区的文件系统中读取 Linux 内核镜像与初始内存文件系统,完成参数配置后启动内核。
在存储布局上,EFI 系统分区为独立的物理分区,仅存放引导程序与启动配置,不存储系统与用户数据。其余的普通数据分区用于存放 Linux 内核、根文件系统、应用程序与用户文件,各分区之间物理隔离,文件系统格式可自由选择。
2.3 ACPI
ACPI 全称 Advanced Configuration and Power Interface,即高级配置与电源接口,是一项跨平台的开放行业标准。该标准为操作系统提供统一的硬件管理接口,支持硬件设备的自动发现、资源分配、运行状态监测、功耗控制与热插拔管理。ACPI 的核心实现方式为固件向操作系统提供一组固定格式的描述表,这些表中记录了硬件资源布局、电源管理策略、设备中断映射等信息,这类信息无法在运行时通过硬件枚举动态获取。
在 x86 平台中,无论是传统 BIOS 还是 UEFI 固件,均会提供 ACPI 描述表,Linux 内核启动时会自动解析 ACPI 表,完成硬件资源管理与电源策略配置。在嵌入式 ARM 平台中,ACPI 仅用于服务器级别的处理器,嵌入式消费类设备极少采用该标准。
2.4 ARM 平台的 UEFI 与 ACPI
UEFI 与 ACPI 均起源于 x86 计算机体系,ARM 联盟为了统一服务器与高端设备的启动规范,将这两项技术纳入 ARM System Ready 认证体系。该认证仅面向服务器、工业控制主机与高端工作站级别的 ARM SoC,要求固件提供完整的 UEFI 交互界面与 ACPI 硬件描述能力。
在嵌入式 ARM Linux 系统中,例如消费电子、工控设备、开发板等产品形态,UEFI 与 ACPI 均不具备实用性,行业普遍采用 U-Boot 与设备树的组合方案。RISC-V 架构的 UEFI 支持仍处于社区开发阶段,尚未形成稳定的行业标准,因此嵌入式 RISC-V 平台同样以 U-Boot 作为默认引导加载程序。对于采用 UEFI 的嵌入式平台,其启动流程、分区规范与文件系统要求,均与 x86 平台保持完全一致。
3 嵌入式平台启动流程
3.1 ROM 代码
嵌入式处理器在芯片生产阶段,会内置一段固化代码,即 ROM code,该代码是处理器上电后执行的第一段程序,属于芯片硬件的固有组成部分,用户无法进行任何修改、擦除或升级操作。ROM code 的执行逻辑与启动流程,由芯片厂商在数据手册中明确规定,启动源选择、加载地址与执行顺序均为固定配置。
ROM code 的核心功能是自动枚举预设的启动设备列表,依次检测是否存在有效的引导加载程序,检测范围包括 NAND Flash、NOR Flash、SD 卡、eMMC、USB 设备与以太网等。由于处理器外部的 DDR 内存尚未完成初始化,ROM code 只能将引导加载程序加载至芯片内置的 SRAM 中运行,而内置 SRAM 的容量通常仅为几十 KB,这一限制直接决定了第一阶段引导加载程序必须保持极小体积。基于上述硬件约束,嵌入式平台的启动流程必须划分为两个阶段,第一阶段引导程序用于初始化外部 DDR,第二阶段引导程序运行于 DDR 中,提供完整的启动功能。
3.2 ROM 代码恢复机制
绝大多数嵌入式处理器的 ROM code 均内置系统恢复功能,该机制用于处理引导加载程序丢失、损坏或启动失败的场景。恢复模式通过特定的硬件引脚组合触发,进入模式后,ROM code 会开启 UART 或 USB 通信接口,接收主机端发送的引导加载程序固件,直接加载至内存并执行,实现系统重新烧录。
不同厂商的处理器有对应的专用烧录工具,STM32MP1 系列使用 STM32 Cube Programmer,NXP i.MX 系列使用 uuu 工具,Microchip AT91/SAM 系列使用 SAM-BA,全志系列使用 sunxi-fel 工具。这类工具部分为开源软件,部分为闭源专有工具,使用方式与通信协议互不兼容。
3.3 两级启动流程
嵌入式平台的两级启动为固定执行流程,所有 ARM 与 RISC-V 嵌入式处理器均遵循该规范。
- 第一步由 ROM code 从选定的启动设备中读取第一阶段引导加载程序,加载至芯片内置 SRAM 并开始执行。
- 第二步第一阶段引导程序运行后,执行 DDR 控制器寄存器配置、时序调节与内存检测,完成外部 DDR 内存的完整初始化,使系统具备大容量内存运行能力。
第一阶段引导程序从启动设备中读取体积更大的第二阶段引导加载程序,加载至已初始化完成的 DDR 内存中,随后跳转执行该程序,由第二阶段引导程序完成后续的内核加载与系统启动。
4 常见引导加载程序简介
4.1 GRUB
GRUB 全称 Grand Unified Bootloader,是 GNU 项目开发的通用引导加载程序,也是所有桌面与服务器版 Linux 发行版在 x86 平台上的默认引导方案。GRUB 同时兼容传统 BIOS 与 UEFI 两种固件环境,支持 Ext4、XFS、Btrfs、FAT 等主流文件系统,提供图形化启动菜单与命令行交互 Shell,支持通过以太网使用 TFTP 协议远程加载内核镜像。
GRUB 具备跨架构支持能力,可用于 ARM、ARM64、RISC-V、PowerPC 等平台,但在嵌入式领域,其体积、启动速度与配置复杂度均不适合资源受限的设备,因此嵌入式 Linux 系统几乎不会采用 GRUB,而是使用更轻量的 U-Boot。
4.2 Syslinux
Syslinux 是一款专注于可移动介质与简易启动场景的引导加载程序套件,由多个独立组件构成,分别对应不同的启动介质。syslinux 组件用于从 FAT 文件系统的 U 盘与 SD 卡启动,pxelinux 组件用于网络 PXE 启动,isolinux 组件用于光盘启动,extlinux 组件支持从 Ext 系列文件系统启动。
Syslinux 的配置文件格式简洁,编译与部署流程简单,适合用于制作启动盘与恢复系统。但该项目的维护活跃度较低,硬件支持范围有限,不具备嵌入式系统所需的驱动扩展能力,因此仅用于辅助启动场景,不作为产品级引导加载程序使用。
4.3 systemd-boot
systemd-boot 是一款轻量级 UEFI 引导管理器,属于 systemd 系统服务套件的独立组件,与 systemd init 系统无运行时依赖。该引导程序仅支持 UEFI 环境,设计目标为极简启动流程,所有配置参数均存放于 EFI 系统分区的文本文件中,无需额外的文件系统解析模块。
systemd-boot 的体积小、启动速度快,适合 UEFI 平台的极简启动需求,但功能高度精简,不支持内核参数动态编辑、网络启动与复杂存储介质识别,仅作为 GRUB 的轻量化替代方案,在嵌入式领域使用范围有限。
4.4 shim
shim 是专为安全启动 Secure Boot 设计的最小化 UEFI 引导加载程序,其核心作用是通过微软官方签名认证,使非 Windows 操作系统可在开启安全启动的 UEFI 平台上运行。shim 自身不提供内核加载功能,仅用于验签并链式加载 GRUB 或 Linux 内核,确保启动链中的每一个组件均具备合法签名。
在嵌入式平台中,安全启动功能通常由芯片厂商与 TF-A 可信固件实现,shim 多用于 PC 与服务器环境,嵌入式 Linux 产品极少使用该组件。
4.5 U-Boot
U-Boot 全称 Universal Bootloader,是 ARM、ARM64、RISC-V、PowerPC、MIPS 等全部嵌入式架构的事实标准引导加载程序,同时支持搭载 UEFI 固件的 x86 平台。所有嵌入式 SoC 厂商、核心板模块厂商与开发板厂商,均将 U-Boot 作为默认的引导解决方案,是嵌入式 Linux 系统开发中必备的核心组件。
U-Boot 采用 GPLv2 开源协议,源码托管于 denx 社区的 Git 仓库,社区通过公开邮件列表进行代码提交与技术讨论,项目保持稳定的发布节奏,每 2 至 3 个月发布一个正式版本,版本号采用年份加月份的格式,例如 2024.07。
The U-Boot Archiveslists.denx.de/pipermail/u-boot/
4.5.1 源码获取
U-Boot 源码的获取优先级明确划分,最优选择为官方上游主线版本,即直接从 denx 社区仓库获取的代码,该版本经过全球开发者的审核与测试,硬件支持规范、驱动质量稳定、可长期同步更新。次优选择为芯片厂商或主板厂商提供的分叉版本,这类版本通常基于旧版主线修改,仅适配特定硬件,不兼容主线更新,代码质量与安全性无法保证。
对于自主设计硬件产品的场景,需要基于上游主线 U-Boot 进行移植适配,仅当厂商硬件在主线暂无支持时,才可临时使用厂商分叉版本作为过渡方案。
4.5.2 配置
U-Boot 采用与 Linux 内核完全一致的 kconfig 配置系统,所有可配置项以菜单形式呈现。源码目录下的 configs 文件夹存放所有官方支持的开发板默认配置文件,单个配置文件可适配同一系列、同一型号 SoC 的多款硬件产品。配置项包含处理器架构、外设驱动、命令集、网络功能、文件系统支持、启动策略等全部运行参数。
U-Boot 的标准配置流程分为两步:
- 首先加载开发板对应的默认配置,执行 make 命令后接开发板配置名称与 defconfig 后缀,生成初始配置文件。
- 随后执行 make menuconfig 命令打开字符配置界面,根据硬件需求与产品功能,对驱动、命令与功能模块进行自定义开启或关闭。
4.5.3 编译
U-Boot 必须采用交叉编译方式生成可执行镜像,编译前需要将交叉编译器路径加入系统环境变量,并通过 CROSS_COMPILE 变量指定交叉编译器前缀。完整编译命令为在加载配置后,执行 make 命令并传入 CROSS_COMPILE 参数,编译过程会自动完成链接、打包与镜像生成。
编译输出的核心文件为 u-boot.bin,这是 U-Boot 的原始二进制镜像。
4.5.4 U-Boot SPL
SPL 全称 Secondary Program Loader,即第二阶段程序加载器,是 U-Boot 的精简裁剪版本。SPL 的设计目标是满足嵌入式处理器的第一阶段引导程序体积限制,代码仅保留 DDR 初始化与第二阶段镜像加载功能,不包含任何交互命令、文件系统与网络模块,所有执行逻辑通过 C 代码固化实现。
部分复杂硬件平台需要三级启动流程,即在 SPL 之前增加更小体积的 TPL,即 Tertiary Program Loader,用于初始化最基础的时钟与供电,再加载 SPL 执行。三级启动多用于多核异构、安全启动的高端嵌入式平台。
4.5.5 U-Boot 中的设备树
设备树 Device Tree 是用于描述硬件拓扑结构的数据结构,最初由 PowerPC 平台使用,现已成为全部嵌入式架构的硬件描述标准。U-Boot 在绝大多数硬件平台上均依赖设备树完成硬件识别与驱动加载。
自 2024.07 版本开始,U-Boot 的设备树文件路径由 CONFIG_OF_UPSTREAM 配置项控制,开启该选项时,设备树存放于 dts/upstream/src 目录下对应架构与厂商的子目录,未开启则存放于 arch/架构名/dts 目录。每一款硬件开发板均对应独立的设备树源文件 dts,编译时可通过 DEVICE_TREE 变量指定使用的设备树,覆盖默认配置。
4.5.6 安装
U-Boot 的安装部署方式由启动介质类型决定。对于 SD 卡、U 盘等外部可移动存储,直接将编译生成的镜像写入存储设备的指定物理偏移地址即可。对于 eMMC、NAND Flash 等板载存储设备,可使用烧录工具或厂商专有工具完成烧录。对于具备 JTAG 接口的硬件平台,可通过 JTAG 调试器直接写入闪存,但该方法流程复杂、操作成本高,仅在无其他烧录方式时使用。
4.5.7 Shell 与命令
U-Boot 通过串口控制台提供独立的命令行交互 Shell,该 Shell 的命令集与 Linux 系统完全无关,所有命令均为 U-Boot 内部实现。直接输入 help 命令可列出当前编译配置下所有可用的命令列表,输入 help 后接具体命令名称,可查看该命令的详细用法、参数格式与使用示例。
U-Boot 提供一组硬件信息查询命令,用于查看系统状态:
- version 命令显示当前 U-Boot 的版本号、编译时间与交叉编译器信息;
- nand info 命令显示 NAND Flash 的容量、块大小、页大小等物理参数;
- mmc info 命令显示 MMC/eMMC 设备的厂商、容量、总线宽度与工作模式;
- bdinfo 命令显示内存起始地址、内存大小、波特率、重定位地址等核心硬件参数。
此外还有很多相关命令,比如操作SPI 、I2C、WDT控制器的命令,不再一一列举。
4.5.8 环境变量
U-Boot 环境变量是运行时配置的核心载体,以键值对形式存储,部分变量用于控制命令执行逻辑与启动策略,部分变量可由用户自定义,用于存储启动脚本与参数。环境变量默认编译至 U-Boot 二进制文件中,运行时加载至内存,修改操作仅在内存生效,断电后丢失,需要执行保存命令写入非易失性存储后才可持久化。
环境变量的持久化存储位置可通过编译配置指定,支持存放在 NAND Flash 的固定偏移地址、MMC 设备的固定偏移地址、FAT 或 ext4 文件系统中的特定文件,也可配置为不进行持久化存储,仅使用编译内置的默认变量。
环境变量的操作命令包含五类:
- printenv 命令用于打印全部变量或指定变量的值;
- setenv 命令用于设置或新建变量,仅在内存生效;
- editenv 命令用于交互式编辑变量内容;
- saveenv 命令用于将内存中的环境变量写入持久化存储;
- env 命令包含一组子命令,支持恢复默认配置、查询存储状态、擦除环境变量等操作。
4.5.9 内存与存储操作
U-Boot 直接使用物理地址进行内存读写操作,因为此时内核还未启动、MMU 未开启,从而不存在虚拟地址映射。
U-Boot 未提供内置的内存分配机制,开发者需要通过 bdinfo 命令获取内存起始地址与总大小,手动避开 U-Boot 自身代码与数据占用的内存尾部区域,选择可用的物理地址段进行数据加载与存储。
U-Boot 的内存操作命令包含三类:md命令用于按字节、半字、字、双字格式显示内存区域内容,mw命令用于向指定物理地址写入数据,mm命令用于从指定地址开始交互式逐地址修改内存。
4.5.10 网络功能
U-Boot 的网络功能依赖三个核心环境变量:ethaddr指定硬件网卡的 MAC 地址,ipaddr设置开发板自身的静态 IP 地址,serverip设置远程 TFTP 服务器的 IP 地址。
U-Boot 提供基础的网络命令,ping 命令用于测试与远程主机的网络连通性,受限于单线程执行机制,仅支持 U-Boot 主动 ping 外部主机,外部主机无法 ping 通 U-Boot。dhcp 命令通过 DHCP 协议自动获取 IP 地址、子网掩码、网关与 DNS 地址。tftp 命令为核心网络加载命令,通过 TFTP 协议从服务器下载文件至指定内存地址。
4.5.11 脚本与内核启动
U-Boot 环境变量支持存储可执行脚本,多个命令之间使用分号分隔,支持 if-then-else 条件判断结构,可引用其他环境变量作为参数。脚本编写完成后,使用 run 命令后接变量名即可直接执行,常用于自动化启动流程、批量烧录镜像与硬件自动化测试。
U-Boot 提供架构专属的内核启动命令,ARM32 平台常常使用bootz命令启动压缩格式的 zImage 镜像,ARM64 与 RISC-V 平台常常使用booti命令启动未压缩的 Image 镜像,传统兼容模式使用bootm命令启动带 U-Boot 专用头的内核镜像,x86 平台使用zboot命令启动压缩格式的 bzImage 镜像。
启动命令的标准格式为内核启动命令后接内核镜像地址、initramfs 地址、设备树地址,三个参数依次排列,无 initramfs 时使用短横线占位。控制启动行为的两个核心环境变量为 bootcmd 与 bootargs,bootcmd 存储自动启动执行的命令序列,倒计时结束后自动运行,bootargs 存储传递给 Linux 内核的命令行参数。
4.6 Barebox
Barebox 是一款面向嵌入式架构的开源引导加载程序,设计初衷为解决 U-Boot 的历史技术债务与设计缺陷。Barebox 采用与 Linux 内核完全一致的 kconfig 配置系统,内部实现规范化的设备模型,命令行 Shell 与 Linux 系统保持高度相似,代码结构简洁模块化,社区维护活跃度较高。
Barebox 支持 ARM/ARM64、MIPS、PowerPC、RISC-V、x86 等主流架构,驱动框架与设备树集成方案优于早期版本的 U-Boot。但由于 U-Boot 已形成完整的行业生态,芯片厂商与硬件厂商的默认支持均以 U-Boot 为主,Barebox 的硬件支持广度、社区资料与工具链成熟度均低于 U-Boot,因此仅在特定专用设备中使用。
Bareboxwww.barebox.org/
