2026/5/21 8:44:46
网站建设
项目流程
画图标网站,二维码生成器怎么使用,wordpress 后台文章,怎么快速推广大家好#xff0c;我是嵌入式兔哥。 #x1f430;
最近有不少兄弟在准备大厂的嵌入式驱动面试#xff0c;U-Boot 启动流程绝对是必问的“八股文”之首。但很多人只背得下来“第一阶段汇编、第二阶段C语言”#xff0c;一被问到“重定位时 .rel.dyn 段是怎么修正的#xff…大家好我是嵌入式兔哥。 最近有不少兄弟在准备大厂的嵌入式驱动面试U-Boot 启动流程绝对是必问的“八股文”之首。但很多人只背得下来“第一阶段汇编、第二阶段C语言”一被问到“重定位时 .rel.dyn 段是怎么修正的”或者“内核启动前 r0/r1/r2 寄存器里存了什么”就哑火了。今天不整虚的我基于 NXP i.MX6ULL 的源码分析整理了四张核心调用栈视图。我们就顺着这四张图把 U-Boot 从上电复位到移交控制权给 Linux 的全过程扒得干干净净。️ 第一阶段汇编入口与硬件基础 (Reset Low Level)这一阶段的核心任务就一个字“活”。CPU 刚上电不仅 Cache 没开连栈都没有C 代码根本跑不起来。我们需要用汇编语言构建最基础的运行环境。1. 入口与模式切换一切的起点在u-boot.lds链接脚本指定的ENTRY(_start)。代码被加载到.text段地址通常是0X87800000。复位跳转arch/arm/lib/vectors.S中的_start也就是复位向量第一条指令直接b reset。模式设置进入save_boot_params_ret后第一件事就是修改CPSR 寄存器。将 CPU 切换到SVC32 (Supervisor) 模式。关键点同时必须禁止 FIQ 和 IRQ 中断CPSR bit 7-6 置 1。在 Bootloader 阶段我们不需要中断干扰直到内核启动。2. 硬件环境清理 (cpu_init_cp15)在调用 C 语言之前硬件环境必须是“纯净”的。关闭 MMU 和 Cache这是面试常考点。为什么因为此时还没建立页表虚拟地址无法映射且初始化阶段我们直接操作物理地址开启 Cache 会导致数据一致性问题。清除 TLB确保之前的地址转换记录被清空。3. 建立临时栈 (lowlevel_init)这是第一阶段的最后一步也是为 C 语言铺路的这一步。SP 指针赋值我们将 SP 指针指向内部 RAM (OCRAM) 的地址0X0091FF00CONFIG_SYS_INIT_SP_ADDR。注意这时候还不敢用外部 DDR因为 DDR 控制器还没初始化好预留 GD 空间U-Boot 核心全局变量global_data(GD) 也是放在这里的。代码中sp - GD_SIZE(248字节)并将结果存入R9 寄存器。记下来在 ARM U-Boot 中R9 寄存器永远指向 gd 结构体千万别改它。 第二阶段前置 C 语言与代码重定位 (Board Init F Relocation)汇编做完了脏活累活_main(arch/arm/lib/crt0.S) 终于接管了流程开始进入 C 语言的世界。这一阶段的核心任务是初始化 DDR并将自己搬运到内存的高地址运行。1. Board Init F (Flash 运行阶段)board_init_f是核心函数它通过initcall_run_list执行了一系列初始化序列init_sequence_f。Serial Init初始化串口这时候你才能在终端看到打印信息。DRAM Init获取 DDR 大小例如 512MB。计算重定位地址这是重头戏。U-Boot 会计算出 DDR 顶端的一个地址如0X9FF47000作为自己新的“豪宅”。2. 代码自搬运 (Relocation)面试中的“深水区”就在这里。为什么要重定位为了把低地址腾出来给 Linux 内核使用。Copy Looprelocate_code汇编函数会将 U-Boot 的代码段、数据段从源地址0X87800000完整拷贝到目的地址0X9FF47000。Fix .rel.dyn (动态符号修正)这是最难理解的。代码拷贝后程序里引用的全局变量地址还是老的指向 0X8780…访问就会出错。解决U-Boot 遍历.rel.dyn段取出每一个需要修正的 Label给它加上一个Offset(新地址 - 旧地址 0X18747000)。这一步做完代码才真正具备了在新地址运行的能力。3. 环境切换搬运完成后_main里的汇编代码会重定位向量表将 CP15 的 VBAR 寄存器指向新的 DDR 地址。Clear BSS将 BSS 段清零。Jump最后ldr pc, board_init_r直接飞到 DDR 中的 C 函数继续执行。 第三阶段后置板级初始化与主循环 (Board Init R Main Loop)此时代码已经在 DDR 的高地址飞奔了资源不再受限。board_init_r开始“满血”初始化。1. 复杂外设初始化 (init_sequence_r)Enable Cachesinitr_caches。现在代码在 DDR 跑了必须开启 I-Cache 和 D-Cache否则速度太慢。MMC/Net/USB初始化 EMMC 控制器、FEC 网卡驱动。这时候你才能用tftp下载内核或者从 EMMC 读取镜像。2. 进入主循环 (main_loop)初始化全部结束后进入死循环common/main.c。Bootdelay读取环境变量bootdelay开始倒计时通常是 3 秒。Autoboot无人打断执行bootcmd环境变量里的命令通常是启动内核。有人按下回车进入cli_loop启动 Hush Shell 解析器等待用户输入命令如printenv,bootz。 第四阶段启动 Linux 内核 (The Final Jump)假设倒计时结束执行bootz命令启动 Linux。这是 U-Boot 生命周期的终点。1. 最后的准备 (do_bootm_states)关闭中断在bootm_disable_interrupts中再次确保中断彻底关闭。Linux 内核启动初期对中断极其敏感如果有未处理的中断内核会直接崩溃。设备树处理boot_prep_linux会解析bootargs环境变量如 console设置、rootfs路径并将这些信息注入到设备树DTB的/chosen节点中。这就解释了为什么 U-Boot 里的设置能传给内核。2. 寄存器设置 (ABI 约定)在boot_jump_linux真正跳转前必须满足 ARM Linux 的启动协议设置三个通用寄存器R0 0必须为 0。R1 Machine ID机器 ID现在设备树时代这个通常不重要了但保留兼容。R2 DTB 地址最重要告诉内核设备树在内存的哪个位置。3. 移交控制权 (kernel_entry)执行最后一句代码Ckernel_entry(0, machid, r2);这实际上是将 PC 指针直接强制修改为内核的入口地址images-ep。也就是在这一瞬间U-Boot 的使命彻底结束Linux 内核开始接管世界。 兔哥总结兄弟们U-Boot 看起来复杂其实剥离掉细枝末节就是一条线汇编阶段关中断、关Cache、配堆栈 (SRAM)。前置C阶段算地址、搬代码、修符号 (Relocation)。后置C阶段开Cache、配网卡、读脚本 (Bootcmd)。启动阶段传参数、设寄存器、改PC指针 (Jump)。把这四张图里的流程印在脑子里下次面试官再问启动流程你就直接把这几个关键函数和寄存器甩给他记得点赞收藏我们下期讲讲 Linux 内核启动的第一行代码干了啥