从芯片上电到操作系统运行的完整链路


一、教程大纲:SoC 冷启动全链路

┌─────────────────────────────────────────────────────────────────────────────┐
│                         SoC 冷启动 10 阶段硬核教程                            │
├─────────────────────────────────────────────────────────────────────────────┤
│  Phase 0: 上电时序与硬件复位                                                  │
│  Phase 1: BootROM 执行 (芯片固化代码)                                         │
│  Phase 2: SPL/FSBL 第一阶段引导 (DDR 初始化)                                  │
│  Phase 3: 可信启动与安全引导 (Secure Boot / TF-A)                             │
│  Phase 4: U-Boot 第二阶段引导 (驱动初始化、环境变量)                           │
│  Phase 5: 设备树传递与内核加载 (DTB + Kernel)                                 │
│  Phase 6: Linux 内核解压与启动 (setup_arch -> start_kernel)                   │
│  Phase 7: 根文件系统挂载 (initramfs / NFS / eMMC / SD)                        │
│  Phase 8: init 进程与用户空间初始化 (systemd / busybox / procd)               │
│  Phase 9: 产品级优化与量产烧录 (OTA、A/B 分区、安全启动链)                      │
└─────────────────────────────────────────────────────────────────────────────┘

二、各阶段详解与开源 Demo

Phase 0: 上电时序与硬件复位

核心知识点:

  • Power-on Reset (POR) 电路原理
  • 电源时序:VDD_CORE → VDD_DDR → VDD_IO 的先后关系
  • 时钟树:外部晶振 → PLL → 各模块分频
  • 复位信号链:POR → nRESET → WDT → Software Reset

硬件信号分析:

VDD_CORE ──┬───┐
           │   │  t1: Core 稳定 (通常 1-10ms)
VDD_DDR  ──┘   ├───┐
               │   │  t2: DDR 稳定
VDD_IO   ──────┘   ├───┐
                   │   │  t3: IO 稳定
nRESET   ──────────┘   └─── 释放复位,CPU 开始取指

开源 Demo:

  • 项目: qemu-system-arm 模拟复位序列
  • 代码: 使用逻辑分析仪捕获树莓派/全志上电波形
# 使用 PulseView + 逻辑分析仪捕获上电时序
sigrok-cli --driver fx2lafw --config samplerate=24mhz \
    --channels 0=VDD_CORE,1=VDD_DDR,2=nRESET,3=CLK_24M \
    --triggers 2=falling --wait-trigger -o power_on.sr

Phase 1: BootROM 执行

核心知识点:

  • BootROM 是芯片掩膜 ROM,不可修改,通常 32KB-128KB
  • 启动介质检测顺序(以全志为例):SPI NOR → SPI NAND → SD/eMMC → USB
  • 启动签名验证(Secure Boot 时)
  • 加载 SPL 到 SRAM(通常 32KB-64KB 内部 RAM)

全志 H6 BootROM 流程:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   POR 释放   │ --> │  初始化时钟  │ --> │  检测启动介质 │
│             │     │  (24MHz->PLL)│     │  (FEL/SPI/SD)│
└─────────────┘     └─────────────┘     └──────┬──────┘
                                                │
                    ┌───────────────────────────┘
                    ▼
            ┌─────────────┐     ┌─────────────┐
            │ 加载 SPL    │ --> │ 跳转到 SPL   │
            │ 到 SRAM     │     │ (0x20000)   │
            │ (校验签名)   │     │             │
            └─────────────┘     └─────────────┘

开源 Demo:

  • 项目: sunxi-tools (全志 BootROM 工具集)
  • 代码: 读取 BootROM 并进行反汇编分析
# 1. 进入 FEL 模式(全志 USB 烧录模式)
sudo sunxi-fel version
# AWUSBFEX soc=00001667(H6) 00000001 ver=0001 44 08 scratchpad=00007e00 00000000

# 2. 读取 BootROM
sudo sunxi-fel read 0x0000 0x10000 bootrom_h6.bin

