• U-Boot 启动流程详解


    链接脚本 u-boot.lds 详解

    要分析 uboot 的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。如果没有编译过 uboot 的话链接
    脚本为 arch/arm/cpu/u-boot.lds。但是这个不是最终使用的链接脚本,最终的链接脚本是在这个链接脚本的基础上生成的。编译一下 uboot,编译完成以后就会在 uboot 根目录下生成 u-boot.lds文件
    _start 后面就是中断向量表,从图中的“.section “.vectors”,
    "ax”可以得到,此代码存放在.vectors 段里面。
    使用如下命令在 uboot 中查找“__image_copy_start”:
    grep -nR "__image_copy_start"
    在这里插入图片描述
    打开 u-boot.map,找到所示位置
    在这里插入图片描述
    u-boot.map 是 uboot 的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址, 932 行可以看到_image_copy_start 为 0X87800000,而.text 的起始地址也是0X87800000。
    第 11 行是 vectors 段,vectors 段保存中断向量表,从图中我们知道了 vectors.S 的代码是存在 vectors 段中的。vectors 段的起始地址也是 0X87800000,说明整个 uboot 的起始地址就是 0X87800000,这也是为什么我们裸机例程的链接起始地址选择 0X87800000 了,目的就是为了和 uboot 一致。
    第 12 行将 arch/arm/cpu/armv7/start.s 编译出来的代码放到中断向量表后面。
    第 13 行为 text 段,其他的代码段就放到这里在 u-boot.lds 中有一些跟地址有关的“变量”需要我们注意一下,后面分析 u-boot 源码的时候会用到,这些变量要最终编译完成才能确定的!!!
    在这里插入图片描述在这里插入图片描述

    变量”值可以在 u-boot.map 文件中查找,表 32.1.1 中除了__image_copy_start以外,其他的变量值每次编译的时候可能会变化,如果修改了 uboot 代码、修改了 uboot 配置、选用不同的优化等级等等都会影响到这些值。所以,一切以实际值为准!

    Uboot启动流程

    1、reset函数
    bicne=bic + ne
    ①、reset函数目的是将处理器设置为SVC模式,并且关闭FIQ和IRQ.
    ②、设置中断向量。
    ③、初始化CP15
    ④、

    2、lowlevel_init函数
    CONFIG_SYS_INIT_SP_ADDR
    

    #define CONFIG_SYS_INIT_SP_ADDR
    (CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)

    #define CONFIG_SYS_INIT_SP_OFFSET
    (CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)

    #define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE
    #define IRAM_SIZE 0x00020000

    #define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
    #define IRAM_BASE_ADDR 0x00900000 6ULL内部OCRAM

    #define GENERATED_GBL_DATA_SIZE 256

    0x00900000 + CONFIG_SYS_INIT_SP_OFFSE =>
    0x00900000 + CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE =>
    0x00900000 + 0x00020000 – 256 = 0x0091ff00

    设置SP指针、R9寄存器

    3、s_init函数
    空函数。
    
    4、_main函数
    
    5、board_init_f函数
    initcall_run_lis此函数会调用一系列的函数,这些函数保存在init_sequence_f数组里面,
    

    version_string[] = U_BOOT_VERSION_STRING
    #define U_BOOT_VERSION_STRING U_BOOT_VERSION " (" U_BOOT_DATE " - "
    U_BOOT_TIME " " U_BOOT_TZ “)” CONFIG_IDENT_STRING

    U_BOOT_VERSION = U-Boot 2016.03

    TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
    CONFIG_SYS_MALLOC_LEN (16 * SZ_1M)
    CONFIG_ENV_SIZE SZ_8K

    mx6ullevk.c mx6ullevk.h这两个文件长打交道

    6、relocate_code函数
    relocate_code函数有一个参数,r0=gd->relocaddr=0X9FF47000,uboot重定位后的首地址。
    r1=0X87800000 源地址起始地址。
    r4=0X9FF47000-0X87800000=0X18747000 偏移。
    r2=0x8785dc6c
    
    当简单粗暴的将uboot从0X87800000拷贝到其他地方以后,关于函数调用、全局变量引用就会出问题。Uboot对于这个的处理方法就是采用位置无关码,这个就需要借助于.rel.dyn段。
    

    8785dcf8 :
    8785dcf8: 00000000 andeq r0, r0, r0

    878042b4 :
    878042b4: e59f300c ldr r3, [pc, #12] ; 878042c8
    878042b8: e3a02064 mov r2, #100 ; 0x64
    878042bc: e59f0008 ldr r0, [pc, #8] ; 878042cc
    878042c0: e5832000 str r2, [r3]
    878042c4: ea00d64c b 87839bfc
    878042c8: 8785dcf8 ; instruction: 0x8785dcf8
    878042cc: 87842aaf strhi r2, [r4, pc, lsr #21]

    设置r3为878042b4+8+12=878042c8的值,r3=8785dcf8。这里并没有直接去读取rel_a的地址,而是借助了878042c。

    878042c8叫做Label。
    

    重定位以后
    9ffa4cf8 :
    9ffa4cf8: 00000000 andeq r0, r0, r0

    9ff4b2b4:
    9ff4b2b4: e59f300c ldr r3, [pc, #12] ; 878042c8
    9ff4b2b8: e3a02064 mov r2, #100 ; 0x64
    9ff4b2bc: e59f0008 ldr r0, [pc, #8] ; 878042cc
    9ff4b2c0: e5832000 str r2, [r3]
    9ff4b2c4: ea00d64c b 87839bfc
    9ff4b2c8: 8785dcf8 ; instruction: 0x8785dcf8
    9ff4b2cc: 87842aaf strhi r2, [r4, pc, lsr #21]

    Label中的值还是原来的!必须要将8785dcf8换为重定位后的rel_a地址。读取9ff4b2c8里面的数据,也就是老的rel_a的地址=8785dcf8+0x18747000=0x9ffa4cf8

    重定位以后,需要对所有的Label保存的数据加上偏移!!

    8785dcec: 87800020 strhi r0, [r0, r0, lsr #32]
    8785dcf0: 00000017 andeq r0, r0, r7, lsl r0
    ……
    8785e2fc: 878042c8 strhi r4, [r0, r8, asr #5]
    8785e300: 00000017 andeq r0, r0, r7, lsl r0

    878042c8+offset = 读取新的Label处的数据+offset

    完成这个功能在连接的时候需要加上”-pie”
    
    7、relocate_vectors函数
    设置VBAR寄存器为重定位后的中断向量表起始地址。
    
    8、board_init_r函数
    Board_init_r函数和board_init_f函数很类似。board_init_r也是执行init_sequence_r初始化序列。
    
    9、run_main_loop函数
    run_main_loop
    	-> main_loop
    		-> bootdelay_process 获取bootdelay的值,然后保存到stored_bootdelay
    

    全局变量里面,获取bootcmd环境变量值,并且将其
    返回
    -> autoboot_command 参数是bootcmd的值。
    -> abortboot 参数为boot delay,此函数会处理倒计时
    -> abortboot_normal 参数为boot delay,此函数会处理倒计时
    -> cli_loop uboot命令模式处理函数。
    -> parse_file_outer
    -> parse_stream_outer
    -> parse_stream 解析输入的字符,得到命令
    -> run_list 运行命令
    -> run_list_real
    -> run_pipe_real
    -> cmd_process 处理命令,也就是执行命令

    10、cli_loop函数

    11、cmd_process函数
    Uboot使用U_BOOT_CMD来定义一个命令。CONFIG_CMD_XXX来使能uboot中的某个命令。
    U_BOOT_CMD最终是定义了一个cmd_tbl_t类型的变量,所有的命令最终都是存放在.u_boot_list段里面。cmd_tbl_t的cmd成员变量就是具体的命令执行函数,命令执行函数都是do_xxx。

    cmd_process
    	->find_cmd  从.u_boot_list段里面查找命令,当找到对应的命令以后以返回值的
    

    形式给出,为cmd_tbl_t类型
    ->cmd_call
    ->cmdtp->cmd 直接引用cmd成员变量

    三、bootz启动Linux内核过程
    Uboot启动Linux内核使用bootz命令,bootm。。bootz是如何启动Linux内核?uboot的生命是怎么终止的呢?linux又是怎么启动的呢?

    1、image全局变量
    

    bootm_headers_t images;

    2、do_bootz函数
    

    tftp 80800000 zImage
    tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb

    bootz 80800000 - 83000000

    bootz命令的执行函数,do_xxxx,do_bootz是bootz的执行函数。
    

    do_bootz
    -> bootz_start
    -> do_bootm_states 阶段为BOOTM_STATE_START
    -> bootm_start 对images全局变量清零,
    -> images->ep = 0X80800000
    ->bootz_setup 判断zImage是否正确
    -> bootm_find_images
    · -> boot_get_fdt 找到设备树,然后将设备树起始地址和长度,写入到images
    的ft_addr和ft_len成员变量中。

    -> bootm_disable_interrupts		关闭中断相关
    -> images.os.os = IH_OS_LINUX;   表示要启动Linux系统
    -> do_bootm_states  状态BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 、
    

    BOOTM_STATE_OS_GO,
    -> bootm_os_get_boot_func 查找Linux内核启动函数。找到Linux内核启动函数
    do_bootm_linux,赋值给boot_fn。
    -> boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); 就是do_bootm_linux。
    -> boot_prep_linux 启动之前的一些工作,对于使用设备树来说,他会将
    Bootargs传递给Linux内核,通过设备树完成。也就是向
    Linux内核传参。
    -> boot_selected_os BOOTM_STATE_OS_GO, do_bootm_linux
    -> do_bootm_linux,BOOTM_STATE_OS_GO
    -> boot_jump_linux
    -> machid= gd->bd->bi_arch_number;
    -> void (kernel_entry)(int zero, int arch, uint params);
    -> kernel_entry = (void (
    )(int, int, uint))images->ep; 0X80800000。
    -> announce_and_cleanup 输出Starting kernel……
    -> kernel_entry(0, machid, r2); 启动Linux内核。Uboot的最终使命,启动Linux内核。

    zimage_header 的zi_magic为zimage的幻数,魔术数。应该为0x016f2818。前面有9个32位的数据,那么9*4=36,0~35,第36个字节的数据开始就是zimage的幻数。

    3、do_bootm_states函数

    4、bootm_os_get_boot_func函数

    5、do_bootm_linux函数

  • 相关阅读:
    操作系统(三)| 进程管理下 经典进程问题分析 线程 死锁
    pango logos 双启动
    day19正则表达式
    python的集合应用
    软件测试的学习笔记(2)
    Vue中如何进行表单验证码与滑动验证
    电子制造行业的数字化转型突破点在哪?精益制造是关键
    MySQL数据库基本操作和完整性约束类型详解
    4 线程创建新增方式(JKD 5.0)
    elementPlus表格组件el-table实现只能同时选择一行,全选按第一行处理
  • 原文地址:https://blog.csdn.net/qq_61540355/article/details/126927093