news 2026/5/15 12:46:54

U-Boot分析【学习笔记】(9)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
U-Boot分析【学习笔记】(9)

9.5 lowlevel_init.S 分析

在9.4 cpu_init_crit 引入文章末尾我们引出了 armv7 架构下的 lowlevel_init.S

/* * A lowlevel_init function that sets up the stack to call a C function to perform further init. */

从注释中我们可以得知这个函数的作用:
设置栈去调用 C 函数来执行进一步的初始化

为什么 lowlevel_init.S 唯一的核心任务是 Setup a temporary stack(设置临时栈)?
因为 C 语言的运行依赖于栈(Stack)。无论是局部变量的分配、函数调用时的参数传递,还是返回地址(LR)的入栈,如果没有一个合法的栈空间,CPU 只要跳入 C 语言函数就会立刻崩溃(跑飞)。
此时 DDR 还没初始化,所以这个“临时栈”通常被指向 SoC 内部的 SRAM(也叫 IRAM),这是一个不需要任何配置,上电即用的极速存储空间。

9.5.1 设置临时栈

/* * Setup a temporary stack. Global data is not available yet. */#ifdefined(CONFIG_SPL_BUILD)&&defined(CONFIG_SPL_STACK)/* 如果当前是在编译 SPL(体积更小的二级引导程序) */ldr sp,=CONFIG_SPL_STACK#else/* 如果是在编译正常的 U-Boot 镜像(主程序) */ldr sp,=CONFIG_SYS_INIT_SP_ADDR#endifbic sp,sp,#7/* 8-byte alignment for ABI compliance */#ifdefCONFIG_SPL_DMmov r9,#0#else

含义:
设置一个临时的栈,全局数据暂不可用
作用:
在没有任何外部内存(DDR)支持的情况下,利用 SoC 内部资源搭建起 C 语言运行所需的最小化环境。

ldr sp, =…:
这是一个加载指令。它把预先计算好的物理地址(比如 0x0091FF00 这种在 SRAM 里的地址)写入到 CPU 的 SP 寄存器中。

CPU 有了栈:

  1. 可以调用 C 函数了。
  2. 可以处理复杂的嵌套逻辑了。
  3. 可以从汇编语言转向C语言了
bic sp,sp,#7/* 8-byte alignment for ABI compliance */

指令解析:
BIC 是位清除指令。#7 的二进制是 111。这行代码的作用是将 sp 寄存器数值的低 3 位强行清零。
2 字节对齐:需被 2 (2 1 2^121) 整除→ \rightarrow低 1 位为 0。
4 字节对齐:需被 4 (2 2 2^222) 整除→ \rightarrow低 2 位为 0。
8 字节对齐:需被 8 (2 3 2^323) 整除→ \rightarrow低 3 位为 0。
所以这行代码的作用就是让栈帧必须 8 字节对齐

物理意义:
在 ARM 架构中,C 语言的调用约定(ABI)要求栈指针必须是 8 字节对齐的。
效率:
现代 CPU 存取 64 位数据(如 double 或 long long)时,如果地址是对齐的,速度最快。
稳定性:
某些指令(如浮点运算或特殊的 LDRD/STRD 指令)在操作未对齐的地址时,会直接触发硬件异常。

9.5.2 SPL/U-Boot 分支

#ifdefCONFIG_SPL_DMmov r9,#0#else/* * Set up global data for boards that still need it. This will be * removed soon. */#ifdefCONFIG_SPL_BUILDldr r9,=gdata#elsesub sp,sp,#GD_SIZE bic sp,sp,#7mov r9,sp#endif#endif

在设置好栈(SP)之后,U-Boot 开始为它最重要的全局管理机构——Global Data (gd) 划分空间,在 U-Boot 中,gd 结构体记录了整个系统的关键信息(如时钟频率、内存容量、重定向偏移等)。因为此时 DDR 还没好,所以 gd 也必须放在 SRAM 里。

