【Uboot】Uboot启动流程分析

U-Boot(Universal Boot Loader)是一个广泛使用的开源引导加载程序,常用于嵌入式系统中。它负责在设备上电或复位后初始化硬件,并引导操作系统或应用程序的启动。U-Boot的启动流程是一个逐步的过程,包含了硬件初始化、环境变量配置、加载操作系统映像(如Linux内核),以及启动内核的多个步骤。

U-Boot 启动流程

U-Boot的启动过程可以分为以下几个主要步骤:

1. 硬件初始化

启动引导加载程序(ROM Bootloader):当嵌入式系统上电时,CPU首先会从一个预定义的存储位置(通常是片上ROM)加载初始的启动代码。这些启动代码通常非常简短,作用是加载uboot到内存中、初始化内存控制器,配置时钟等。

2.uboot初始化

和STM32一样,uboot也有链接脚本文件和启动文件,链接脚本为 arch/arm/cpu/u-boot.lds,部分内容如下:

1 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")#指定输出可执行文件是 32 位 ARM 指令、小端模式的 ELF 格式。
2 OUTPUT_ARCH(arm)  #指定输出可执行文件的平台为 ARM
3 ENTRY(_start)     #代码入口点:_start,_start 在文件 arch/arm/lib/vectors.S 中有定义
4 SECTIONS
5 {
6 . = 0x00000000;   #指明目标代码的起始地址从 0x0 位置开始,"."代表当前位置。
7 . = ALIGN(4);     #表示此处 4 字节对齐
8 .text :
9 {
10 *(.__image_copy_start)
11 *(.vectors)      #vectors 段保存中断向量表。从u-boot.map(uboot 的映射文件,可以从该文件看到某个文件或者函数链接到了哪个地址)可以看出,vectors 段的起始地址也是 0x4000000,说明整个uboot的起始地址就是 0x4000000。

根据入口_start找到文件 arch/arm/lib/vectors.S,部分内容如下

22 #ifdef CONFIG_ARCH_K3
23 ldr pc, _reset
24 #else
25 b reset

使用 grep -Nr "CONFIG_ARCH_K3"命令查找 CONFIG_ARCH_K3 变量在 uboot 中没有定义,所以当 uboot 启动后程序会跳转到第 25 行的复位异常向量的指令"b reset"处,紧接着执行 reset 代码段。标号 reset 在 arch/arm/cpu/armv7/start.S 中定义

38 reset:
39  	/* Allow the board to save important registers */
40	    b	save_boot_params

从标号 reset 跳转到了 save_boot_params 函数,而 save_boot_params 函数同样定义在 start.S 中

119 ENTRY(save_boot_params)
120	        b	save_boot_params_ret		@ back to my caller

save_boot_params 函数也是只有一句跳转语句,跳转到该文件中的 save_boot_params_ret 函数

75 #ifdef CONFIG_HAS_VBAR
76 /* Set vector address in CP15 VBAR register */
77 ldr r0, =_start
78 mcr p15, 0, r0, c12, c0, 0 @Set VBAR
79 #endif
82 /* the mask ROM code should have PLL and others stable */
83 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
84 #ifdef CONFIG_CPU_V7A
85 bl cpu_init_cp15
86 #endif
87 #ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
88 bl cpu_init_crit
89 #endif
90 #endif
91 
92 bl _main

这部分和STM32中的复位中断的处理函数作用类似

第 78 行设置 r0 寄存器的值为_start,_start 就是整个 uboot 的入口地址,其值为 0x4000000

第81行函数 cpu_init_cp15 用来设置 CP15(ARM 体系结构中的一个协处理器(Coprocessor),它用于控制和配置系统的一些低级操作和特性相关的内容),比如关闭 MMU

第88行函数 cpu_init_crit 也在是定义在 start.S 文件中,一般是为了允许执行函数 board_init_f 所做的基本初始化

第92行要执行的是 save_boot_params_ret 中的_main 函数,接下来分析_main函数

_main 函数定义在文件 arch/arm/lib/crt0.S 中,主要需要知道uboot 中定义了一个指向 gd_t 的指针 gd(保存了uboot的相关配置信息),gd 存放在寄存器 r9 里面的

在 _main 函 数 里 面 主 要 调 用 了 board_init_f 、 relocate_code、relocate_vectors 和 board_init_r 这 4 个函数,接下来依次来看下这 4 个函数的作 用。

board_init_f 函数定义在文件 common/board_f.c 中,此函数主要有两个工作:

1、初始化一系列外设,比如串口、定时器,或者打印一些消息等。

2、初始化 gd 的各个成员变量。

relocate_code 函数是用于代码拷贝的,此函数定义在文件 arch/arm/lib/relocate.S 中。

函数 relocate_vectors用于重定位向量表,此函数定义在文件 arch/arm/lib/relocate.S中,比如r0=gd->relocaddr,也就是重定位后 uboot 的首地址 ,向量表从这个地址开始存放。

board_init_f 函数,在此函数里面会调用一系列的函数来初始化一些外设和 gd的成员变量。但是 board_init_f并没有初始化所有的外设,还需要做一些后续工作,这 些后续工作就是由函数 board_init_r 来完成的,board_init_r 函数定义在文件 common/board_r.c 中,最后进入main_loop();main_loop 函数定义在文件 common/main.c 里面

3.uboot是否启动Linux

main_loop 函数代码如下:

41 void main_loop(void)
42 {
43 const char *s;
45 bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); #打印出启动进度
47 if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
48     env_set("ver", version_string); /* set version variable */
50 cli_init();
52 if (IS_ENABLED(CONFIG_USE_PREBOOT))
53     run_preboot_environment_command();
55 if (IS_ENABLED(CONFIG_UPDATE_TFTP))
56     update_tftp(0UL, NULL, NULL);
58 s = bootdelay_process();
59 if (cli_process_fdt(&s))  #函数cli_process_fdt读取设备树中的 bootcmd
60     cli_secure_boot_cmd(s);
62 autoboot_command(s);  #autoboot_command 函数,此函数就是检查倒计时是否结束,结束就启动Linux打断则启动uboot
64 cli_loop();   #uboot 的命令行处理函数
65 panic("No CLI available");
66 }

autoboot_command 函数定义在文件 common/autoboot.c 中。
cli_loop 函数是 uboot 的命令行处理函数,我们在 uboot 中输入各种命令,进行 各种操作就是由cli_loop 来处理的,此函数定义在文件 common/cli.c 中。

4.uboot启动Linux

假设倒计时自动结束,接下来就是uboot启动Linux,目的是使处理器开始处理Linux代码部分,接着分析uboot如何启动Linux的
上述的autoboot_command 函数会调用do_boot()。这个函数负责启动内核或操作系统镜像,
我们这里是do_bootz,在文件 cmd/bootz.c 中

int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
62 {
63 int ret;
65 /* Consume 'bootz' */
66 argc--; argv++;
68 if (bootz_start(cmdtp, flag, argc, argv, &images))
69 return 1;
75 bootm_disable_interrupts();
77 images.os.os = IH_OS_LINUX;
78 ret = do_bootm_states(cmdtp, flag, argc, argv,
79 #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
80 BOOTM_STATE_RAMDISK |
81 #endif
82 BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
83 BOOTM_STATE_OS_GO,
84 &images, 1);
86 return ret;
87 }

注:images是Linux相关配置信息的一个结构体和上述uboot的gd类似

可以看到do_bootz函数调用了三个函数,逐一分析,

第 68 行,调用 bootz_start 函数,该函数用于支持 zImage 镜像的启动,定义在文件 cmd/bootz.c 中,设置 images 的 ep 成员变量,也就是系统镜像的入口点,使用 bootz 命令启动系 统的时候就会设置系统在 DRAM 中的存储位置,调用 bootz_setup 函数,此函数会判断当前的系统镜像文件是否为 Linux 的镜像 文件,并且会打印出镜像相关信息bootz_start 主要用于初始化 images 的相关成员变量。

第 75 行,调用函数 bootm_disable_interrupts 关闭中断。

第78行函数 do_bootm_states会 根据不同的 BOOT 状态执行不同的代码段

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],int states, bootm_headers_t *images, int boot_progress){
616 if (states & BOOTM_STATE_START)
617     ret = bootm_start(cmdtp, flag, argc, argv);
......
683 if (!ret && (states & BOOTM_STATE_OS_PREP)) {
688     ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
689 }
690
691 #ifdef CONFIG_TRACE
692 /* Pretend to run the OS, then run a user command */
693 if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
694     char *cmd_list = env_get("fakegocmd");
695
696 ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,images, boot_fn);
698 if (!ret && cmd_list)
699     ret = run_command_list(cmd_list, -1, flag);
700 }
701 #endif

