Uboot 大全 | uboot 启动流程(一)
- 不带atf启动
- Atf与U-boot组合方式启动
- 从cpu处理流程
- _main流程分析
- U-boot重定位前的GD及内存规划
U-boot总体流程
atf基本启动流程为:BL1 – BL2 – BL31 – BL32 – BL33(uboot),即在bl32启动完成后再启动uboot,uboot作为启动链中作为最后一级镜像,用于启动最终的os。Atf是arm为了增强系统安全性引入,只支持armv7和armv8架构的可信固件。而uboot是通用的嵌入式系统引导程序,其可以支持包含arm在内的多种处理器架构,如mips、riscv、powerpc以及x86等,且其历史比atf更加久远。因此默认情况下uboot并不需要与atf共同启动,而其自身就被设计为支持完整的多级启动链,该启动链被设计为最多可包含spl、tpl和uboot三个阶段。接下来我们通过一些典型启动流程,来看下这些阶段的一些组合关系吧。
不带atf启动
spl被称为secondary program loader,在启动链中一般由bootrom加载而作为第二级启动镜像(bl2),它主要用于完成一些基础模块和ddr的初始化,以及加载下一级镜像uboot。由于spl需要被加载到sram中执行,对于有些sram size比较小的系统,可能无法放入整个spl镜像,tpl即是为了解决该问题引入的。加入了tpl之后,可将spl的功能进一步划分为两部分,如spl包含ddr初始化相关代码,而tpl包含镜像加载相关驱动,从而减少spl镜像的size。此时启动流程可被设计为如下方式:
bootrom --> spl(init ddr) --> bootrom --> tpl(load and run uboot)--> uboot
其示意图如下:
在此流程中,spl主要完成ddr初始化,由于其不带有镜像加载相关的驱动,因此执行完成后需要跳转回bootrom,由bootrom完成tpl的加载(类似atf中bl2加载完成后跳转回bl1),并由tpl完成最终uboot的加载。由于tpl的主体流程与spl几乎相同,且大多数系统并不需要tpl,故接下来我们的讨论将主要围绕spl和uboot这两个阶段。
- 若不需要支持tpl,则uboot的典型启动流程可精简为如下方式(这也是uboot最常见的运行方式):
- 对于有些启动速度要求较高的场景,可以进一步简化其启动流程。如可将其设计为下面这种跳过uboot,直接通过spl启动操作系统的方式,此时其启动流程如下:
Atf与U-boot组合方式启动
若系统需要支持secure和non secure两种执行状态,则必须要从secure空间开始启动,且启动完成后需要通过secure monitor(bl31)完成normal os对secure空间服务相关请求的处理。 这时atf将非常方便地帮助我们达成这一目的,这也是第一篇中我们已经介绍过的启动方式,以下我们重新贴一下其加载和启动流程图:
atf启动uboot的典型镜像跳转流程
在以上流程中bl32是可选的,若不支持trust os则可裁剪掉该流程。典型情况下bl33为uboot,而bl2既可以使用atf实现,也可以用spl代替
U-boot初始化
除了一些通过编译选项区分的部分,以及board_init_f和board_init_r函数的具体实现以外,uboot与spl的初始化流程完全相同。spl初始化流程在另一篇文章<spl启动分析>中已经做了较详细的介绍,<<SPL启动分析>>
故文本将主要介绍uboot特有部分的内容,其它代码只做简要分析。,我们还是先给出uboot的初始化流程图(为什么从start.S开始执行见链接脚本中的起始地址):
- save_boot_params保存上一级镜像传入的参数,该函数由平台自行定义
- 若支持pie则检查代码段是否为4k对齐(因为由于指令集中操作数长度的限制,adr等类型指令的寻址范围是需要4k对齐的)
- pie_fixup为pie重定位全局地址相关的.rela.dyn段内容
- reset_sctrl根据配置确定是否重设sctlr寄存器
- 为uboot设置异常向量表。spl和uboot异常向量表设置有以下不同:
a. spl在设置了配置选项CONFIG_ARMV8_SPL_EXCEPTION_VECTORS,则会为其设置异常向量表,否则不为其设置异常向量表 b. uboot默认情况就会设置异常向量表 armv8的异常向量表格式如下:
即根据中断触发时cpu正在运行的异常等级、使用的栈寄存器类型以及运行状态,armv8会跳转到不同的中断向量。由于spl和uboot在启动流程中不会执行比当前更低异常等级的代码,因此只需要实现当前异常等级下的8个异常向量即可。其对应的向量表定义在arch/arm/cpu/armv8/exceptions.S中。
由于根据不同的配置,spl或uboot可运行在el1 – el3异常等级下,因此需要根据当前实际的异常等级来选择异常向量表基地址寄存器。
- 若配置了COUNTER_FREQUENCY选项,则根据当前正在运行的异常等级,确定是否要设置cpu的system counter的频率。由于system counter的频率是所有异常等级共享的,为了确保该频率不被随意修改,因此约定只有运行于最高异常等级时才允许修改该寄存器
- 若设置了配置选项CONFIG_ARMV8_SET_SMPEN,则设置S3_1_c15_c2_1以使能cpu之间的数据一致性
- apply_core_errata用于处理cpu的errata
- lowlevel_init流程可参考spl启动分析
- secondary cpu处理流程将在2.1节中介绍
- _main的定义位于arch/arm/lib/crt0_64.S
从cpu处理流程
smp系统只有主cpu执行完整的启动流程,其它从cpu在启动初期需要被设置到一个特定的状态。,待主cpu将系统启动完成后,再唤醒从cpu从给定地址处执行。armv8的从cpu启动包含psci和spintable两种方式,其中psci方式需要由bl31处理,我们将在后面再专门介绍。此处我们看下uboot对spintable方式是如何处理的,以下是其源码:
#if defined(CONFIG_ARMV8_SPIN_TABLE) && !defined(CONFIG_SPL_BUILD) (1)
branch_if_master x0, x1, master_cpu (2)
b spin_table_secondary_jump (3)
#elif defined(CONFIG_ARMV8_MULTIENTRY) (4)
branch_if_master x0, x1, master_cpu (5)
slave_cpu:
wfe (6)
ldr x1, =CPU_RELEASE_ADDR (7)
ldr x0, [x1]
cbz x0, slave_cpu (8)
br x0 (9)
#endif
master_cpu:
bl _main
- 若当前从cpu为spin table启动方式,且当前执行的是uboot时。则从cpu将通过wfe进入自旋状态,并等待内核向给定地址填入其启动入口函数,该流程如下:
ENTRY(spin_table_secondary_jump)
.globl spin_table_reserve_begin
spin_table_reserve_begin:
0: wfe (a)
ldr x0, spin_table_cpu_release_addr (b)
cbz x0, 0b (c)
br x0 (d)
.globl spin_table_cpu_release_addr
.align 3
spin_table_cpu_release_addr: (e)
.quad 0
.globl spin_table_reserve_end
spin_table_reserve_end:
ENDPROC(spin_table_secondary_jump)
a 从cpu进入wfe睡眠模式 b 若该cpu被唤醒,则读取spin_table_cpu_release_addr的值 c 若内核未向该地址写入其启动的入口函数,则继续返回睡眠 d 否则,跳转到读取到的入口处开始从cpu的启动流程 e 定义保存从cpu入口函数的内存地址,该地址在uboot启动时会被填入设备树spintable节点的属性中。内核启动从cpu时,则通过向解析到地址写入入口函数,并唤醒secondary cpu,从而完成其启动
- 若当前cpu为主cpu,继续执行冷启动流程
- 若当前cpu为从cpu,则进入step 1的spin模式
- 若未配置spintable,则从cpu需要spin在一个系统预先定义的地址上,并等待uboot在合适的时机向该地址填入入口函数
- 若当前cpu为主cpu,则继续执行冷启动流程
- -9 该流程与spintable方式类似,也是cpu通过wfe进入睡眠模式,并在唤醒后查询给定地址的值是否已被填入。若被填入则跳转到入口函数开始执行,否则继续进入睡眠模式
_main流程分析
U-boot重定位前的GD及内存规划
在进入c语言之前,我们需要为其准备好运行环境,以及做好内存规划,这其中除了栈和堆内存之外,还需要为gd结构体分配内存空间。gd是uboot中的一个global_data类型全局变量,该变量包含了很多全局相关的参数,为各模块之间参数的传递和共享提供了方便。由于该变量在跳转到c流程之前就需要准备好,此时堆管理器尚未被初始化,所以其内存需要通过手工管理方式分配。以下为uboot内存规划相关代码:
#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
ldr x0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr x0, =(CONFIG_SPL_STACK)
#elif defined(CONFIG_INIT_SP_RELATIVE)
#if CONFIG_POSITION_INDEPENDENT
adrp x0, __bss_start
add x0, x0, #:lo12:__bss_start
#else
adr x0, __bss_start
#endif
add x0, x0, #CONFIG_SYS_INIT_SP_BSS_OFFSET
#else
ldr x0, =(CONFIG_SYS_INIT_SP_ADDR) (1)
#endif
bic sp, x0, #0xf (2)
mov x0, sp
bl board_init_f_alloc_reserve (3)
mov sp, x0 (4)
mov x18, x0 (5)
bl board_init_f_init_reserve (6)
- 以上部分根据不同的配置情况获取uboot的初始栈地址
- 为了遵循ABI规范,栈地址需要16字节对齐,该指令将地址做对齐以后设置到栈指针寄存器中,以为系统设置运行栈
- 该函数为gd和early malloc分配内存,其代码如下:
ulong board_init_f_alloc_reserve(ulong top)
{
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
top -= CONFIG_VAL(SYS_MALLOC_F_LEN); (a)
#endif
top = rounddown(top-sizeof(struct global_data), 16); (b)
return top;
}
a 为早期堆管理器预留内存 b 为gd预留内存
- 将预留后的内存地址设置为新的栈地址,此时各部分的地址如下:
- 将gd地址保存到x18寄存器中,其可用被于后续gd指针的获取
- 该流程主要用于初始化gd,和设置early malloc的堆管理器基地址,其代码如下:
void board_init_f_init_reserve(ulong base)
{
struct global_data *gd_ptr;
gd_ptr = (struct global_data *)base;
memset(gd_ptr, '\0', sizeof(*gd)); (a)
#if !defined(CONFIG_ARM)
arch_setup_gd(gd_ptr); (b)
#endif
if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
board_init_f_init_stack_protection_addr(base); (c)
base += roundup(sizeof(struct global_data), 16);
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
gd->malloc_base = base; (d)
#endif
if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
board_init_f_init_stack_protection(); (e)
}
a 获取gd指针,并清空gd结构体内存 b 该函数用于非arm架构的gd指针获取,armv8架构则通过前面设置的x18寄存器获取gd指针,其定义如下(arch/arm/include/asm/global_data.h):
#ifdef CONFIG_ARM64
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18")
#else
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
#endif
c 该函数用于获取该栈溢出检测的地址 d 设置early malloc的基地址 e 初始化栈溢出检测的canary值,该值被设置为SYS_STACK_F_CHECK_BYTE
相关文章
- springboot启动流程概述_简述app启动的主要流程
- 神器,轻松可视化Python程序调用流程
- 测试流程如何落地
- (七十)Android O Service启动流程梳理——bindService
- HashMap扩容流程[通俗易懂]
- SpringBoot启动流程–总结
- 面试官:说一下Spring MVC的启动流程呗
- 关于Spring体系的各种启动流程
- GDB查看xtrabackup备份流程
- PAD流程定时启动的三种方式
- 《上海悠悠接口自动化平台》-3.流程性用例,有关联的接口如何写?
- Linux 内核启动流程之 start_kernel
- Uboot 大全 | uboot 启动流程(二)
- Android 用户态启动流程分析
- Android12 应用启动流程分析
- Hbuilder用自有证书打包 ios App上架AppStore流程
- iOS开发之进阶篇(1)—— 证书、打包上架流程、p12文件
- 【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 一 | Activity 进程相关源码 )
- 【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 二 | AMS 进程相关源码 | 主进程相关源码 )
- 腾讯轻联流程运行错误如何排查问题?
- 在线的ios App Store文件上传流程
- Android Activity启动流程分析详解手机开发
- MongoDB: 配置启动流程指南(mongodb配置启动)
- 深入了解Linux系统中断处理流程(linux中断流程)
- Mysql启动过程:让你一次搞定(mysql启动流程)
- 使用 SQL Server建立索引,简化查询流程(sqlserver ix)
- MySQL下载须注册吗详解注册流程及其必要性(mysql下载要注册吗)
- mysql基础:mysqld_safe启动执行流程详解
- redis启动流程介绍