1. uboot介绍:

   uboot是bootloader的一种,是Linux内核的引导启动程序。会初始化嵌入式平台上的一些外设(比如:ddr等),把Linux内核镜像从flash中加载到内存,在完成一些初始化工作后,最后启动Linux内核,类似于windows的BIOS程序。uboot相当于是一段功能较多的裸机程序。

   下面将是对uboot启动流程的源码分析,此处使用的嵌入式平台芯片是NXP的 i.mx6ull 芯片(Cortex-A7内核,arm v7架构),uboot源码是NXP官方提供的4.1.15版本uboot。
下载地址:
http://git.freescale.com/git/cgit.cgi/imx/uboot-imx.git/tag/?h=imx_v2016.03_4.1.15_2.0.0_ga&id=rel_imx_4.1.15_2.1.0_ga

2. uboot启动流程(源码分析)

其中_start是uboot的入口函数

Uboot启动流程:
   _start  <arch/arm/lib/vectors.S>
       |--->reset  <arch/arm/cpu/armv7/start.S>
       |       |--->save_boot_params   <arch/arm/cpu/armv7/start.S>
       |                      |--->save_boot_params_ret    <arch/arm/cpu/armv7/start.S>
       |                                   |               // 禁止中断,设置cpu的模式为SVC
       |                                   |               // 清楚SCTLR的bit13,允许向量重定位,同时
                                           |               // 重定位向量表,把CP15的c12(VBAR)寄存器设置为
                                           |               // 0x87800000(uboot的起始地址,也是向量表的起始地址)
                                           |
                                           |--->cpu_init_cp15      <arch/arm/cpu/armv7/start.S>
                                           |                       // 设置cp15相关内容,比如关闭mmu,cache
                                           |
                                           |--->cpu_init_crit      <arch/arm/cpu/armv7/start.S>
                                           |           |
                                           |           |--->lowlevel_init  <arch/arm/cpu/armv7/lowlevel_init.S>
                                           |           |                   // 设置栈指针sp = 0x0091FF00,属于MX6ULL
                                           |           |                   // 的内部ram,同时(sp - GD_SIZE(248))-->sp
                                           |           |                   // 留出global_data数据结构的位置sp = 0x0091FE08
                                           |           |                   // 设置sp-->r9, sp==r9
                                           |           |
                                           |           |--->s_init     <arch/arm/cpu/armv7/mx6/soc.c>
                                           |           |               // 空函数,直接返回
                                           |
                                           |--->_main      <arch/arm/lib/crt0.S>
                                                   |       // 设置sp为0x0091ff00,调用函数
                                                   |       // board_init_f_alloc_reserve(arg:0x0091FF00)后,把sp设为
                                                   |       // 此函数的返回值:0x0091FA00, r9(gd)设为0x0091FA00;
                                                   |       // 调用board_init_f_init_reserve(arg:0x0091FA00)后
                                                   |       // 把gb的成员malloc_base设为0x0091FB00(early_malloc的起始地址)
                                                   |       // 调用board_init_f函数:会初始化gd,返回之后重新设置环境(sp和gd)
                                                   |       // 把gd的成员start_addr(0x9EF44E90)赋值给sp, 此时sp == 0x9EF44E90
                                                   |       // 是外部DDR的地址,gd->bd赋给r9(gd),新的gd结构在bd结构下面,
                                                   |       // 重新设置gd = r9 - sizeof(*gd); lr = here. gd指向新的区域(DDR内)时
                                                   |       // lr = here + 68,这是为什么?uboot的拷贝目的地址:0x9FF47000
                                                   |--->board_init_f_alloc_reserve(arg:0x0091FF)    <common/init/board_init.c>
                                                   |                       // 在包含此函数的文件中有:DECLARE_GLOBAL_DATA_PTR;
                                                   |                       // 是个宏定义:#define  DECLARE_GLOBAL_DATA_PTR \
                                                   |                       //                 register volatile gd_t *gd asm("r9")
                                                   |                       // 此函数设置留出早期malloc和global_data内存区域,
                                                   |                       // 返回值:0x0091FA00
                                                   |
                                                   |--->board_init_f_init_reserve(arg:0x0091FA00)     <common/init/board_init.c>
                                                   |                       // 此函数用于初始化gd所指向的结构(清零处理)
                                                   |                       // 设置gd的成员malloc_base为0x91FB00
                                                   |                       // 就是early_malloc的起始地
                                                   |
                                                   |--->board_init_f       <common/board_f.c>
                                                   |                       // 主要做两个工作:初始化一系列外设(串口、定时器等)
                                                   |                       // 初始化gd的各个成员变量(此时gd还保存在内部ocram中)。上面的工作都是通过在函数内运行
                                                   |                       // initcall_sequence_f函数表中的一些函数来实现的,此函数表与
                                                   |                       // board_init_f函数定义在相同的文件,是static属性的静态表
                                                   |                       // 表中的函数执行完后会把gd->mon_len设为0xA8E74(__bss_end-_start),
                                                   |                       // 也就是代码长度。gd->malloc_init设为0x400(malloc内存池的大小)
                                                   |                       // gd->ram_size:0x20000000  gd->ram_top:0xA0000000  gd->relocaddr:0x9FF47000
                                                   |                       // gd->arch.tlb_size:0x4000  gd->arch.tlb_addr:0x9FFF0000
                                                   |
                                                   |--->relocate_code(arg:0x9FF47000)      <arch/arm/lib/relocate.S>
                                                   |                       // 代码拷贝。0x9FF47000是uboot拷贝目标首地址,offset=0x9FF47000-0x8780000,offset:0x18747000
                                                   |                       // 拷贝源地址:__image_copy_start=0x87800000,结束地址:__image_copy_end =0x8785dd54
                                                   |                       // 裸机程序运行需要链接地址与运行地址相同,uboot解决拷贝后的重定位问题是采用ld链接器
                                                   |                       // 链接时使用选项'-pie'生成位置无关的可执行文件,使用此选项时会生成一个.rel.dyn段,
                                                   |                       // uboot就是靠这个.rel.dyn来解决重定位问题的(.rel.dyn 段是存放.text 段中需要重定位地址的集合)
                                                   |                       // 修改.rel.dyn中的label来重定位
                                                   |
                                                   |--->relocate_vectors       <arch/arm/lib/relocate.S>
                                                   |                       // 重定位向量表,将CP15的VBAR寄存器的值设为0x9FF47000,uboot拷贝后的目标首地址
                                                   |
                                                   |--->c_runtime_cpu_setup      <arch/arm/cpu/armv7/start.S>
                                                   |
                                                   |--->board_init_r       <common/board_r.c>
                                                   |           |           // 初始化一些在board_init_f函数中未初始化的一些外设,做些后续工作。
                                                   |           |           // 是通过运行init_sequence_r函数集合中的函数来实现的,init_sequence_r与board_init_f函数
                                                   |           |           // 在同一个文件。在函数集合中initr_reloc_global_data函数初始化重定位后的gd的一些成员变量
                                                   |           |           // 集合中的其他函数:初始化了malloc、串口、电源芯片、emmc、环境变量、LCD、初始化跳转表、中断,使能中断
                                                   |           |           // 初始化网络地址(获取MAC地址,通过读取环境变量ethaddr的值,环境变量保存在emmc中)、
                                                   |           |           // 初始化网络设备,最后执行run_main_loop函数,主循环(处理命令)
                                                   |           |
                                                   |           |--->run_main_loop      <common/board_r.c>
                                                                       |            // uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下按下回车键,那么就
                                                                       |            // 会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动Linux内
                                                                       |            // 核,这个功能就是由run_main_loop函数来完成的.
                                                                       |
                                                                       |--->main_loop(void)    <common/main.c>
                                                                       |           |        // 如果如果倒计时结束之前按下按键,那么就会执行cli_loop函数,这个就是
                                                                       |           |        // 命令处理函数,负责接收好处理输入的命令。
                                                                       |           |
                                                                       |           |--->bootstage_mark_name
                                                                       |           |                   // 打印处启动进度
                                                                                   |
                                                                                   |--->autoboot_command
                                                                                   |                   // 此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?
                                                                                   |
                                                                                   |--->cli_loop       <common/cli.c>
                                                                                   |       |     // cli_loop函数是uboot的命令行处理函数,我们在uboot中输入
                                                                                   |       |     // 各种命令,进行各种操作就是由cli_loop来处理的
                                                                                   |       |
                                                                                   |       |--->parse_file_outer
                                                                                                       |
                                                                                                       |--->setup_file_in_str
                                                                                                       |
                                                                                                       |--->parse_stream_outer
                                                                                                       |                   // 这个函数就是 hush shell 的命令解释器,
                                                                                                       |                   // 负责接收命令行输入,然后解析并执行相应的命令

3. 总结

  在经过上述流程后 uboot 就进入了命令行处理模式,等待用户输入命令,此时uboot的启动流程就完成了,接下来就是加载Linux内核镜像,进行Linux内核启动的工作了。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