# 3. 反汇编 (ARM64)
aarch64-linux-gnu-objdump -b binary -m aarch64 -D bootrom_h6.bin | head -100

Phase 2: SPL / FSBL (First Stage Boot Loader)

核心知识点:

  • SPL (Secondary Program Loader) = U-Boot 第一阶段
  • FSBL (First Stage Boot Loader) = Xilinx Zynq 术语
  • 核心任务:初始化 DDR、时钟、电源,加载 U-Boot 到 DDR
  • 代码位置:arch/arm/cpu/armv8/spl.c

SPL 内存布局(以 512MB DDR 为例):

0x40000000  ├─────────────────────┤  DDR 起始
            │      U-Boot         │  (通常 1-2MB)
            │   (由 SPL 加载)      │
0x4A000000  ├─────────────────────┤
            │      Kernel         │  (FIT Image 或 zImage)
            │   (由 U-Boot 加载)   │
0x48000000  ├─────────────────────┤
            │      DTB            │  (设备树二进制)
0x47000000  ├─────────────────────┤
            │   U-Boot ENV        │  (环境变量)
0x46000000  ├─────────────────────┤
            │      SPL            │  <-- 最初运行在 SRAM
            │   (运行在 SRAM)      │
0x00020000  ├─────────────────────┤  SRAM (内部 64KB)

开源 Demo:

  • 项目: u-boot 官方 SPL 代码
  • 代码: 自定义 SPL 添加 LED 闪烁调试
// arch/arm/mach-sunxi/board.c (SPL 入口)
void board_init_f(ulong dummy)
{
    // 1. 初始化串口 (最早的可调试输出)
    sunxi_serial_init();
    printf("SPL: Hello from SRAM!\n");
    
    // 2. 初始化时钟
    clock_init_safe();
    
    // 3. 初始化 DRAM
    dram_init();
    printf("SPL: DDR initialized, size=%dMB\n", 
           sunxi_dram_size() / 1024 / 1024);
    
    // 4. 从 eMMC/SD 加载 U-Boot
    mmc_initialize();
    struct mmc *mmc = find_mmc_device(0);
    mmc_init(mmc);
    
    // 读取 U-Boot 到 DDR
    ulong uboot_load_addr = CONFIG_SYS_TEXT_BASE; // 0x4A000000
    mmc_read(mmc, UBOOT_SECTOR_START, uboot_load_addr, UBOOT_SIZE);
    
    printf("SPL: Jumping to U-Boot at 0x%lx\n", uboot_load_addr);
    
    // 5. 跳转到 U-Boot
    ((void (*)(void))uboot_load_addr)();
}

Phase 3: 可信启动与安全引导 (TF-A / OP-TEE)

核心知识点:

  • TF-A (Trusted Firmware-A): ARM 官方安全固件
  • BL1 → BL2 → BL31 → BL32 (OP-TEE) → BL33 (U-Boot)
  • TrustZone: Secure World (EL3/Secure EL1) vs Normal World (EL2/EL1/EL0)
  • 安全启动链: 每个阶段验证下一阶段的签名

TF-A 启动流程:

┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐
│  BL1    │ --> │  BL2    │ --> │  BL31   │ --> │  BL32   │ --> │  BL33   │
│ (BootROM)│     │ (SCP/SPL)│     │ (EL3    │     │ (OP-TEE │     │ (U-Boot │
│         │     │         │     │ Runtime)│     │  Secure │     │  Normal)│
└─────────┘     └─────────┘     └─────────┘     └─────────┘     └─────────┘
   验证 BL2        验证 BL31       验证 BL32       验证 BL33

开源 Demo:

  • 项目: trusted-firmware-a (ARM 官方)
  • 代码: 在 QEMU 上运行完整 TrustZone 流程
# 1. 编译 TF-A for QEMU ARM64
git clone https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git
cd trusted-firmware-a
make CROSS_COMPILE=aarch64-linux-gnu- PLAT=qemu \
    BL33=../u-boot.bin \
    all fip

# 2. 使用 QEMU 运行 (启用 EL3/TrustZone)
qemu-system-aarch64 \
    -machine virt,secure=on \
    -cpu cortex-a53 \
    -smp 4 -m 2G \
    -bios build/qemu/release/bl1.bin \
    -device loader,file=build/qemu/release/fip.bin,addr=0x44000000 \
    -nographic

OP-TEE 安全世界示例:

// optee_examples/hello_world/ta/hello_world_ta.c (Trusted Application)
#include <tee_internal_api.h>

TEE_Result TA_InvokeCommandEntryPoint(void *sess_ctx, uint32_t cmd_id,
                                       uint32_t param_types, TEE_Param params[4])
{
    switch (cmd_id) {
    case TA_HELLO_WORLD_CMD_INC_VALUE:
        // 安全世界操作:加密、密钥存储、安全存储
        params[0].value.a++;
        break;
    }
    return TEE_SUCCESS;
}

Phase 4: U-Boot 第二阶段引导

核心知识点:

  • U-Boot 是完整的 bootloader,支持驱动、网络、文件系统
  • 关键组件:命令行、环境变量、驱动模型 (Driver Model)、FIT Image
  • 启动脚本:bootcmd 环境变量定义自动启动流程

U-Boot 启动流程:

board_init_f() -> relocate_code() -> board_init_r() -> main_loop()
     │                  │                  │              │
     ▼                  ▼                  ▼              ▼
  初始化全局数据      重定位到 DDR      驱动初始化       执行 bootcmd
  (GD结构体)         (从Flash复制)     (MMC/Net/FS)    或进入命令行

开源 Demo:

  • 项目: u-boot 官方仓库
  • 代码: 自定义 U-Boot 命令和网络启动
// cmd/my_cmd.c (自定义 U-Boot 命令)
#include <common.h>
#include <command.h>

static int do_hello(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
    printf("Hello from U-Boot!\n");
    printf("DRAM size: %ld MB\n", gd->ram_size / 1024 / 1024);
    printf("Board: %s\n", CONFIG_SYS_BOARD);
    
    // 读取并打印环境变量
    char *bootargs = env_get("bootargs");
    printf("bootargs: %s\n", bootargs ? bootargs : "(not set)");
    
    return CMD_RET_SUCCESS;
}

U_BOOT_CMD(
    hello, 1, 0, do_hello,
    "Print hello message and system info",
    ""
);

// 编译后在 U-Boot 命令行执行:
// => hello
// Hello from U-Boot!
// DRAM size: 1024 MB
// Board: my_board

U-Boot 网络启动脚本:

# 设置环境变量自动从 TFTP 启动
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.1
setenv bootcmd 'tftp 0x42000000 zImage; tftp 0x43000000 dtb; bootz 0x42000000 - 0x43000000'
saveenv
reset

Phase 5: 设备树传递与内核加载

核心知识点:

  • 设备树 (DTB): 描述硬件配置,替代硬编码的 machine 类型
  • FIT Image (Flattened Image Tree): 将 Kernel + DTB + Initramfs 打包为一个镜像
  • U-Boot 加载内核: bootz (zImage) / booti (Image) / bootm (uImage/FIT)

FIT Image 结构:

/ {
    description = "Kernel + DTB + Initramfs";
    #address-cells = <1>;
    
    images {
        kernel {
            description = "Linux Kernel";
            data = /incbin/("arch/arm64/boot/Image.gz");
            type = "kernel";
            arch = "arm64";
            os = "linux";
            compression = "gzip";
            load = <0x40080000>;
            entry = <0x40080000>;
            hash-1 { algo = "sha256"; };
        };
        
        fdt {
            description = "Device Tree";
            data = /incbin/("arch/arm64/boot/dts/myboard.dtb");
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            load = <0x4FA00000>;
            hash-1 { algo = "sha256"; };
        };
        
        ramdisk {
            description = "Initramfs";
            data = /incbin/("initramfs.cpio.gz");
            type = "ramdisk";
            arch = "arm64";
            os = "linux";
            compression = "gzip";
            load = <0x4FE00000>;
            hash-1 { algo = "sha256"; };
        };
    };
    
    configurations {
        default = "conf-1";
        conf-1 {
            description = "Default config";
            kernel = "kernel";
            fdt = "fdt";
            ramdisk = "ramdisk";
            signature-1 {
                algo = "sha256,rsa2048";
                key-name-hint = "dev";
                sign-images = "kernel", "fdt", "ramdisk";
            };
        };
    };
};

开源 Demo:

  • 项目: u-boot + mkimage 工具
  • 代码: 生成并启动 FIT Image
# 1. 生成 FIT Image
mkimage -f kernel.its kernel.itb

# 2. U-Boot 加载 FIT Image
=> tftp 0x42000000 kernel.itb
=> bootm 0x42000000#conf-1
## Loading kernel from FIT Image at 42000000 ...
## Loading fdt from FIT Image ...
## Loading ramdisk from FIT Image ...

Phase 6: Linux 内核解压与启动

核心知识点:

  • 入口点: arch/arm64/kernel/head.Sstext
  • 启动阶段: setup_arch()start_kernel()rest_init()kernel_init()
  • MMU 开启: 从物理地址运行切换到虚拟地址运行
  • 设备树解析: unflatten_device_tree() 构建设备节点

ARM64 内核启动汇编:

// arch/arm64/kernel/head.S
ENTRY(stext)
    // 1. 关闭中断,确保处于安全状态
    msr     daifset, #0xf
    
    // 2. 保存设备树物理地址 (x0 = dtb_phys)
    mov     x21, x0
    
    // 3. 初始化页表 (identity mapping)
    bl      __create_page_tables
    
    // 4. 使能 MMU
    msr     sctlr_el1, x0
    isb
    
    // 5. 跳转到 C 代码 (虚拟地址)
    ldr     x8, =__primary_switched
    br      x8
ENDPROC(stext)

C 语言启动流程:

// init/main.c
asmlinkage __visible void __init start_kernel(void)
{
    // 1. 架构相关初始化
    setup_arch(&command_line);  // 解析设备树,初始化内存
    
    // 2. 核心子系统初始化
    mm_init();          // 内存管理
    sched_init();       // 调度器
    preempt_disable();  // 关闭抢占
    
    // 3. 中断和时钟
    early_irq_init();
    init_IRQ();
    tick_init();
    init_timers();
    
    // 4. 控制台和驱动
    console_init();
    vfs_caches_init();
    
    // 5. 创建 init 进程
    rest_init();
}

// rest_init 中:
pid_t kernel_init(void *unused)
{
    // 挂载根文件系统
    kernel_init_freeable();
    
    // 尝试执行 /sbin/init, /etc/init, /bin/init, /bin/sh
    if (execute_command) {
        ret = run_init_process(execute_command);
    }
    // ...
}

开源 Demo:

  • 项目: linux 内核源码
  • 代码: 添加自定义 early printk 调试
// arch/arm64/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
    pr_info("=== My SoC Setup ===\n");
    pr_info("DTB phys: 0x%llx\n", __pa(initial_boot_params));
    pr_info("Memory: %ld MB\n", memblock_phys_mem_size() / 1024 / 1024);
    
    // 打印设备树节点
    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
    of_scan_flat_dt(early_init_dt_scan_root, NULL);
    
    // 标准初始化
    setup_machine_fdt(__pa(initial_boot_params));
    arm64_memblock_init();
    paging_init();
}