709 /* Now run the OS! We hope this doesn't return */
710 if (!ret && (states & BOOTM_STATE_OS_GO))
711     ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn);
......
724     return ret;
725 }

do_bootm_states 是一个状态机函数,它通过循环处理不同的启动状态。每个状态执行对应的操作,并在完成后转移到下一个状态。四个状态如下:

**BOOTM_STATE_START:**这是启动流程的开始。此时会进行一些初始化工作。然后将状态更新为 BOOTM_STATE_OS_PREP,准备操作系统。

**BOOTM_STATE_OS_PREP:**该状态负责准备操作系统的相关资源,例如加载内核映像。如果没有有效的内核映像,处理函数将会失败并退出。

**BOOTM_STATE_OS_FAKE_GO:**这个阶段是一个模拟启动阶段,通常用于调试或仿真。它将状态更新为 BOOTM_STATE_OS_GO,以继续进行实际的操作系统启动。我们这里没有定义故不需要。

**BOOTM_STATE_OS_GO:**这是实际的操作系统启动阶段,U-Boot 将会跳转到操作系统的入口点,启动内核。如果内核映像不可用或启动失败,函数会返回错误代码。

可以看到代码中最后一个执行函数为boot_selected_os,函数定义在文件common/bootm_os.c 中,该函数内调用了 do_bootm_linux函数,此函数 定义在文件 arch/arm/lib/bootm.c

