Linux U-Boot 与内核启动流程深度解析:从上电到 Shell 的完整之旅

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 主要任务:

  1. 初始化 CPU 核心模式
  2. 设置栈指针
  3. 初始化 PLL 和时钟
  4. 初始化 DDR 内存控制器
  5. 从存储介质加载 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 使用 bootmbootz 命令启动 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

八、总结与展望

核心要点总结

  1. 启动阶段:上电 → ROM → SPL → U-Boot → 内核 → 文件系统
  2. U-Boot 功能:硬件初始化、环境变量管理、多格式内核引导
  3. 内核启动:start_kernel → 设备初始化 → 驱动加载 → 挂载根文件系统 → 启动 init
  4. 调试技巧:串口日志、早期打印、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 内核撰写,不同版本可能存在差异。

相关推荐
大白菜和MySQL2 小时前
Linux下dhcp服务搭建
linux·运维·服务器
大白菜和MySQL2 小时前
linux系统环境常用命令
android·linux·adb
SPC的存折2 小时前
1、MySQL故障排查与运维案例
linux·运维·服务器·数据库·mysql
Run_Teenage2 小时前
Linux:认识信号,理解信号的产生和处理
linux·运维·算法
Deitymoon2 小时前
linux——TCP服务器获取客户端IP地址
linux·服务器·tcp/ip
CDN3602 小时前
高防服务器磁盘 / CPU 爆满?攻击引流与资源扩容实战
运维·服务器·网络协议
小贾要学习3 小时前
【Linux】应用层自定义协议与序列化
linux·服务器·c++·json
white-persist3 小时前
【vulhub spring CVE-2018-1270】CVE-2018-1270 Spring Messaging 远程命令执行漏洞 完整复现详细分析解释
java·服务器·网络·数据库·后端·python·spring
freewlt3 小时前
OpenClaw 工作流自动化实战:打造你的智能定时任务中心
运维·servlet·自动化