【IMX6ULL Linux系统移植】SD卡分区 | uboot详解
文章目录前言一、SD分区布局1.1 概览1.2 MBR分区表1.3 U-Boot1.4 zImage内核与 DTB设备树1.5 Rootfs根文件系统1.6 总结启动时的联动流程二、uboot2.1 uboot概念及启动流程2.2 uboot交互命令与环境变量2.2.1 交互命令2.2.2 环境变量2.3 编译2.4 uboot烧录2.5 uboot启动的三个阶段2.5.1 i.MX6ULL 内部 ROMBoot ROM 阶段2.5.2 U‐Boot 启动第一阶段 U‐Boot2.5.3 U‐Boot 主程序第二阶段真正的 U‐Boot三、uboot解析(启动过程梳理)3.1 找到uboot运行起点3.2 reset3.2.1 reset逐步梳理3.2.2 reset启动总结3.3 bootz四、 U-Boot 启动整体框架五、关键函数梳理六、U-Boot 如何给 Linux 传递参数6.1 寄存器传参遵循 ARM 启动规范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-BootU-Boot引导加载程序原始扇区存储非文件形式存储位置SD卡的第1KB1024字节偏移量处位于第一个分区之前的“保留区域”。存储方式裸数据写入Raw Binary。脚本使用dd命令直接跳过文件系统将.imx镜像写入块设备的绝对地址。关键细节bs1024 seek1 代表从第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设备树存储在分区 1FAT32 文件系统。存储介质/dev/sdx1第一个分区。文件系统格式FAT32卷标为 boot。目录结构这两个文件被复制到该分区的根目录(/)下形成以下路径/zImage内核镜像/emmc.dtb 或 /nand.dtb根据传入参数筛选出的设备树文件设计原因i.MX6 的 U-Boot 内置了 fatload 命令能直接读取 FAT32 分区。启动时U-Boot 会从该分区的根目录加载 zImage 和设备树到内存中而不需要解析复杂的 EXT 文件系统从而简化了启动流程。1.5 Rootfs根文件系统存储在分区 2EXT3 文件系统。存储介质/dev/sdx2第二个分区。文件系统格式EXT3卷标为 rootfs注意前面分析过脚本中存在变量拼写Bug需手动修复格式化部分。目录结构脚本将 filesystem/.tar.压缩包解压到该分区的根目录(/)下。存储内容解压后该分区内会生成标准的 Linux 根目录结构例如 /bin、/etc、/lib、/usr、/home 等。内核模块驱动脚本额外将 modules/.tar.解压到该分区下的 /lib/modules/ 目录中。这确保了内核启动后能正确加载对应版本的硬件驱动。1.6 总结启动时的联动流程上电 - i.MX6 Boot ROM 读取偏移1KB处的U-Boot到RAM并运行。U-Boot运行 - 读取MBR分区表找到第一个活动分区FAT32。U-Boot加载内核 - 通过fatload从分区1的根目录读取zImage和DTB到内存。启动内核 - 内核初始化后挂载分区2EXT3作为根文件系统/并从/lib/modules/中加载驱动。系统启动完成。二、uboot2.1 uboot概念及启动流程uboot是bootloader的一种主要作用是在内核加载前对硬件设备初始化并为内核的运行提供引导功能。通过系统的架构图可以知道片内 RAM 只有 128KB装不下完整 U‐Boot因此uboot 启动必须分两阶段。启动流程如下所示2.2 uboot交互命令与环境变量2.2.1 交互命令环境变量操作命令主要使用命令功能格式printenv查看环境变量printenvsetenv新建 / 修改环境变量setenv 环境变量名 环境变量值saveenv保存环境变量saveenv网络命令主要使用命令功能格式示例ping测试网络是否连通ping destipping 192.168.1.100tftp通过网络利用tftp协议下载文件到SDRAMtftp [localAddress] [[hostIPaddr:]bootfilename]tftp 80800000 zImagenfs通过网络利用nfs协议下载文件到SDRAMnfs [localAddress] [[hostIPaddr:]bootfilename]nfs 80800000 192.168.1.100:/home/linux/nfs/zImage内存操作命令了解即可命令功能格式示例md显示内存值md[.b,.w,.l] address [#of objects]md.b 80000000 14nm修改指定地址的内存值nm[.b,.w,.l] addressnm.l 80000000mw使用一个指定的数据填充一段内存mw[.b,.w,.l] address value [count]mw.l 80000000 0A0A0A0A 10cp数据拷贝命令cp[.b,.w,.l] source target countcp.l 80000000 80000100 10mmc操作命令(了解即可)命令功能格式示例mmc info输出MMC设备信息mmc infommc infommc read读取MMC中的数据mmc read addr blk cntmmc read 80800000 600 10mmc write向MMC设备写入数据mmc write addr blk# cntmmc write 80800000 2 2E6mmc rescan扫描MMC设备mmc rescanmmc rescanmmc part列出MMC设备的分区mmc partmmc partmmc dev切换MMC设备0表示SD卡1表示EMMCmmc dev 0/1mmc dev 0mmc list列出当前有效的所有mmc设备mmc listmmc listmmc erase擦除MMC设备的指定块mmc erase blk# cntmmc erase 600 10boot命令bootz需掌握命令功能格式示例bootz用于启动zImage镜像文件bootz [addr [initrd [:size] [fdt]]]bootz 80800000 - 83000000bootm用于启动uImage镜像文件bootm addrbootm 80800000boot读取环境变量bootcmd来启动Linux系统bootbootgo跳到指定的地址处执行应用go addr [arg …]go 87800000run用于运行环境变量中定义的命令run envnamerun mmcboot2.2.2 环境变量bootcmdbootcmd保存着uboot默认命令uboot倒计时结束以后就会执行bootcmd中的命令。例如1.从emmc中读取内核、设备树、启动setenv bootcmdmmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;saveenv2.从网络中下载内核、设备树、启动setenv bootcmdtftp 80800000 zImage;tftp 83000000 imx6ull-alientekemmc.dtb;bootz 80800000 - 83000000saveenvbootargsbootargs保存着uboot传递给Linux内核的参数例如1.从emmc中启动Linux系统setenv bootargsconsolettymxc0,115200 root/dev/mmcblk1p2 rootwait rwsaveenv2.从nfs网络中启动Linux系统setenv bootargsconsolettymxc0,115200 root/dev/nfs nfsroot192.168.1.100:/home/linux/nfs/rootfs,prototcp rw ip192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0::eth0:offsaveenv完整的uboot环境变量配置envdefault-a# uboot 变量恢复默认saveenv# 保存变量reset# 重启ubootsetenv ipaddr192.168.1.50# 设置开发板ip地址setenv ethaddr 00:04:9f:04:d2:35# 设置开发板网卡mac地址setenv gatewayip192.168.1.1# 设置开发板网关setenv netmask255.255.255.0# 设置开发板子网掩码setenv serverip192.168.1.100# 设置pc段的ip地址setenv bootargsconsolettymxc0,115200 root/dev/nfs nfsroot192.168.1.100:/home/linux/nfs/rootfs,v3,prototcp rw ip192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off# 设置根文件系统的位置setenv bootcmdtftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000# 设置下载zImagedtbsaveenv#保存变量reset#重启uboot2.3 编译1.解压uboot压缩包并进入uboot源码目录tar-zxvfuboot.tar.gzcduboot2.uboot图形界面是基于ncurses库编写的所以需要先安装该库sudoapt-getinstalllibncurses5-dev3.编译uboot源码makeARCHarmCROSS_COMPILEarm-linux-gnueabihf- distcleanmakeARCHarmCROSS_COMPILEarm-linux-gnueabihf- mx6ull_alientek_emmc_defconfigmakeV0ARCHarmCROSS_COMPILEarm-linux-gnueabihf--j16参数说明ARCH: 指定目标硬件架构CROSS_COMPILE: 设置交叉编译工具链V0: 控制编译输出的详细程度0表示启动静默/精简输出模式1表示完整的编译命令便于调试。j16表示最多16个核心一起编译xxx_defconfig是U-Boot 的默认配置文件本质是预写好的一套配置用来快速生成.config。.config 的来源作用✅ 来源.config是编译 U-Boot 真正使用的配置文件。从 defconfig 生成makeARCHarmCROSS_COMPILEarm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig读取configs/mx6ull_alientek_emmc_defconfig这个板级预制模板在源码顶层目录自动生成.config。✅ 作用.config保存所有编译开关控制 U-Boot 编译的时候编译哪些驱动EMMC、网卡、串口、LCD 等驱动要不要编译进 U-Boot启用哪些命令tftp、bootz、fatload、saveenv等命令是否支持芯片 / 板子参数CPU 架构、时钟、内存大小、设备树相关选项编译宏开关y编译进镜像n不编译m模块U-Boot 很少用 mMake 编译 U-Boot 时读取 .config 里面的宏定义决定代码哪些参与编译、哪些忽略。没有.configmake 无法编译。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 内部 ROMBoot ROM 阶段这一步不属于 U-Boot是 boot rom 中固化的程序但必须先做才能启动 U-Boot。作用检测启动模式SD 卡、EMMC、NAND、QSPI。sd/emmc 设备没有完全初始化内部程序写死只会读取前面一部分内容。根据拨码开关有关决定了启动方式初始化最基础的时钟 arm_pll 396Msystem_PLL 528M读取启动设备的第一个镜像uboot.imx 头部信息片上的 Rom 中程序的功能。刚上电 pc 0 ; rom 就在 0 地址把 uboot.imx 头部信息加载到片内 RAMOCRAM 0x009000002.5.2 U‐Boot 启动第一阶段 U‐Boot有时候也称作SPL阶段Secondary Program Loader二级程序加载器。运行位置片内 RAMOCRAM核心使命初始化 DDR然后加载完整 U‐Boot。编译直接得到的是 U-Boot.bin裸程序它自己没法被 ROM 识别所以在前面拼了 1KB 头部IVT BOOT_DATA DCD拼完的才是烧进 SD 卡的 u-boot.imxIVTImage Vector Table镜像的“目录”里面写着程序入口和各配置块的位置BOOT_DATA搬运说明——把程序复制到哪个地址start 0x8780_0000、总共多大DCDDevice Configuration Data一串“寄存器配置脚本”ROM 照着它逐条写入把 DDR 控制器等硬件提前配好。这 7 步常被叫作“U-Boot 第一阶段”但严格说干活的主体是芯片里固化的 boot ROM它只是借用了 u-boot.imx 头部里的 DCD 数据而已——所以才叫 SPLSecondary Program Loader二级加载器ROM 是一级。IVT、DCD与Boot Data的协作关系这三者共同构成了I.MX系列芯片完整的启动镜像头部。其工作流程大致如下芯片上电 BootROM 开始执行。查找IVT BootROM 根据启动设备如SD卡在预定义的偏移位置如1KB处找到 IVT 。解析IVT BootROM 读取 IVT 从中获取 DCD 、 Boot Data 和用户程序 entry 的地址。执行DCD BootROM 根据 IVT 提供的地址找到 DCD 并执行其中的命令完成硬件特别是DDR的初始化。解析Boot Data BootROM 根据 IVT 提供的地址找到 Boot Data 得知用户程序的大小和应该被加载到内存的哪个地址。加载并跳转 BootROM 将用户程序从存储设备复制到 Boot Data 指定的内存地址然后跳转到IVT 中 entry 字段指定的地址将CPU控制权交给用户程序。2.5.3 U‐Boot 主程序第二阶段真正的 U‐Boot运行位置DDR 0x87800000这是在命令行显示的 U‐Boot。完成的工作1. 初始化 DDR最关键2. 初始化时钟CPU 主频、总线时钟3. 初始化启动设备SD/EMMC/NAND4. 加载 Linux 内核和设备树到内存5. 传递启动参数给内核6. 启动 Linux 内核三、uboot解析(启动过程梳理)3.1 找到uboot运行起点先编译uboot在uboot的顶层目录会生成u-boot.lds。可以先从 u-boot.lds 和 u-boot.map 入手分析 U-Bootu-boot.lds 链接脚本u-boot.map uboot在内存地址中的映射(或者说位置)内存映射文件。链接完成后输出记录所有段、函数、全局变量对应的内存地址与占用大小用来查看代码 / 变量在内存中的位置用于地址排查、镜像空间分析、定位链接相关问题。下面是链接脚本的代码/* 指定输出文件格式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 reset3.2.1 reset逐步梳理跟进reset看一下save_boot_params_ret(关闭fiqirqcpu 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_initlowlevel_init.S设置栈、初始化片内地址→s_init在 arch/arm/cpu/armv7/mx6/soc.c 中是个空函数i.MX6ULL 上直接返回虚线框是中间被省略的步骤向量表重定位、切 SVC32 模式、cpu_init_cp15配协处理器等最后汇入_mainarch/arm/lib/crt0.S——在这里建立global_data通用全局数据结构U-Boot 从此开始向“第二阶段”过渡。找到_main跟进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_tdata uboot的通用全局变量 struct gd_tstack 栈区这个阶段的代码是 c asm ,c环境需要栈被初始化好。最下面的区域预留出来是空白的。relocate_code(arch/arm/lib/relocate.S), 代码拷贝重定位ubootrelocate_verctor(arch/arm/lib/relocate.S:27), 重定位向量表c_runtime_cpu_setup(arch/arm/cpu/armv7/start.S) 初始化c运行库board_init_r(common/board_r.c)板级初始化board_init_f初始化剩下的。-initcall_run_list(函数指针)3.3 bootzbootz是 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_bootzcmd/bootz.cOS 类型 JH_OS_LINUX→bootz_start置 START 标志→do_bootm_statesFINDOS|FINDOTHER→bootm_os_get_boot_func—— 这一步是关键它按 OS 类型从boot/os[]函数表里取出对应的启动函数 →boot_selected_os拿到boot_fndo_bootm_linux→boot_prep_linux准备启动参数和设备树绿色交棒boot_jump_linux通过kernel_entry跳入内核 —— U-Boot 使命结束。四、 U-Boot 启动整体框架i.MX6ULL的启动过程可以分为芯片级引导Boot ROM / SPL和 U-Boot 主体汇编与C语言阶段两个大的部分。整体框架如下Boot ROM阶段芯片上电后CPU从内部固化的 Boot ROM 开始执行根据 BOOT 引脚电平判断启动设备如 SD 卡或 eMMC。Boot ROM 会读取镜像头部的 DCDDevice Configuration Data来初始化 DDR 内存并将 U-Boot 镜像加载到 DDR 中。SPL 阶段可选如果是 SPL 架构SPL 会首先执行极早期的 CPU 初始化完成 DDR 控制器初始化然后从存储设备加载完整的 U-Boot 到 DDR 中。咱们手上用的imx6ull的uboot中没有开启SPL需要注意下。U-Boot汇编阶段第一阶段从_start入口开始执行reset函数关闭中断、设置 CPU 为 SVC 模式、初始化 CP15 协处理器并建立早期的栈和全局数据GD描述板子信息的cpu等信息的地方最终跳转到 C 语言入口_main。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_fC 语言前期执行init_sequence_f序列初始化早期 malloc、串口、定时器获取 DDR 大小计算重定位地址。relocate_code汇编阶段将 U-Boot 代码、BSS 段等从加载地址复制到 DDR 高地址运行避免与内核镜像发生内存覆盖。board_init_rC 语言后期执行init_sequence_r序列初始化完整的外设驱动模型MMC、网络等加载环境变量使能中断。main_loopC 语言主循环处理用户输入倒计时结束后自动执行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传递机器 IDMachine IDmachid用于内核识别硬件平台。这个参数老 uboot 中使用新版本使用设备树根目录的compatible属性识别机器 ID。R2传递设备树Device Tree, DTB在内存中的物理地址fdt_addr。现代 Linux 内核主要依赖设备树来识别硬件资源这是最核心的传参方式。6.2 环境变量传递通过设备树节点U-Boot 中的两个核心环境变量直接决定了 Linux 的启动行为它们会被 U-Boot 写入到设备树的chosen节点中传递给内核bootargs这是传递给 Linux 内核的命令行参数字符串。它包含了诸如控制台设备如consolettymxc0,115200、根文件系统位置如root/dev/mmcblk1p2、读写权限rw等关键信息。bootcmd这是 U-Boot 倒计时结束后默认执行的命令。它定义了如何从存储介质eMMC/SD 卡或网络TFTP将 zImage 和 .dtb 文件加载到 DDR 的指定内存地址如0x80800000和0x83000000并最终调用 bootz 命令启动内核。