418 int do_bootm_linux(int flag, int argc, char * const argv[],
419 bootm_headers_t *images)
420 {
422     if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
423         return -1;
424
425     if (flag & BOOTM_STATE_OS_PREP) {
426         boot_prep_linux(images);
427     return 0;
428 }
429
430     if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
431         boot_jump_linux(images, flag);
432     return 0;
433 }
434
435     boot_prep_linux(images);
436     boot_jump_linux(images, flag);
437     return 0;
438 }

由于boot_selected_os 函数在调用 do_bootm_linux 的时候会将flag设置为 BOOTM_STATE_OS_GO。执行 boot_jump_linux 函数,该函数最后调用函数 kernel_entry,看名字"内核_进入",说明此函数是进入 Linux 内核的 。此函数有三个参数:zero,arch,params,

第一个参数 zero 为 0;

第二个参数为机器 ID;

第三个参数 ATAGS 或者设备树(DTB)首地址;

5.简要流程图

相关推荐
van叶~12 分钟前
Linux探秘坊-------4.进度条小程序
linux·运维·小程序
秋风&萧瑟13 分钟前
【数据结构】顺序队列与链式队列
linux·数据结构·windows
我科绝伦(Huanhuan Zhou)20 分钟前
Linux 系统服务开机自启动指导手册
java·linux·服务器
hunter2062062 小时前
ubuntu终端当一段时间内没有程序运行时,自动关闭终端。
linux·chrome·ubuntu
代码讲故事4 小时前
从Windows通过XRDP远程访问和控制银河麒麟ukey v10服务器,以及多次连接后黑屏的问题
linux·运维·服务器·windows·远程连接·远程桌面·xrdp
qq_243050796 小时前
irpas:互联网路由协议攻击套件!全参数详细教程!Kali Linux入门教程!黑客渗透测试!
linux·网络·web安全·网络安全·黑客·渗透测试·系统安全
IT北辰7 小时前
Linux下 date时间应该与系统的 RTC(硬件时钟)同步
linux·运维·实时音视频
Jason Yan7 小时前
【经验分享】ARM Linux-RT内核实时系统性能评估工具
linux·arm开发·经验分享
肖田变强不变秃7 小时前
C++实现矩阵Matrix类 实现基本运算
开发语言·c++·matlab·矩阵·有限元·ansys
步、步、为营7 小时前
.net无运行时发布原理
linux·服务器·.net