Linux U-Boot 与内核启动流程深度解析:从上电到 Shell 的完整之旅
前言
嵌入式 Linux 系统的启动过程是一个复杂而精密的工程,涉及硬件初始化、引导加载程序、内核解压、设备驱动、根文件系统挂载等多个阶段。U-Boot (Universal Bootloader) 作为最流行的开源引导加载器,在这一过程中扮演着至关重要的角色。
本文将从硬件上电开始,详细解析 U-Boot 和 Linux 内核的启动流程,帮助读者深入理解嵌入式系统的启动机制。
一、系统启动全景图
1.1 启动阶段划分
嵌入式 Linux 系统的启动过程可以划分为以下五个阶段:
上电复位
ROM/固件代码
U-Boot SPL
U-Boot 主程序
Linux 内核
根文件系统
Shell/Init进程
| 阶段 | 名称 | 主要功能 | 执行位置 |
|---|---|---|---|
| 1 | 硬件初始化 | CPU、内存、时钟基础配置 | 芯片内 ROM |
| 2 | SPL/TPL | 最小化初始化,加载 U-Boot | SRAM/DDR |
| 3 | U-Boot | 硬件全面初始化,加载内核 | DDR |
| 4 | Linux 内核 | 系统初始化,驱动加载 | DDR |
| 5 | 根文件系统 | 用户空间启动 | DDR |
二、U-Boot 启动流程详解
2.1 U-Boot 概述
U-Boot (Das U-Boot) 是遵循 GPL 条款的开放源代码项目,由 DENX 软件工程中心的 Wolfgang Denk 创建。它支持多种架构(ARM、PowerPC、MIPS、x86 等)和多种板卡。
U-Boot 的核心功能:
- 硬件初始化(CPU、内存、外设)
- 引导操作系统加载
- 提供交互式命令行
- 支持网络启动(TFTP、NFS)
- 支持多种文件系统(FAT、EXT4、JFFS2)
2.2 U-Boot 启动流程
U-Boot 的启动过程可以分为两个阶段:
阶段 1:SPL (Secondary Program Loader)
c
// arch/arm/cpu/armv7/start.S
_start:
b reset
b undefined_instruction
b software_interrupt
b prefetch_abort
b data_abort
b not_used
b irq
b fiq
reset:
/* 保存 CPU ID 到 r9 */
mrc p15, 0, r9, c0, c0, 0
/* 关闭中断 */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
/* 初始化栈指针 */
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
/* 跳转到 lowlevel_init */
bl lowlevel_init
SPL 主要任务:
- 初始化 CPU 核心模式
- 设置栈指针
- 初始化 PLL 和时钟
- 初始化 DDR 内存控制器
- 从存储介质加载 U-Boot 主程序
阶段 2:U-Boot 主程序
c
// common/board_f.c
void board_init_f(ulong boot_flags)
{
/* 全局数据初始化 */
memset(&gd, 0, sizeof(gd));
/* 初始化硬件 */
arch_cpu_init();
initf_malloc();
initf_bootstage();
/* 串口初始化 */
serial_init();
printf("U-Boot SPL %s (%s %s)\n", PLAIN_VERSION,
U_BOOT_DATE, U_BOOT_TIME);
/* DDR 初始化 */
dram_init();
/* 重新定位到 DDR */
board_init_r(gd, 0);
}
2.3 U-Boot 命令体系
U-Boot 提供了丰富的命令集:
| 命令类别 | 常用命令 | 功能描述 |
|---|---|---|
| 内存操作 | md, mw, mm, cmp | 内存显示/修改/比较 |
| 存储操作 | nand, mmc, sf | NAND/Flash/EEPROM 操作 |
| 网络操作 | ping, tftp, nfs | 网络测试和文件传输 |
| 启动操作 | bootm, bootz, bootx | 启动各种格式的内核 |
| 环境变量 | printenv, setenv, saveenv | 环境变量管理 |
| 设备操作 | usb, ide, scsi | 外部设备操作 |
三、U-Boot 启动 Linux 内核
3.1 启动命令详解
U-Boot 使用 bootm 或 bootz 命令启动 Linux 内核:
bash
# U-Boot 环境变量配置
setenv bootcmd 'tftp 0x82000000 uImage; tftp 0x84000000 rootfs.ext4.gz; bootm 0x82000000'
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rw'
saveenv
3.2 内核镜像格式
Linux 内核有多种镜像格式:
| 格式 | 描述 | U-Boot 命令 |
|---|---|---|
| Image | 原始 vmlinux 二进制 | booti |
| zImage | 自解压压缩内核 | bootz |
| uImage | U-Boot 包装的镜像 | bootm |
| Image.gz | gzip 压缩的 Image | booti |
3.3 设备树 (Device Tree)
设备树是描述硬件配置的数据结构:
dts
// arch/arm/boot/dts/myboard.dts
/dts-v1/;
/ {
model = "My Custom Board";
compatible = "myvendor,myboard";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512MB */
};
uart@uart0 {
compatible = "ns16550a";
reg = <0x01c28000 0x1000>;
interrupts = <0 254>;
clock-frequency = <24000000>;
};
};
3.4 bootm 命令执行流程
c
// common/cmd_bootm.c
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
/* 1. 验证镜像头 */
if (image_check_magic(images)) {
printf("Bad Magic Number\n");
return 1;
}
/* 2. 禁用中断 */
interrupts_disable();
/* 3. 准备参数 */
if (image_setup_libfdt(images)) {
printf("FDT creation failed\n");
return 1;
}
/* 4. 跳转到内核 */
bootm_headers_t *hdr = &images;
boot_prep_linux(images);
boot_jump_linux(images);
return 0;
}
四、Linux 内核启动流程
4.1 内核启动入口
内核解压后,跳转到 start_kernel 函数:
c
// init/main.c
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
/* 设置中断栈 */
setup_arch(&command_line);
/* 命令行参数解析 */
parse_args("Booting kernel", command_line);
/* 内存初始化 */
setup_log_buf(0);
setup_per_cpu_areas();
build_all_zonelists();
/* 中断和异常初始化 */
trap_init();
mm_init();
sched_init();
/* 设备模型初始化 */
early_irq_init();
init_IRQ();
tick_init();
/* 时间子系统 */
time_init();
/* 控制台初始化 */
console_init();
profile_init();
/* RCU 初始化 */
rcu_init();
/* 其余初始化 */
rest_init();
}
4.2 内核启动阶段
| 阶段 | 函数 | 主要任务 |
|---|---|---|
| early | start_kernel | 基础设施初始化 |
| core | kernel_init | 内核核心初始化 |
| late | do_basic_setup | 驱动模型和设备初始化 |
| userspace | run_init_process | 启动 init 进程 |
4.3 设备初始化流程
c
// drivers/base/init.c
int __init driver_init(void)
{
/* 这些核心子系统最先初始化 */
devtmpfs_init();
devices_init();
buses_init();
classes_init();
firmware_init();
hypervisor_init();
/* 这些按顺序初始化 */
of_core_init();
platform_bus_init();
cpu_dev_init();
memory_dev_init();
return 0;
}
4.4 根文件系统挂载
c
// init/do_mounts.c
int __init init_mount(const char *name, const char *fs_name,
const char *dev_name, unsigned long flags)
{
struct file_system_type *fs = get_fs_type(fs_name);
struct mount_info *mi;
int ret;
if (!fs)
return -ENODEV;
/* 创建挂载信息 */
mi = kmalloc(sizeof(*mi), GFP_KERNEL);
if (!mi)
return -ENOMEM;
mi->flags = flags;
mi->dev_name = dev_name;
/* 执行挂载 */
ret = do_mount(mi);
return ret;
}
五、常见启动方式
5.1 SD 卡启动
bash
# 分区布局
# mmcblk0p1: FAT32 (boot) - U-Boot + DTB + 内核
# mmcblk0p2: EXT4 (root) - 根文件系统
# U-Boot 启动命令
setenv bootcmd 'mmc dev 0; fatload mmc 0:1 0x82000000 zImage; fatload mmc 0:1 0x88000000 myboard.dtb; bootz 0x82000000 - 0x88000000'
5.2 网络启动 (TFTP + NFS)
bash
# 服务器端配置
# /etc/dhcp/dhcpd.conf
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.100 192.168.1.200;
option subnet-mask 255.255.255.0;
option broadcast-address 192.168.1.255;
option routers 192.168.1.1;
option domain-name-servers 192.168.1.1;
next-server 192.168.1.1;
filename "uImage";
}
# U-Boot 网络启动
setenv bootcmd 'dhcp; tftp 0x82000000 uImage; tftp 0x84000000 myboard.dtb; bootm 0x82000000 - 0x84000000'
setenv bootargs 'console=ttyS0,115200 ip=dhcp root=/dev/nfs nfsroot=192.168.1.1:/nfs/rootfs,tcp,v3 rw'
5.3 eMMC 启动
bash
# eMMC 分区
# mmcblk0boot0: bootloader (SPL + U-Boot)
# mmcblk0p1: FAT32 (boot)
# mmcblk0p2: EXT4 (root)
# 烧写 U-Boot 到 eMMC
mmc dev 0
mw.b 0x82000000 0xFF 0x200000
fatload mmc 0:1 0x82000000 u-boot-sunxi-with-spl.bin
mmc write 0x82000000 0x8000 0x700
六、调试技巧
6.1 U-Boot 调试
bash
# 串口波特率设置
setenv baudrate 115200
# 启用调试信息
#define DEBUG
# 常用调试命令
printenv # 查看所有环境变量
mtdparts default # 列出 MTD 分区
bdinfo # 显示板信息
coninfo # 显示控制台信息
6.2 内核启动调试
bash
# 内核启动参数
console=ttyS0,115200n8 # 串口控制台
earlyprintk # 早期打印
debug # 启用内核调试
ignore_loglevel # 显示所有日志
loglevel=15 # 最高日志级别
# 启用特定调试
initcall_debug # 跟踪 initcall 调用
driver_debug # 驱动调试
ext4_debug # EXT4 调试
6.3 常见问题排查
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| U-Boot 不启动 | SPL 损坏 | 重新烧写 bootloader |
| 内核不启动 | 设备树错误 | 检查 DTB 匹配 |
| 找不到根文件系统 | root= 参数错误 | 确认分区和驱动 |
| 应用不运行 | 缺少库文件 | 检查交叉编译环境 |
七、实战案例:构建最小启动系统
7.1 编译 U-Boot
bash
# 1. 获取源码
git clone https://github.com/u-boot/u-boot.git
cd u-boot
# 2. 选择配置
make myboard_defconfig
# 3. 编译
make -j$(nproc)
# 4. 生成文件
# u-boot.bin : 原始二进制
# u-boot.img : 带 U-Boot 头的镜像
# spl/u-boot-spl.bin: SPL 二进制
7.2 编译 Linux 内核
bash
# 1. 获取源码
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz
tar xf linux-6.1.tar.xz
cd linux-6.1
# 2. 配置内核
make myboard_defconfig
# 3. 编译内核
make -j$(nproc) zImage dtbs
# 4. 生成文件
# arch/arm/boot/zImage : 压缩内核镜像
# arch/arm/boot/dts/*.dtb : 设备树文件
7.3 构建根文件系统
bash
# 使用 Buildroot 创建根文件系统
git clone https://github.com/buildroot/buildroot.git
cd buildroot
make myboard_defconfig
make -j$(nproc)
# 生成的输出
# output/images/rootfs.ext2 : EXT2 文件系统
# output/images/rootfs.tar : tar 归档文件
# output/images/zImage : 内核镜像
# output/images/u-boot.bin : U-Boot 镜像
7.4 创建启动镜像
bash
# 创建 SD 卡启动镜像
dd if=/dev/zero of=sdcard.img bs=1M count=256
# 分区
fdisk sdcard.img << EOF
n
p
1
+64M
n
p
2
w
EOF
# 格式化并挂载
sudo kpartx -av sdcard.img
sudo mkfs.vfat -F 32 /dev/mapper/loop0p1
sudo mkfs.ext4 /dev/mapper/loop0p2
# 复制文件
sudo mount /dev/mapper/loop0p1 /mnt/boot
sudo cp u-boot-sunxi-with-spl.bin /mnt/boot/
sudo cp zImage /mnt/boot/
sudo cp myboard.dtb /mnt/boot/
sudo mount /dev/mapper/loop0p2 /mnt/root
sudo tar xf rootfs.tar -C /mnt/root/
# 卸载
sudo umount /mnt/boot /mnt/root
sudo kpartx -d sdcard.img
八、总结与展望
核心要点总结
- 启动阶段:上电 → ROM → SPL → U-Boot → 内核 → 文件系统
- U-Boot 功能:硬件初始化、环境变量管理、多格式内核引导
- 内核启动:start_kernel → 设备初始化 → 驱动加载 → 挂载根文件系统 → 启动 init
- 调试技巧:串口日志、早期打印、initcall 调试、设备树验证
最佳实践
| 方面 | 建议 |
|---|---|
| U-Boot 移植 | 先使用厂商提供的 SPL,只移植主程序 |
| 内核裁剪 | 只保留必需的驱动,减小内核体积 |
| 文件系统 | 开发用 NFS,生产用 initramfs + EXT4 |
| 启动优化 | 并行初始化、延迟加载、压缩内核 |
未来发展方向
- ATF (ARM Trusted Firmware):安全启动支持
- ACPI:更好的硬件抽象
- EFI Stub:直接从 EFI 引导
- 容器化启动:Docker/K8s 支持嵌入
对于嵌入式开发者而言,深入理解 U-Boot 和内核启动流程,不仅能解决实际工程问题,更能为系统优化和故障排查提供坚实基础。
参考资料
本文基于 U-Boot 2024.01 和 Linux 6.1 内核撰写,不同版本可能存在差异。