目录
一、嵌入式系统启动总体流程
1.1 硬件组成
1.2 内存空间
1.3 代码块结构
1.4 嵌入式系统的启动模块与启动流程 -- 有SPL的情形
1.5 嵌入式系统的启动模块与启动流程 - 无SPL的情形
二、关键步骤1:自拷贝relocate概述
2.1 什么是自拷贝?
2.2 为什么自拷贝?
2.3 relocate() 函数
2.4 代码段自拷贝的源代码
三、关键步骤2:什么是重定位
3.1 程序代码寻址的两种方法:相对寻址与绝对寻址
3.2 地址重定位的方法
3.3 uboot拷贝后的地址重定向:所有的链接地址+新的偏移量
四、跳转到新的uboot地址继续执行
五、异常向量表定义
一、嵌入式系统启动总体流程
1.1 硬件组成
(1)SOC芯片
- CPU
- 片内ROM:启动代码
- 片内SRAM:大小有限的片内RAM, 通常只有xK大小,无法存放整个uboot。
- 片内FLASH控制器
(2)片外FLASH
- SPL:初始启动代码,完成SOC芯片和Flash控制器的初始化
- uboot:用于SOC芯片初始化和操作系统的加载
(3)片外DDR
- uboot
- 操作系统
- 文件系统
- 应用程序
SPL(Secondary Program Loader)和 U-Boot(Universal Boot Loader)是在嵌入式系统中用于引导启动的两个关键组件,它们之间有一些区别:
功能:
- SPL: SPL 是一个辅助程序加载器,主要负责在系统上电后的早期阶段执行。它的主要功能包括硬件初始化、加载引导程序uboot、启动内核以及错误处理等。
- U-Boot: U-Boot 是一个功能更为完整的引导加载程序,它在系统启动过程的后期阶段执行。除了加载内核之外,U-Boot 还提供了更多的功能,如命令行界面、环境变量配置、网络引导等。
复杂性:
- SPL: SPL 通常比较轻量级和简单,因为它主要负责早期的硬件初始化和引导加载,不涉及过多的系统管理功能。
- U-Boot: U-Boot 则更为复杂,因为它提供了丰富的功能和配置选项,包括文件系统支持、网络协议支持、驱动程序加载等,同时还有一个交互式命令行界面。
启动过程:
- SPL: 在系统上电后的最早阶段执行,主要负责启动引导加载程序。
- U-Boot: 在引导加载程序的后期阶段执行,可以与用户交互,并在加载内核之前执行一系列初始化和配置操作。
依赖关系:
- SPL: 通常在系统上电后直接执行,不依赖于其他引导加载程序。
- U-Boot: 通常依赖于 SPL 或其他辅助程序加载器,在 SPL 加载后才执行。
总的来说,SPL 是 U-Boot 的一个组成部分,负责早期的系统初始化和引导加载,而 U-Boot 则是一个更为完整和功能丰富的引导加载程序,提供了更多的系统管理功能和配置选项。
1.2 内存空间
1.3 代码块结构
1.4 嵌入式系统的启动模块与启动流程 -- 有SPL的情形
- BL1: SPL
- BL2: uboot
- 系统上电后,CPU跳转到复位地址,执行片内的bootrom,片内的boot rom完成CPU的和Flash控制器的初始化,从外部的SPI flash或NAND FLASH读取SPL代码
- SPL完成部分初始化,并把自己拷贝到片内的SRAM中,
- SPL在SRAM中完成CPU和DDR RAM的初始化。
- SPL把uboot拷贝到外部的DDR RAM的0x0000000地址处。
- 在外部DDR RAM中执行uboot。
- uboot完成SOC系统的初始化,并把自己从RAM低端地址拷贝到RAM的高端地址,把低端地址空间留给操作系统 (可选)
- uboot完成地址的重定向,并且在RAM的高端继续执行代码
- uboot把OS从Flash中加载到RAM的低地址空间段
- uboot把执行控制交给OS(直接跳转), 在RAM的低地址空间段执行OS
1.5 嵌入式系统的启动模块与启动流程 - 无SPL的情形
- 系统上电后,CPU跳转到复位地址,执行片内的bootrom,片内的boot rom完成CPU的和Flash控制器的初始化。
- 片内的bootrom读取Flash空间的DDR配置信息,并完成DDR RAM的初始化
- XXX
- bootrom把uboot拷贝到外部的DDR RAM的0x0000000地址处。
- 在外部DDR RAM中执行uboot。
- uboot完成SOC系统的初始化,并把自己从RAM低端地址拷贝到RAM的高端地址,把低端地址空间留给操作系统 (可选)
- uboot完成地址的重定向,并且在RAM的高端继续执行代码
- uboot把OS从Flash中加载到RAM的低地址空间段.
- uboot把执行控制交给OS(直接跳转), 在RAM的低地址空间段执行OS
二、关键步骤1:自拷贝relocate概述
2.1 什么是自拷贝?
在 U-Boot 中,自拷贝指的是将自身的代码从一个内存区域拷贝到另一个内存区域的操作。
这通常发生在启动过程中,其中的代码需要在不同的内存地址空间中执行,或者需要提高执行效率和可维护性。
2.2 为什么自拷贝?
将 ROM 中的代码拷贝到 RAM 中的主要原因包括:
-
执行速度: RAM 通常比 ROM 速度更快,因此将代码从 ROM 拷贝到 RAM 中可以提高代码的执行速度。在启动过程中,执行速度的提升可以缩短启动时间,提升系统的响应速度。
-
可写性: ROM 通常是只读的,而 RAM 是可读写的。将代码从 ROM 拷贝到 RAM 中可以使得代码变得可写,从而在运行时可以动态修改或更新代码,例如修复 bug 或者添加新功能,提高系统的可维护性和灵活性。
-
位置无关性: 一些代码可能需要在位置无关的环境下执行,而 ROM 中的代码通常是在固定地址上执行的。将代码拷贝到 RAM 中可以使得代码在任意地址上正确地执行,从而增强了代码的可移植性和灵活性。
-
内存映射或重定位: 在启动过程中,可能需要对内存进行映射或重定位,以适应不同的硬件配置或启动流程。将代码从 ROM 拷贝到 RAM 中可以确保代码可以正确地在新的内存地址空间中执行。
-
为OS预留空间:"uboot自拷贝"通常指的是 U-Boot 在启动时将自身代码从存储介质(如闪存、SD 卡等)拷贝到系统内存中执行。这个过程通常是为了在系统启动后,为操作系统(OS)留出足够的空间。
在嵌入式系统中,U-Boot 通常被加载到系统的非易失性存储器中,例如闪存。但由于闪存等存储介质的读写速度相对较慢,为了提高系统启动速度,U-Boot 会首先将自身代码拷贝到系统内存中执行。这样,一旦系统启动完毕,U-Boot 就可以释放存储介质上的空间,留给操作系统使用。
因此,"uboot自拷贝"的过程既是为了提高启动速度,又是为了给操作系统预留足够的空间,以便后续操作系统的正常运行。
-
硬件限制: 一些硬件平台可能要求代码必须存储在 RAM 中才能正确执行,因此需要将 ROM 中的代码拷贝到 RAM 中才能启动系统。
总的来说,将 ROM 中的代码拷贝到 RAM 中可以提高代码的执行速度、可写性和灵活性,同时确保代码可以在不同的硬件配置或启动流程中正确执行。
2.3 relocate()
函数
在 U-Boot 中,relocate()
函数是用于执行地址重定位操作的关键函数之一。这个函数通常被调用在 U-Boot 启动过程的早期阶段,它的主要作用是:将 U-Boot 代码从加载地址(链接地址)重定位到实际的内存地址上,以便程序能够正确执行。
relocate()
函数的具体实现可能会因不同的 U-Boot 版本或针对特定的硬件平台而有所不同,但其主要步骤通常包括以下内容:
-
计算重定位偏移量:
在开始执行重定位之前,需要计算出实际内存地址与加载地址之间的偏移量。这个偏移量通常是硬件相关的,因为不同的硬件平台可能有不同的内存映射方式。 -
更新代码中的地址:
遍历 U-Boot 代码中所有涉及地址的地方,将这些地址加上重定位偏移量,以将其映射到实际的内存地址上。 -
更新全局数据指针:
由于 U-Boot 可能包含一些全局数据结构,这些结构中包含了指向代码段、数据段等的指针。在重定位过程中,这些指针也需要相应地更新,以确保指向正确的内存地址。 -
处理可能的自修改代码:
如果 U-Boot 中存在一些自修改代码(self-modifying code),即在运行时会修改自身指令的代码,那么在重定位过程中需要特别处理,确保这些代码修改后的指令仍然能够正确执行。 -
执行重定位:
最后,执行实际的重定位操作,将 U-Boot 代码段和数据段移动到正确的内存地址上,并更新相关的指针和数据结构。
relocate()
函数的实现通常是高度依赖于具体的硬件平台和 U-Boot 的版本的。在阅读或修改 relocate()
函数时,需要特别注意硬件的内存映射方式以及 U-Boot 的代码结构,以确保正确理解和处理地址重定位过程中的各种细节和特殊情况。
2.4 代码段自拷贝的源代码
三、关键步骤2:什么是重定位
3.1 程序代码寻址的两种方法:相对寻址与绝对寻址
程序代码寻址的两种方法是相对寻址和绝对寻址:
-
相对寻址: 相对寻址是指根据当前指令的位置来计算目标地址的方法。在相对寻址中,指令中包含了一个偏移量或者相对地址,该偏移量表示了指令执行时目标地址相对于当前指令位置的偏移。当指令执行时,处理器会使用当前指令的地址加上该相对地址来计算目标地址。相对寻址通常用于相对较短的跳转或分支指令,它使得指令更加紧凑,并且在代码重定位时更加方便。以当前程序计数器pc的内容为基址,加上指令给出的一字节补码数(偏移量)形成新的pc值的寻址方式称为相对寻址。隐含引用的专用寄存器是程序计数器(PC),即EA=A+(PC),它是当前PC的内容加上指令字段中A的值。一般来说,地址字段的值在这种操作下被看成2的补码数的值。因此有效地址是对当前指令地址的一个上下范围的偏移,他基于程序的局部性原理。使用相对寻址可节省指令中的地址位数,也便于程序在内存中成块搬动。
-
绝对寻址: 绝对寻址是指直接指定目标地址的方法。在绝对寻址中,指令中包含了目标地址的绝对值。当指令执行时,处理器会直接使用指令中指定的地址作为目标地址。绝对寻址通常用于跳转到固定位置的代码或数据,它的主要优势是可以直接指定目标地址,因此适用于较大的代码空间。
这两种寻址方法各有优劣,并且在不同的情况下有不同的应用场景。相对寻址通常用于控制流程中的短跳转或条件分支,而绝对寻址则更适用于需要直接访问特定位置的数据或代码的情况。在实际编程中,程序员需要根据具体需求选择合适的寻址方法。
3.2 地址重定位的方法
在 U-Boot 中进行地址重定位的方法通常包括以下步骤:
-
配置链接地址:
- 在 U-Boot 的配置文件中,通过设置
CONFIG_SYS_TEXT_BASE
宏来指定链接地址,告诉编译器将代码链接到哪个地址。
- 在 U-Boot 的配置文件中,通过设置
-
重定位代码:
- U-Boot 包含专门用于地址重定位的代码,在启动过程中执行。这些代码负责将 U-Boot 程序中的地址映射到实际的内存地址上。
-
设置链接地址与实际硬件地址的映射关系:
- 根据硬件的实际情况,设置链接地址与实际硬件地址之间的映射关系,确保 U-Boot 中的地址能够正确地映射到内存地址上。
-
初始化内存控制器:
- 在进行地址重定位之前,需要确保内存控制器已经正确初始化,以确保 U-Boot 能够正常地访问内存。
-
编写板级初始化代码:
- 针对特定的硬件平台,可能需要编写板级初始化代码,完成特定的地址重定位操作,例如将外设地址映射到正确的内存地址上。
-
测试与调试:
- 完成地址重定位后,需要进行测试与调试,确保 U-Boot 能够正确执行,并且能够顺利启动操作系统。
通过以上步骤,可以确保 U-Boot 能够在系统启动时正确加载并执行,并顺利启动操作系统。
3.3 uboot拷贝后的地址重定向:所有的链接地址+新的偏移量
通过汇编语言所有所有的所有的链接地址+新的偏移量。
四、跳转到新的uboot地址继续执行
- 在跳转到新的位置的地址之前,先无效icache
- 根据uboot在RAM中的新的位置,设置向量表入口地址
- 在uboot新的位置,继续执行uboot的后续程序:_board_init_r()函数。
- 重新使能icache
五、异常向量表定义
start开始:
b reset =》直接跳转到reset异常处理函数
ldr pc, _undefined_instruction =》把异常处理函数加载到pc指针
ldr pc, _software_interrupt =》软中断处理程序加载到pc指针