Phase 7: 根文件系统挂载

核心知识点:

  • Initramfs: 内置到内核的临时根文件系统 (cpio 格式)
  • 根文件系统类型: ext4, squashfs, jffs2, ubifs, nfs
  • 挂载流程: prepare_namespace()mount_root()sys_mount()

Initramfs 构建:

# 1. 创建最小根文件系统
mkdir -p initramfs/{bin,sbin,etc,proc,sys,dev,lib,lib64}

# 2. 使用 busybox 或 toybox
cp /path/to/busybox initramfs/bin/
initramfs/bin/busybox --install initramfs/bin/

# 3. 创建 init 脚本
cat > initramfs/init << 'EOF'
#!/bin/sh
echo "Initramfs started!"

# 挂载虚拟文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev

# 查找真实根文件系统
echo "Looking for rootfs on /dev/mmcblk0p2..."
if [ -b /dev/mmcblk0p2 ]; then
    mount -t ext4 /dev/mmcblk0p2 /mnt
    exec switch_root /mnt /sbin/init
fi

# 如果找不到,进入救援 shell
echo "Rootfs not found, dropping to shell..."
exec /bin/sh
EOF
chmod +x initramfs/init

# 4. 打包为 cpio
cd initramfs && find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz

开源 Demo:

  • 项目: buildroot 自动生成 initramfs
  • 代码: 自定义 initramfs 实现根文件系统切换