SPL 模式(小镜像模式):直接让寄存器 r9 指向一个预先定义好的静态变量 gdata
为什么 SPL 模式要“指向一个预定义好的静态变量”?
在 SPL 模式下(也就是引导程序的最初阶段),硬件环境非常简陋:
内存非常小:SoC 内部的 SRAM 可能只有 32KB 或 64KB。
工期短、任务重:SPL 的唯一使命就是初始化 DDR,然后把正式版 U-Boot 搬到 DDR 里运行。
为什么要用“静态变量”而不是“动态划范围”?
节省开销:动态计算(比如从栈顶减去一个大小)需要额外的指令。而在编译时直接给 gdata 分配一个固定的地址,指令只需要一条 ldr 就够了。
结构简单:SPL 的逻辑非常固定。既然我知道 SRAM 里哪块地方是空的,我直接在那定义静态变量给全局数据用,既直观又不容易出错。
gdata是什么?
C 语言源码里通常被定义为一个结构体变量,它代表了这块空间的起始点。
既然 gdata 是一个结构体,它本身就占用了一定长度的字节(由 sizeof(struct global_data) 决定)。从 r9 开始往后的这一整块连续内存,都是属于这个结构体的。
所以,并不是只写在 r9 那一个点上,而是根据结构体的定义,像填表一样,把数据填入从 r9 开始往后的那一整片预留区域里。

正式版 U-Boot 模式:动态内存锚定策略
在正式版 U-Boot 启动阶段,硬件环境比 SPL 稍显复杂,为了保证内存利用率的最大化,系统采用了“动态割地”的策略来安置全局数据区。

  1. 将当前的栈指针(SP)向下移动一个 GD_SIZE 的偏移量。
    SUB <目标寄存器>, <第一操作数>, <第二操作数>。
    sub:Subtraction(减法)的缩写。
    sp (目标):计算结果存回 sp(栈指针寄存器)。
    sp (操作数1):读取当前的栈指针位置。
    #GD_SIZE (操作数2):# 前缀代表立即数,说明是一个常量,它的具体数值是由编译器计算出来的 struct global_data 结构体的大小(单位:字节)。
    翻译:把当前的栈顶地址减去 GD_SIZE 这么大的字节数,然后把新的地址作为新的栈顶。
    在 SRAM 极其有限的空间内,并不预留固定地址,而是直接从“临时堆栈”的顶端强行截取一块空间。这块空间被专门封存,作为 global_data 结构体的物理载体,实现了栈与全局数据的“背靠背”共生,防止了内存碎片的产生。
  2. 将地址的低 3 位清零,必须确保新的栈顶(SP)依然符合 8 字节对齐 的规矩,否则后续 C 语言运行会报错。
  3. 把当前切割出来的这块空间的起始地址(即对齐后的新 SP)赋值给 r9 寄存器。,在 ARM 架构中,r9 它被约定为全局数据指针。从此以后,无论是汇编还是 C 语言,只要通过 gd->xxx 访问变量,底层逻辑都是以 r9 为基准进行偏移寻址。r9 成了连接硬件底层与高级 C 逻辑的唯一纽带。

这段代码的作用:
该流程通过对栈空间的动态切分,完成了全局数据区(Global Data)的物理内存分配。它不仅确立了 gd 在内存中的“家”,更通过 r9 寄存器建立了访问这个“中央数据库”的唯一通行证,为与C语言的对接提供了接口

9.5.3 push {ip, lr}

/* * Save the old lr(passed in ip) and the current lr to stack */push{ip,lr}

现场保护:最后的“备份”动作
push:压栈指令。
在刚刚割好并对齐的栈空间里,存入两个地址
ip:start.S 前就由调用者提前存入 ip 的值
lr:上一次改变 lr 寄存器的指令是 start.S 文件中的 bl cpu_init_crit 。bl表示跳转并链接,硬件会自动修改 lr,存入返回地址。

