【IMX6ULL Linux系统移植】SD卡分区 | uboot详解

文章目录

    • 前言
    • 一、SD分区布局
      • [1.1 概览](#1.1 概览)
      • [1.2 MBR分区表](#1.2 MBR分区表)
      • [1.3 U-Boot](#1.3 U-Boot)
      • [1.4 zImage(内核)与 DTB(设备树)](#1.4 zImage(内核)与 DTB(设备树))
      • [1.5 Rootfs(根文件系统)](#1.5 Rootfs(根文件系统))
      • [1.6 总结:启动时的联动流程](#1.6 总结:启动时的联动流程)
    • 二、uboot
      • [2.1 uboot概念及启动流程](#2.1 uboot概念及启动流程)
      • [2.2 uboot交互命令与环境变量](#2.2 uboot交互命令与环境变量)
        • [2.2.1 交互命令](#2.2.1 交互命令)
        • [2.2.2 环境变量](#2.2.2 环境变量)
      • [2.3 编译](#2.3 编译)
      • [2.4 uboot烧录](#2.4 uboot烧录)
      • [2.5 uboot启动的三个阶段](#2.5 uboot启动的三个阶段)
        • [2.5.1 i.MX6ULL 内部 ROM(Boot ROM 阶段)](#2.5.1 i.MX6ULL 内部 ROM(Boot ROM 阶段))
        • [2.5.2 U‐Boot 启动(第一阶段 U‐Boot)](#2.5.2 U‐Boot 启动(第一阶段 U‐Boot))
        • [2.5.3 U‐Boot 主程序(第二阶段,真正的 U‐Boot)](#2.5.3 U‐Boot 主程序(第二阶段,真正的 U‐Boot))
    • 三、uboot解析(启动过程梳理)
      • [3.1 找到uboot运行起点](#3.1 找到uboot运行起点)
      • [3.2 reset](#3.2 reset)
        • [3.2.1 reset逐步梳理](#3.2.1 reset逐步梳理)
        • [3.2.2 reset启动总结](#3.2.2 reset启动总结)
      • [3.3 bootz](#3.3 bootz)
    • [四、 U-Boot 启动整体框架](#四、 U-Boot 启动整体框架)
    • 五、关键函数梳理
    • [六、U-Boot 如何给 Linux 传递参数](#六、U-Boot 如何给 Linux 传递参数)
      • [6.1 寄存器传参(遵循 ARM 启动规范)](#6.1 寄存器传参(遵循 ARM 启动规范))
      • [6.2 环境变量传递(通过设备树节点)](#6.2 环境变量传递(通过设备树节点))

前言

在上篇【IMX6ULL Linux系统移植】从零搭建 Linux 移植开发环境,多种系统烧录方式详解文章中实现了对正点原子给出的linux系统在IMX6ULL上完成了移植,在这篇文章中,由于后续都将采用第三种移植方式进行驱动开发,因此这篇文章将对SD卡的分区进行讲解,并对uboot进一步进行了解,以及对内核编译及裁剪进行一定的学习。

一、SD分区布局

1.1 概览

根据烧写shell脚本(./imx6mksdboot.sh)数据在sd的布局:

  • MBR 分区表(灰色):仅第 0 扇区,512 字节,存放分区定义
  • 保留区域(黄色):从 1KB 处写入 u-boot.imx,占用约 300KB~800KB,整块不超过 1MB
  • 分区 1 · FAT32(蓝色):64MB,挂载为 /boot,只放 zImage 和设备树文件(xxx-emmc.dtb 或 nand.dtb)
  • 分区 2 · EXT3(绿色):剩余空间,挂载为根目录 /,包含 bin/、etc/、lib/(其中 lib/modules/ 已解压驱动模块)、usr/

1.2 MBR分区表

分区表(MBR):位于绝对起始扇区;

在 U-Boot 之前(0~512 字节)存储着 MBR(主引导记录)分区表。

脚本定义了 两个分区,分区信息如下表:

1.3 U-Boot

U-Boot(引导加载程序):原始扇区存储(非文件形式)

  • 存储位置:SD卡的第1KB(1024字节)偏移量处,位于第一个分区之前的"保留区域"。
  • 存储方式:裸数据写入(Raw Binary)。脚本使用dd命令,直接跳过文件系统,将.imx镜像写入块设备的绝对地址。
  • 关键细节:
    • bs=1024 seek=1 代表从第1个块(1KB)开始写入。这是因为i.MX6的Boot ROM启动时会固定读取SD卡偏移1KB处的数据作为程序头部(DCD配置),随后加载到内部RAM运行。
    • 为什么没覆盖分区表?虽然分区表位于第1扇区(0~512字节),但脚本先执行了dd清零前1MB,然后用fdisk重新创建了分区表。由于fdisk默认将第一个分区的起始扇区设置为2048(即1MB对齐),而U-Boot仅占用1KB ~ 1MB之间的空闲区域,两者完美错开,互不干扰。

1.4 zImage(内核)与 DTB(设备树)

存储在分区 1(FAT32 文件系统)。

  • 存储介质:/dev/sdx1(第一个分区)。
  • 文件系统格式:FAT32(卷标为 boot)。
  • 目录结构:这两个文件被复制到该分区的根目录(/)下,形成以下路径:
    • /zImage(内核镜像)
    • /emmc .dtb 或 /nand.dtb(根据传入参数筛选出的设备树文件)
  • 设计原因:i.MX6 的 U-Boot 内置了 fatload 命令,能直接读取 FAT32 分区。启动时,U-Boot 会从该分区的根目录加载 zImage 和设备树到内存中,而不需要解析复杂的 EXT 文件系统,从而简化了启动流程。

1.5 Rootfs(根文件系统)

存储在分区 2(EXT3 文件系统)。

  • 存储介质:/dev/sdx2(第二个分区)。
  • 文件系统格式:EXT3(卷标为 rootfs,注意:前面分析过脚本中存在变量拼写Bug,需手动修复格式化部分)。
  • 目录结构:脚本将 filesystem/.tar. 压缩包解压到该分区的根目录(/)下。
  • 存储内容:解压后,该分区内会生成标准的 Linux 根目录结构,例如 /bin、/etc、/lib、/usr、/home 等。
  • 内核模块(驱动):脚本额外将 modules/.tar. 解压到该分区下的 /lib/modules/ 目录中。这确保了内核启动后能正确加载对应版本的硬件驱动。

1.6 总结:启动时的联动流程

  1. 上电 -> i.MX6 Boot ROM 读取偏移1KB处的U-Boot到RAM并运行。
  2. U-Boot运行 -> 读取MBR分区表,找到第一个活动分区(FAT32)。
  3. U-Boot加载内核 -> 通过fatload从分区1的根目录读取zImage和DTB到内存。
  4. 启动内核 -> 内核初始化后,挂载分区2(EXT3)作为根文件系统/,并从/lib/modules/中加载驱动。
  5. 系统启动完成。

二、uboot

2.1 uboot概念及启动流程

uboot是bootloader的一种,主要作用是在内核加载前对硬件设备初始化,并为内核的运行提供引导功能。

通过系统的架构图可以知道:片内 RAM 只有 128KB,装不下完整 U‐Boot,因此,uboot 启动必须分两阶段。

启动流程如下所示:

2.2 uboot交互命令与环境变量

2.2.1 交互命令
  • 环境变量操作命令(主要使用)

    命令 功能 格式
    printenv 查看环境变量 printenv
    setenv 新建 / 修改环境变量 setenv 环境变量名 环境变量值
    saveenv 保存环境变量 saveenv
  • 网络命令(主要使用)

    命令 功能 格式 示例
    ping 测试网络是否连通 ping destip ping 192.168.1.100
    tftp 通过网络利用tftp协议下载文件到SDRAM tftp localAddress \[hostIPaddr:bootfilename] tftp 80800000 zImage
    nfs 通过网络利用nfs协议下载文件到SDRAM nfs localAddress \[hostIPaddr:bootfilename] nfs 80800000 192.168.1.100:/home/linux/nfs/zImage
  • 内存操作命令(了解即可)

    命令 功能 格式 示例
    md 显示内存值 md.b,.w,.l address #of objects md.b 80000000 14
    nm 修改指定地址的内存值 nm.b,.w,.l address nm.l 80000000
    mw 使用一个指定的数据填充一段内存 mw.b,.w,.l address value count mw.l 80000000 0A0A0A0A 10
    cp 数据拷贝命令 cp.b,.w,.l source target count cp.l 80000000 80000100 10
  • mmc操作命令(了解即可)

    命令 功能 格式 示例
    mmc info 输出MMC设备信息 mmc info mmc info
    mmc read 读取MMC中的数据 mmc read addr blk cnt mmc read 80800000 600 10
    mmc write 向MMC设备写入数据 mmc write addr blk# cnt mmc write 80800000 2 2E6
    mmc rescan 扫描MMC设备 mmc rescan mmc rescan
    mmc part 列出MMC设备的分区 mmc part mmc part
    mmc dev 切换MMC设备(0表示SD卡,1表示EMMC) mmc dev 0/1 mmc dev 0
    mmc list 列出当前有效的所有mmc设备 mmc list mmc list
    mmc erase 擦除MMC设备的指定块 mmc erase blk# cnt mmc erase 600 10
  • boot命令(bootz需掌握)

    命令 功能 格式 示例
    bootz 用于启动zImage镜像文件 bootz addr \[initrd \[:size fdt]] bootz 80800000 - 83000000
    bootm 用于启动uImage镜像文件 bootm addr bootm 80800000
    boot 读取环境变量bootcmd来启动Linux系统 boot boot
    go 跳到指定的地址处执行应用 go addr arg ... go 87800000
    run 用于运行环境变量中定义的命令 run envname run mmcboot
2.2.2 环境变量
  • bootcmd

    bootcmd保存着uboot默认命令,uboot倒计时结束以后就会执行bootcmd中的命令。例如:

    1.从emmc中读取内核、设备树、启动

    shell 复制代码
    setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'
    saveenv

    2.从网络中下载内核、设备树、启动

    shell 复制代码
    setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-alientekemmc.dtb;bootz 80800000 - 83000000'
    saveenv
  • bootargs

    bootargs保存着uboot传递给Linux内核的参数,例如:

    1.从emmc中启动Linux系统:

    shell 复制代码
    setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
    saveenv

    2.从nfs网络中启动Linux系统:

    shell 复制代码
    setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/home/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off'
    saveenv
  • 完整的uboot环境变量配置

    shell 复制代码
    env default -a          # uboot 变量恢复默认
    saveenv                 # 保存变量
    reset                   # 重启uboot
    setenv ipaddr 192.168.1.50      # 设置开发板ip地址
    setenv ethaddr 00:04:9f:04:d2:35 # 设置开发板网卡mac地址
    setenv gatewayip 192.168.1.1     # 设置开发板网关
    setenv netmask 255.255.255.0     # 设置开发板子网掩码
    setenv serverip 192.168.1.100    # 设置pc段的ip地址
    setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/home/linux/nfs/rootfs,v3,proto=tcp rw ip=192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off' # 设置根文件系统的位置
    setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000' # 设置下载zImage,dtb
    saveenv                 #保存变量
    reset                   #重启uboot

2.3 编译

1.解压uboot压缩包,并进入uboot源码目录

shell 复制代码
tar -zxvf uboot.tar.gz
cd uboot

2.uboot图形界面是基于ncurses库编写的,所以需要先安装该库

shell 复制代码
sudo apt-get install libncurses5-dev

3.编译uboot源码

shell 复制代码
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

参数说明:

  • ARCH: 指定目标硬件架构
  • CROSS_COMPILE: 设置交叉编译工具链
  • V=0: 控制编译输出的详细程度,0表示启动静默/精简输出模式,1表示完整的编译命令,便于调试。
  • j16:表示最多16个核心一起编译
  • xxx_defconfig 是U-Boot 的默认配置文件 ,本质是预写好的一套配置,用来快速生成 .config。

.config 的来源,作用 :

✅ 来源

.config 是编译 U-Boot 真正使用的配置文件 。

从 defconfig 生成

shell 复制代码
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig

读取 configs/mx6ull_alientek_emmc_defconfig 这个板级预制模板,在源码顶层目录自动生成 .config。

✅ 作用

.config 保存所有编译开关,控制 U-Boot 编译的时候:

  1. 编译哪些驱动:EMMC、网卡、串口、LCD 等驱动要不要编译进 U-Boot
  2. 启用哪些命令 :tftp、bootz、fatload、saveenv 等命令是否支持
  3. 芯片 / 板子参数:CPU 架构、时钟、内存大小、设备树相关选项
  4. 编译宏开关 :y编译进镜像;n不编译;m模块(U-Boot 很少用 m)

Make 编译 U-Boot 时,读取 .config 里面的宏定义 ,决定代码哪些参与编译、哪些忽略。没有 .config,make 无法编译。

4.生成结果

生成的结果文件主要是位于uboot顶层目录的:u-boot.imx文件。

2.4 uboot烧录

在Ubuntu下利用imxdownload完成u-boot.imx烧写 :

./imxdownload u-boot.bin /dev/sdb

开发板 sd卡启动,启动后需要配置uboot的参数,在前面的环境变量中已经写出。

2.5 uboot启动的三个阶段

2.5.1 i.MX6ULL 内部 ROM(Boot ROM 阶段)

这一步不属于 U-Boot,是 boot rom 中固化的程序,但必须先做,才能启动 U-Boot。

作用:

  1. 检测启动模式(SD 卡、EMMC、NAND、QSPI)。sd/emmc 设备没有完全初始化,内部程序写死,只会读取前面一部分内容。根据拨码开关有关,决定了启动方式;
  2. 初始化最基础的时钟 arm_pll 396M,system_PLL 528M;
  3. 读取启动设备的第一个镜像(uboot.imx 头部信息)片上的 Rom 中程序的功能。刚上电 pc =0 ; rom 就在 0 地址;
  4. 把 uboot.imx 头部信息加载到片内 RAM(OCRAM 0x00900000);
2.5.2 U‐Boot 启动(第一阶段 U‐Boot)

有时候也称作SPL阶段(Secondary Program Loader),二级程序加载器。

运行位置:片内 RAM(OCRAM);

核心使命:初始化 DDR,然后加载完整 U‐Boot。

  • 编译直接得到的是 U-Boot.bin(裸程序),它自己没法被 ROM 识别,所以在前面拼了 1KB 头部(IVT + BOOT_DATA + DCD),拼完的才是烧进 SD 卡的 u-boot.imx;
  • IVT(Image Vector Table):镜像的"目录",里面写着程序入口和各配置块的位置;
  • BOOT_DATA:搬运说明------把程序复制到哪个地址(start = 0x8780_0000)、总共多大;
  • DCD(Device Configuration Data):一串"寄存器配置脚本",ROM 照着它逐条写入,把 DDR 控制器等硬件提前配好。

这 7 步常被叫作"U-Boot 第一阶段",但严格说干活的主体是芯片里固化的 boot ROM,它只是借用了 u-boot.imx 头部里的 DCD 数据而已------所以才叫 SPL(Secondary Program Loader,二级加载器),ROM 是一级。

IVT、DCD与Boot Data的协作关系 :

这三者共同构成了I.MX系列芯片完整的启动镜像头部。其工作流程大致如下:

  1. 芯片上电, BootROM 开始执行。
  2. 查找IVT: BootROM 根据启动设备(如SD卡),在预定义的偏移位置(如1KB处)找到 IVT 。
  3. 解析IVT: BootROM 读取 IVT ,从中获取 DCD 、 Boot Data 和用户程序 entry 的地址。
  4. 执行DCD: BootROM 根据 IVT 提供的地址找到 DCD ,并执行其中的命令,完成硬件(特别是
    DDR)的初始化。
  5. 解析Boot Data: BootROM 根据 IVT 提供的地址找到 Boot Data ,得知用户程序的大小和应该被
    加载到内存的哪个地址。
  6. 加载并跳转: BootROM 将用户程序从存储设备复制到 Boot Data 指定的内存地址,然后跳转到
    IVT 中 entry 字段指定的地址,将CPU控制权交给用户程序。
2.5.3 U‐Boot 主程序(第二阶段,真正的 U‐Boot)

运行位置:DDR 0x87800000

这是在命令行显示的 U‐Boot。

完成的工作:

  1. 初始化 DDR(最关键)

  2. 初始化时钟(CPU 主频、总线时钟)

  3. 初始化启动设备(SD/EMMC/NAND)

  4. 加载 Linux 内核和设备树到内存

  5. 传递启动参数给内核

  6. 启动 Linux 内核

三、uboot解析(启动过程梳理)

3.1 找到uboot运行起点

先编译uboot,在uboot的顶层目录会生成u-boot.lds。可以先从 u-boot.lds 和 u-boot.map 入手分析 U-Boot:

u-boot.lds 链接脚本;

u-boot.map uboot在内存地址中的映射(或者说位置),内存映射文件。链接完成后输出,记录所有段、函数、全局变量对应的内存地址与占用大小,用来查看代码 / 变量在内存中的位置,用于地址排查、镜像空间分析、定位链接相关问题。

下面是链接脚本的代码:

c 复制代码
/* 指定输出文件格式:32位小端ARM的ELF文件,三个参数分别对应不同输出目标,这里统一elf32-littlearm */
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/* 指定目标CPU架构为arm */
OUTPUT_ARCH(arm)
/* 指定程序入口函数为_start,程序上电从_start开始执行 */
ENTRY(_start)

/* 段布局定义,链接脚本核心,描述各个段在内存中的排布 */
SECTIONS
{
    /* 设置当前地址计数器(location counter)起始地址为0x00000000 */
    . = 0x00000000;
    /* 地址对齐到4字节 */
    . = ALIGN(4);
    /* .text 代码段,存放程序可执行指令 */
    .text :
    {
        /* 镜像拷贝起始标记符号 */
        *(.__image_copy_start)
        /* 中断向量表段,ARM启动最先读取的向量表 */
        *(.vectors)
        /* 强制把start.o放在代码段靠前位置,这是U-Boot启动入口汇编文件 */
        arch/arm/cpu/armv7/start.o (.text*)
        /* 其他所有文件的代码段,剩余所有指令 */
        *(.text*)
    }
    . = ALIGN(4);
    /* .rodata 只读数据段,存放常量、字符串常量,运行时不可修改 */
    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
    . = ALIGN(4);
    /* .data 已初始化全局变量段,存放已经赋初值的全局变量 */
    .data : {
        *(.data*)
    }
    . = ALIGN(4);
    . = .;
    . = ALIGN(4);
    /* uboot命令列表段,存放U-Boot所有命令结构体,KEEP防止链接器优化删除 */
    .u_boot_list : {
        KEEP(*(SORT(.u_boot_list*)));
    }
    . = ALIGN(4);
    /* 镜像拷贝结束标记 */
    .image_copy_end :
    {
        *(.__image_copy_end)
    }
    /* rel_dyn 动态重定位起始标记 */
    .rel_dyn_start :
    {
        *(.__rel_dyn_start)
    }
    /* .rel.dyn 动态重定位段,U-Boot运行时做动态地址重定位 */
    .rel.dyn : {
        *(.rel*)
    }
    /* 动态重定位结束标记 */
    .rel_dyn_end :
    {
        *(.__rel_dyn_end)
    }
    /* 镜像结束标记 */
    .end :
    {
        *(.__end)
    }
    /* 定义符号,标识二进制镜像的末尾地址 */
    _image_binary_end = .;

    /* 4096字节对齐(页对齐) */
    . = ALIGN(4096);
    /* mmutable段,Uboot内部特殊段 */
    .mmutable : {
        *(.mmutable)
    }

    /* OVERLAY:覆盖段,bss段不占用镜像文件空间,运行时在内存开辟,不烧写到flash */
    /* bss_start标记,KEEP保证符号不会被链接器丢弃 */
    .bss_start __rel_dyn_start (OVERLAY) : {
        KEEP(*(.__bss_start));
        __bss_base = .;  /* 记录bss段起始地址 */
    }
    /* .bss段:未初始化全局变量,启动时清零 */
    .bss __bss_base (OVERLAY) : {
        *(.bss*)
        . = ALIGN(4);
        __bss_limit = .; /* 记录bss段结束地址 */
    }
    /* bss结束标记 */
    .bss_end __bss_limit (OVERLAY) : {
        KEEP(*(.__bss_end));
    }

    /* 下面是ELF动态链接相关段,U-Boot用于动态符号、重定位 */
    .dynsym _image_binary_end : { *(.dynsym) }      /* 动态符号表 */
    .dynbss : { *(.dynbss) }                        /* 动态BSS */
    .dynstr : { *(.dynstr*) }                       /* 动态字符串表 */
    .dynamic : { *(.dynamic*) }                     /* 动态链接信息 */
    .plt : { *(.plt*) }                             /* PLT过程链接表 */
    .interp : { *(.interp*) }                       /* 解释器信息 */
    .gnu.hash : { *(.gnu.hash) }                    /* gnu哈希表,加速符号查找 */
    .gnu : { *(.gnu*) }
    .ARM.exidx : { *(.ARM.exidx*) }                 /* ARM异常索引表,异常处理 */
    .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}

链接脚本代码中有这一段:

通过grep "__image_copy_start" u-boot.map指令,我们可以在.map文件中找到代码存放的地址,在镜像开始之后就是异常中断向量表,这个与我们当时在裸机部分写的启动代码类似,并且这里就可以找到为什么当时我们裸机部分的启动代码起始下载地址要放在0x87800000这个位置了。:

简单来说:u-boot.lds 决定代码段摆放顺序,编译链接完成后,u-boot.map 把每个符号、段对应的真实内存地址全部打印出来,方便查看和调试。

所以,linux首先运行的就是_start。

我们跟进去看一下(vim跟进功能安装指令sudo apt install cscope ctags # vim的代码追踪):

这里有多个跟进结果,根据使用的arm芯片中通用的lib库文件,我们选择2回车跟进。

_start 是一个标号:

在异常向量表中,可以看到第一句 b reset 也就是uboot运行起来要做的事情,从这个地方开始。

3.2 reset

3.2.1 reset逐步梳理

跟进reset看一下:

save_boot_params_ret(关闭fiq,irq,cpu mode->svc32,设置向量表重定位,sctrl(13bit ,Vbit,向量表控制位 ),VBAR),cpu_init_cp15(设置cp15),cpu_init_crit -> lowlevel_init(设置栈大小,).

在reset的最后为lowlevel_init:

我们回到顶层目录,使用grep -Rin 'entry(lowlevel_init)'指令查找entry(lowlevel_init)入口:

我们打开查到的文件:

我们可以发现0x0090 0000这个地址是 imx6ull 核心内部的内存, 不是外部ddr。,说明现在的代码还在片内存(ocram),还没有搬移到外部DDR。

lowlevel_init.S->lowlevel_init(初始化片内地址,OCRAM)-> bl s_init (空函数,imx6ull 会返回,arch/arm/cpu/armv7/mx6/soc.c )

最后,跳转 _main (arch/arm/lib/crt0.S ), struct global_data ,通用全局变量(uboot 相关)

3.2.2 reset启动总结

主线流程:reset 的第一条有效指令 b reset 之后 → save_boot_params_ret(保存启动参数后返回)→ 沿主干依次做:

  • cpu_init_crit(关键初始化)→ 一路深入 lowlevel_init(lowlevel_init.S:设置栈、初始化片内地址)→ s_init(在 arch/arm/cpu/armv7/mx6/soc.c 中是个空函数,i.MX6ULL 上直接返回);
  • 虚线框是中间被省略的步骤:向量表重定位、切 SVC32 模式、cpu_init_cp15(配协处理器)等;
  • 最后汇入 _main(arch/arm/lib/crt0.S)------在这里建立 global_data 通用全局数据结构,U-Boot 从此开始向"第二阶段"过渡。

找到_main跟进:

  1. board_init_f(common/board_f.c ), 初始化一系列外设,比如串口、定时器,或者打印一些消息等 ;初始化 gd 的各个成员变量, uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。

    round,边界

    mmu区域,预留给os的mmu的TLB使用,(快表,转换旁路缓存器)

    reserve_uboot,从ocram,搬运到DDR 的地址。

    malloc ,给uboot期间的 内存池

    board, 开发板相关信息 struct bd_t

    data ,uboot的通用全局变量 struct gd_t

    stack 栈区,这个阶段的代码是 c+ asm ,c环境需要栈被初始化好。

    最下面的区域,预留出来,是空白的。

  2. relocate_code (arch/arm/lib/relocate.S), 代码拷贝,重定位uboot

  3. relocate_verctor(arch/arm/lib/relocate.S:27), 重定位向量表

  4. c_runtime_cpu_setup (arch/arm/cpu/armv7/start.S) 初始化c运行库

  5. board_init_r(common/board_r.c),板级初始化,board_init_f初始化剩下的。->initcall_run_list(函数指针)

3.3 bootz

bootz 是 U-Boot 命令,专门用来启动 ARM32 Linux 的zImage压缩内核镜像。

bootz/bootm 阶段关键结构体

bootz 把"启动操作系统"拆成 10 个步骤,每步用一个位标志表示(include/image.h 定义宏、common/bootm.c 使用),镜像信息则统一记录在 bootm_headers 结构体里:

bootm_headers 结构体(include/image.h)是"启动档案袋",记录 OS 镜像信息、入口地址、设备树地址等。启动过程本身则被拆成 10 个位标志(0x1 → 0x200,都是 2 的幂,可按位或组合):

  • 前半段:START → FINDOS → FINDOTHER → LOADOS → RAMDISK → FDT ------ 找镜像、搬镜像、展开设备树;
  • 后半段:OS_CMDLINE → OS_PREP → OS_GO ------ 传 bootargs、最后准备、跳转;
  • 中间夹着一个特殊的 FAKE_GO("Almost run the OS"):假跳转,只走流程不真进内核,用来测试启动链路是否通畅。

倒计时归零后,U-Boot 一步步走到 kernel_entry 跳进 Linux 内核:

  • 灰色(U-Boot 常规流程):run_main_loop → cli_loop → cmd_process。倒计时归零时自动执行 bootargs 里的 bootz 命令;
  • 蓝色(bootz 处理):do_bootz(cmd/bootz.c,OS 类型 JH_OS_LINUX)→ bootz_start(置 START 标志)→ do_bootm_states(FINDOS|FINDOTHER)→ bootm_os_get_boot_func ------ 这一步是关键,它按 OS 类型从 boot/os[] 函数表里取出对应的启动函数 → boot_selected_os 拿到 boot_fn = do_bootm_linux → boot_prep_linux 准备启动参数和设备树;
  • 绿色(交棒):boot_jump_linux 通过 kernel_entry 跳入内核 ------ U-Boot 使命结束。

四、 U-Boot 启动整体框架

i.MX6ULL的启动过程可以分为芯片级引导(Boot ROM / SPL)和 U-Boot 主体(汇编与C语言阶段)两个大的部分。整体框架如下:

  1. Boot ROM阶段:芯片上电后,CPU从内部固化的 Boot ROM 开始执行,根据 BOOT 引脚电平判断启动设备(如 SD 卡或 eMMC)。Boot ROM 会读取镜像头部的 DCD(Device Configuration Data)来初始化 DDR 内存,并将 U-Boot 镜像加载到 DDR 中。

  2. SPL 阶段(可选) :如果是 SPL 架构,SPL 会首先执行极早期的 CPU 初始化,完成 DDR 控制器初始化,然后从存储设备加载完整的 U-Boot 到 DDR 中。咱们手上用的imx6ull的uboot中没有开启SPL,需要注意下。

  3. U-Boot汇编阶段(第一阶段) :从 _start 入口开始,执行 reset 函数,关闭中断、设置 CPU 为 SVC 模式、初始化 CP15 协处理器,并建立早期的栈和全局数据(GD,描述板子信息的,cpu等信息的地方),最终跳转到 C 语言入口 _main。

  4. U-Boot C语言阶段(第二阶段):

    • 板级前期初始化(board_init_f):初始化串口、定时器等基础外设,计算 U-Boot 在 DDR 中的重定位目标地址。
    • 代码重定位(relocate_code):将 U-Boot 自身从当前的加载地址(如 SRAM 或 DDR 低地址)搬运到 DDR 的高地址区域,为后续加载 Linux 内核和设备树腾出低地址内存空间。
    • 板级后期初始化(board_init_r):初始化完整的硬件外设(如网卡、MMC/eMMC、USB 等),加载环境变量,初始化控制台。
    • 主循环(main_loop):进入命令行交互或自动执行 bootcmd 环境变量,加载 Linux 内核镜像(zImage)和设备树(dtb),最终跳转启动内核。

五、关键函数梳理

以下是 U-Boot 启动过程中最核心的函数及其作用:

关键函数 所在阶段 核心作用
_start / reset 汇编阶段 U-Boot 入口,执行异常向量表,关闭 IRQ/FIQ 中断,设置 CPU 为 SVC 模式,初始化 CP15。_start在arch/ARM/lib/vectors.S
_main 汇编转 C 阶段 位于crt0.S,负责设置早期栈,初始化全局数据结构(GD),并依次调用后续的 C 语言初始化函数。
board_init_f C 语言前期 执行init_sequence_f序列,初始化早期 malloc、串口、定时器,获取 DDR 大小,计算重定位地址。
relocate_code 汇编阶段 将 U-Boot 代码、BSS 段等从加载地址复制到 DDR 高地址运行,避免与内核镜像发生内存覆盖。
board_init_r C 语言后期 执行init_sequence_r序列,初始化完整的外设驱动模型(MMC、网络等),加载环境变量,使能中断。
main_loop C 语言主循环 处理用户输入,倒计时结束后自动执行bootcmd命令(如bootz),引导内核启动。

六、U-Boot 如何给 Linux 传递参数

U-Boot 引导 Linux 启动时,主要通过寄存器传参和环境变量配置两种方式将硬件信息和系统配置传递给 Linux 内核:

6.1 寄存器传参(遵循 ARM 启动规范)

在 U-Boot 最终跳转到 Linux 内核时,会调用 kernel_entry(0, machid, fdt_addr) 函数。此时,U-Boot 通过 ARM 寄存器向内核传递关键参数:

  • R0:固定传递值 0。
  • R1:传递机器 ID(Machine ID,machid),用于内核识别硬件平台。这个参数老 uboot 中使用,新版本使用设备树根目录的compatible属性识别机器 ID。
  • R2:传递设备树(Device Tree, DTB)在内存中的物理地址(fdt_addr)。现代 Linux 内核主要依赖设备树来识别硬件资源,这是最核心的传参方式。

6.2 环境变量传递(通过设备树节点)

U-Boot 中的两个核心环境变量直接决定了 Linux 的启动行为,它们会被 U-Boot 写入到设备树的 chosen 节点中传递给内核:

  • bootargs:这是传递给 Linux 内核的命令行参数字符串。它包含了诸如控制台设备(如 console=ttymxc0,115200)、根文件系统位置(如 root=/dev/mmcblk1p2)、读写权限(rw)等关键信息。
  • bootcmd:这是 U-Boot 倒计时结束后默认执行的命令。它定义了如何从存储介质(eMMC/SD 卡)或网络(TFTP)将 zImage 和 .dtb 文件加载到 DDR 的指定内存地址(如 0x80800000 和 0x83000000),并最终调用 bootz 命令启动内核。
相关推荐
@三十一Y1 小时前
Linux:实现简易的shell
linux
2301_777998342 小时前
TCP连接断开:进程终止、主机断电与网络断开
linux·服务器·网络·tcp/ip
星恒随风2 小时前
Linux进程基础(二):进程调度、上下文切换、O(1)调度器、环境变量与虚拟地址空间
linux·笔记·学习·状态模式
xbzb3 小时前
一边装一边报错:CentOS 7 + LNMP 跑 ECShop 的常见故障与解法
linux·运维·centos
学linux的QQ蛋3 小时前
图解 Linux 驱动编译进内核的完整工作流程
linux·运维·服务器
承渊政道3 小时前
Linux网络学习【UDP Socket编程实战:网络命令与客户端访问Linux验证】
linux·网络·学习·ubuntu·编程实战·udp socket
程序员-Benothing3 小时前
正则表达式详解:BRE、ERE、PCRE语法与实战案例
linux·运维·服务器
软件架构师-叶秋3 小时前
V853方案之ubuntu开发环境搭建
linux·运维·服务器·全志·v853
lzx_0023 小时前
Linux 开发工具(二) 简单认识一下编辑器 —— vim
linux·编辑器·vim