// init/do_mounts.c (内核源码)
static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
    int err = sys_mount(name, "/root", fs, flags, data);
    if (err)
        return err;
    
    sys_chdir("/root");
    sys_mount(".", "/", NULL, MS_MOVE, NULL);
    sys_chroot(".");
    return 0;
}

void __init prepare_namespace(void)
{
    // 等待设备就绪
    wait_for_device_probe();
    
    // 挂载根文件系统
    if (initrd_load())  // 如果有 initrd/initramfs
        goto out;
    
    // 否则挂载块设备
    mount_block_root(root_device_name, root_mountflags);
    
out:
    initrd_end = 0;
    mount_root();  // 实际执行 sys_mount
}

Phase 8: Init 进程与用户空间

核心知识点:

  • 传统 init: SysVinit (/sbin/init) → 串行启动
  • 现代 init: systemd (并行) / procd (OpenWrt) / busybox init
  • 启动目标: default.target (systemd) / rcS (SysV)

Systemd 启动流程:

systemd (PID 1)
    │
    ├── basic.target (基础系统)
    │       ├── sysinit.target (初始化)
    │       │       ├── systemd-modules-load.service
    │       │       ├── systemd-sysctl.service
    │       │       └── ...
    │       └── sockets.target / timers.target
    │
    ├── sysinit.target
    │       └── ...
    │
    └── default.target (多用户图形界面)
            ├── network.target
            ├── ssh.service
            ├── my-app.service  <-- 你的应用
            └── ...

OpenWrt procd 启动:

# /etc/rc.common 示例
#!/bin/sh /etc/rc.common

START=99
STOP=10

start() {
    echo "Starting my application..."
    /usr/bin/my-app &
}

stop() {
    killall my-app
}

开源 Demo:

  • 项目: systemdprocd 服务单元
  • 代码: 创建自定义 systemd 服务
# /etc/systemd/system/my-embedded-app.service
[Unit]
Description=My Embedded Application
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/my-app
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

# 资源限制(嵌入式关键)
MemoryMax=50M
CPUQuota=50%
OOMScoreAdjust=-100

[Install]
WantedBy=multi-user.target

Phase 9: 产品级优化与量产

核心知识点:

  • 启动时间优化: 内核裁剪、异步初始化、精简 initramfs
  • A/B 分区升级: 冗余分区,原子更新,失败回滚
  • 安全启动链: eFuse 烧录公钥哈希,验证每个启动阶段
  • OTA 机制: 双分区 + 恢复模式 + 签名验证

启动时间优化数据:

优化项 时间节省 方法
禁用 printk ~200ms quiet 内核参数
异步驱动 probe ~300ms driver_async_probe
精简内核 ~500ms 移除未使用驱动
预读取 DTB ~50ms 内嵌到内核
XIP (Execute In Place) ~100ms 从 Flash 直接执行
总计 ~1-2s

A/B 分区布局:

eMMC/SD 布局:
┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐
│   SPL (1MB) │  U-Boot (2MB)│   Env (1MB) │  Boot A (32MB)│  Boot B (32MB)│
│             │             │             │  Kernel+DTB   │  Kernel+DTB   │
├─────────────┴─────────────┴─────────────┼─────────────┼─────────────┤
│              Rootfs A (500MB)           │  Rootfs B (500MB)│
│              (当前运行)                  │  (升级目标)      │
└─────────────────────────────────────────┴─────────────┘