物理定义在 ARMv7 架构(i.MX6ULL 所属架构)中:
l r lrlr(Link Register /r 14 r14r14):
连接寄存器。
其硬件逻辑是:当 CPU 执行bl (Branch with Link) 指令时,硬件会自动将紧随其后的那条指令地址(即返回地址)写入l r lrlr
i p ipip(Intra-Procedure-call scratch register /r 12 r12r12):
过程调用间暂存寄存器。
它是通用寄存器,没有硬件自动写入逻辑。在汇编开发中,它通常被约定用于在函数切换时临时转存数据。
i p ipip的数据来源(跨文件协议)
虽然在你当前的 lowlevel_init.S 文件以及更之前的 start.S 中都没有看到对i p ipip的赋值,但在 U-Boot 的调用链中存在以下硬性约定:
在start.S之前,调用者就已经执行:mov ip, lr
此时,i p ipip存储的是比 start.S 更高层级的“总回路”地址
l r lrlr的状态变化
当 CPU 执行权转移到 cpu_init_crit 时:此时的l r lrlr存储的是 cpu_init_crit 执行完后应该返回的地址,虽然随后在 cpu_init_crit 函数中执行了 b lowlevel_init,但由于 b 指令不具备“链接”功能,它不会修改l r lrlr。因此,此时的l r lrlr依然指向 start.S。。
即将发生的动作:接下来的 bl s_init 指令是一个跳转指令。执行 bl 的瞬间,硬件会用 s_init 之后的指令地址覆盖掉l r lrlr中的旧值。
push {ip, lr} 的数据保护逻辑
由于接下来要进入 C 语言环境(s_init),寄存器中的值是不安全的。
i p ipip:保存了回到比 start.S 更高层级(总回路)的路径。
l r lrlr:保存了回到 start.S(即 bl cpu_init_crit 的下一行)的路径。
总结:
“通过 push 动作,我们将受硬件指令(bl)影响而变得‘易失’的寄存器路标,转化为了 SRAM 中‘稳固’的内存记录,从而保障了 C 语言函数调用后的路径溯源。”

9.5.4 bl s_init

/* * Call the very early init function. This should do only the * absolute bare minimum to get started. It should not: * * - set up DRAM * - use global_data * - clear BSS * - try to start a console * * For boards with SPL this should be empty since SPL can do all of * this init in the SPL board_init_f() function which is called * immediately after this. */bl s_init

调用极早期初始化函数(s_init)。该函数应仅执行启动所需的“绝对最小化”操作。它不应该执行以下操作:

  • set up DRAM初始化内存(DRAM):此时内存控制器尚未配置,外部 DDR 芯片处于不可用状态。
  • use global_data使用全局数据(gd):虽然汇编中已将 r9 指向了 gd 区域,但该结构体尚未被填充有效内容,访问将导致逻辑错误。
  • clear BSS清理 BSS 段:BSS 段存储的是未初始化的全局变量。此时 BSS 还没被清零,意味着你在 C 语言里定义的未初始化全局变量(如 static int flag;)里面全是随机的垃圾值。
  • try to start a console尝试启动控制台:串口(UART)驱动尚未加载,此时调用 printf 等打印函数会导致程序崩溃。

对于带有 SPL(二级引导程序)的单板,此函数通常为空。因为 SPL 会在紧随其后的 board_init_f() 函数中完成所有这些初始化工作。

在 U-Boot 的函数中,s_init 属于 Low Level Init(低级初始化)阶段。
位置:它是汇编环境向 C 环境交接后的第一站。
职责:执行芯片级(而非板级)的最小硬件补丁。
移植要点:如果移植的是 i.MX6ULL 这种成熟芯片,这个函数你基本不用改。它是厂商(NXP/Freescale)已经写好的底层固件逻辑。

s_init 是 U-Boot 架构中定义的一个 SOC 级初始化接口。它在汇编设置好栈后立即被调用,运行在 DRAM 初始化之前。
它的核心作用是执行芯片级的紧急硬件修复(如关闭看门狗或修复时钟 Bug)。在移植 i.MX6ULL 时,我们只需确保它存在即可,因为它主要处理芯片通用的底层限制,而不涉及具体的板级外设开发。