开源 Demo:

  • 项目: u-boot + swupdateRAUC
  • 代码: 实现 A/B 分区切换
// U-Boot 环境变量控制 A/B 启动
// fw_printenv / fw_setenv 工具

// 升级脚本 (在运行系统中)
#!/bin/sh
# update.sh

CURRENT_SLOT=$(fw_printenv -n bootslot)
if [ "$CURRENT_SLOT" = "A" ]; then
    TARGET_SLOT="B"
    TARGET_PART="mmcblk0p5"
else
    TARGET_SLOT="A"
    TARGET_PART="mmcblk0p4"
fi

# 下载并验证新镜像
wget -O /tmp/update.img "$UPDATE_URL"
veritysetup verify /tmp/update.img /tmp/update.img.verity

# 写入目标分区
dd if=/tmp/update.img of=/dev/$TARGET_PART bs=1M

# 标记新分区为可启动,但先不切换
fw_setenv bootslot_try $TARGET_SLOT
fw_setenv bootcount 0

# 重启
reboot
// U-Boot 启动逻辑 (env 脚本)
if test "${bootslot_try}" != ""; then
    # 尝试新分区
    if test "${bootcount}" -lt 3; then
        setenv bootcount $((bootcount + 1))
        setenv bootslot ${bootslot_try}
        saveenv
    else
        # 尝试 3 次失败,回滚
        setenv bootslot_try ""
        setenv bootcount 0
        saveenv
    fi
fi

if test "${bootslot}" = "A"; then
    load mmc 0:4 ${kernel_addr_r} Image
    load mmc 0:4 ${fdt_addr_r} dtb
else
    load mmc 0:5 ${kernel_addr_r} Image
    load mmc 0:5 ${fdt_addr_r} dtb
fi

三、完整开源项目推荐

项目 用途 链接
U-Boot Bootloader https://github.com/u-boot/u-boot
Linux Kernel 操作系统内核 https://github.com/torvalds/linux
TF-A 可信固件 https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git
OP-TEE 安全操作系统 https://github.com/OP-TEE/optee_os
Buildroot 根文件系统构建 https://github.com/buildroot/buildroot
Yocto 完整发行版构建 https://git.yoctoproject.org/
swupdate OTA 升级 https://github.com/sbabic/swupdate
RAUC 安全 A/B 升级 https://github.com/rauc/rauc
sunxi-tools 全志芯片工具 https://github.com/linux-sunxi/sunxi-tools
Crust 全志 SCP 固件 https://github.com/crust-firmware/crust

四、学习路径建议

第 1-2 周: 硬件基础
  └─ 阅读芯片 datasheet,理解上电时序、时钟树、内存映射
  └─ 用逻辑分析仪捕获实际波形

第 3-4 周: BootROM + SPL
  └─ 用 sunxi-tools 读取并分析 BootROM
  └─ 修改 U-Boot SPL,添加 LED/串口调试

第 5-6 周: U-Boot 深度定制
  └─ 添加自定义命令、驱动、环境变量脚本
  └─ 实现网络启动 (TFTP/NFS)

第 7-8 周: 内核启动与设备树
  └─ 编写自定义设备树
  └─ 添加 early printk,跟踪启动流程

第 9-10 周: 根文件系统与 Init
  └─ 用 Buildroot 构建最小系统
  └─ 实现 initramfs + pivot_root 切换

第 11-12 周: 安全启动与量产
  └─ 配置 TF-A + OP-TEE
  └─ 实现 Secure Boot 签名链
  └─ 集成 OTA 升级机制

需要我针对某个具体芯片(如全志 H6/R528、树莓派 BCM2711、瑞芯微 RK3568)展开更详细的启动代码分析,或者提供完整的 QEMU 模拟环境搭建教程吗?

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