9.5.5 pop {ip, pc}

pop{ip,pc}

这是 lowlevel_init 汇编函数的最后一条指令
物理动作:
精准的出栈:
从栈顶(你刚才割出的 SRAM 空间)弹出第一个 4 字节数据,放入i p ipip
从栈顶弹出第二个 4 字节数据,放入p c pcpc(程序计数器)。
第一个出栈的(给i p ipip):是 start.S 之前调用者备份的那个原始地址。
第二个出栈的(给p c pcpc):是 start.S 执行 bl cpu_init_crit 时产生的返回地址。
关键在于p c pcpc的改变:
在 ARM 架构中,一旦你往p c pcpc里写入一个地址,CPU 下一个时钟周期就会直接去那个地址取指令。
结果:CPU 并没有回到 cpu_init_crit 函数的结尾,而是直接跳转到了 start.S 中 bl cpu_init_crit 的下一句 继续执行。

为什么还要保存i p ipip
栈对齐(硬件需求):
ARM 架构要求进入 C 环境前栈须 8 字节对齐。压入两个 4 字节寄存器(i p ipipl r lrlr)能完美保持栈平衡。
数据保险(软件协议):
i p ipip中存有更早期的“总回路”地址。由于 C 语言函数(s_init)会无条件破坏i p ipip寄存器,必须通过压栈将其保护在 SRAM 中,待返回时通过 pop 还原,防止后续流程“迷路”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/15 12:44:04

HI3516A电源树分析实战:为什么PowerTree提取是电热仿真的第一步?

HI3516A电源树分析实战&#xff1a;为什么PowerTree提取是电热仿真的第一步&#xff1f; 在高速PCB设计中&#xff0c;电源完整性分析如同建筑物的地基&#xff0c;而PowerTree提取则是绘制这张地基蓝图的第一步。当我们面对HI3516A这类高性能处理器时&#xff0c;其复杂的供电…

作者头像 李华
网站建设 2026/5/15 12:41:29

高德千问开源行业首个三端的端云一体原生A2UI框架;魔芯科技连获两轮亿元融资,世界模型走出第三条技术路线;Anthropic启动300亿融资

1. 高德千问开源AGenUI&#xff0c;三端原生A2UI框架降低Agent开发门槛牛喀网获悉&#xff0c;高德与阿里千问C端应用团队&#xff0c;联合开源了行业首个覆盖iOS、Android、HarmonyOS三端的端云一体原生A2UI框架AGenUI。技术层面&#xff0c;该框架基于GoogleA2UI协议&#xf…

作者头像 李华
网站建设 2026/5/15 12:41:05

如何5分钟部署Zabbix多GPU监控模板:告别手动配置烦恼

如何5分钟部署Zabbix多GPU监控模板&#xff1a;告别手动配置烦恼 【免费下载链接】zabbix-nvidia-smi-multi-gpu A zabbix template using nvidia-smi. Works with multiple GPUs on Windows and Linux. 项目地址: https://gitcode.com/gh_mirrors/za/zabbix-nvidia-smi-mul…

作者头像 李华
网站建设 2026/5/15 12:39:42

手把手教你用 TensorFlow.js 在浏览器里跑机器学习模型

手把手教你用 TensorFlow.js 在浏览器里跑机器学习模型 引言 你有没有想过&#xff0c;在不依赖后端服务器的情况下&#xff0c;直接在浏览器里训练和运行机器学习模型&#xff1f;这听起来像是科幻小说里的情节&#xff0c;但 TensorFlow.js 让这一切变成了现实。 TensorFlow.…

作者头像 李华
网站建设 2026/5/15 12:39:40

5分钟快速上手:JavaScript PPT自动化生成终极指南

5分钟快速上手&#xff1a;JavaScript PPT自动化生成终极指南 【免费下载链接】PptxGenJS Build PowerPoint presentations with JavaScript. Works with Node, React, web browsers, and more. 项目地址: https://gitcode.com/gh_mirrors/pp/PptxGenJS 还在为每周重复制…

作者头像 李华