一,XIP设备
-
一上电,CPU必定从XIP设备得到第1条指令。
-
XIP设备是指一种可以直接在存储器中执行程序代码的设备,而不需要将代码复制到内存中。XIP的全称是eXecute In Place,即芯片内执行。这类设备包括片内的SRAM、NOR Flash、BootROM等。XIP设备的启动方式是系统上电之后CPU执行的第一条指令是直接在非易失性存储器中执行的,比如被存储在flash中的启动程序,便可以直接在flash中执行,而不需要拷贝到内存中运行。这种启动方式的特点是节省了从存储器到内存的复制过程,从而减少了启动时间和资源消耗。
-
CPU上电之后可以直接从XIP设备中取指令到CPU中运行,看上去像是在flash或者ram中执行。
-
Nand Flash是非XIP设备,CPU不能直接从nand flash中取指令运行。
二,BootROM的作用
号称支持Nand启动、支持SD卡启动、支持USB启动、支持UART启动的芯片,里面必定有BootROM。
BootROM:硬件初始化、把程序从非XIP设备复制进RAM,从RAM里执行。
如何支持多种启动方式(SD卡、EMMC、USB、UART启动):
-
方法1:芯片有boot pin,决定使用哪个外设。bootrom根据引脚决定读取哪个设备的程序
-
方法2:芯片有boot pin,决定多种外设的尝试顺序
示例顺序1:SD、EMMC、USB
示例顺序2:EMMC、SD、USB
示例顺序3:USB
三,完整的u-boot复制进内存
BootROM被用来启动用户程序,用户程序可能有几百KB、几MB,但是片内的RAM只有几KB:
-
方法1:
BootROM从启动设备读取用户程序的前几KB到SRAM,运行它;
这前几KB的代码负责:初始化DDR、把完整的程序从启动设备复制到DDR、并跳到DDR运行。
-
方法2:
BootROM从启动设备读取SPL到SRAM,运行它;
SPL负责:初始化DDR、把用户程序从启动设备复制到DDR、并跳到DDR运行。
四,SPL
SPL 全称叫做:Secondary Program Loader,看名字,像是一个什么二级加载相关的;实质上,也是二级加载;
那么BootROM 的代码除了去初始化硬件环境以外,还需要去外部存储器上面,将接下来可执行的程序读到内存来执行;
既然是读到内存执行,那么这个内存可以不可以是我们板载的 DDR 呢?理论上是可以的,但是,SoC 厂家设计的 DDR 控制器呢,一般会支持很多种类型的 DDR 设备,并且会提供兼容性列表,SoC 厂家怎么可能知道用户 PCB 上到底用了哪种内存呢?所以,直接把外部可执行程序读到 DDR 显然是不太友好的,一般来说呢,SoC 都会做一个内部的小容量的 SRAM (又是成本),BootROM 将外部的可执行程序从存储器中读出来,放到 SRAM 去执行;
BootROM 会根据 Bootstrap Pin 去确定从某个存储器来读可执行的二进制文件到 SRAM 并执行;理论上来说,这个二进制文件就可以是我们的 u-boot.bin 文件了;也就是 BootROM 直接加载 u-boot.bin;
理论上是这样的,但是这里有一个问题,就是 SRAM 很贵,一般来说,SoC 的片上 SRAM 都不会太大,一般 4KB、8KB、16KB...256KB不等;但是呢,u-boot 编译出来,却很大,好几百KB,放不下!
放不下怎么办?有两种办法:
1、放不下就放不下呗,BootROM 加载多少算多少;
2、做一个小一点的 boot 程序,先让 BootROM 加载这个小的程序,后面再由这个小 boot 去加载 u-boot;
比如,我们的 u-boot 有 300KB,SRAM 有 8KB,外部 DDR 1GB:
如果使用第一种方案的话,u-boot 的前面 8K 被加载进入 SRAM 执行,u-boot 被截断,我们就需要保证在 u-boot 的前 8KB 代码,把板载的 DDR 初始化好,把整个 u-boot 拷贝到 DDR,然后跳转到 DDR 执行;
第二种方案的话,我们做一个小的 u-boot ,这个 u-boot 就叫做 spl,它很小很小(小于SRAM大小),它先被 BootROM 加载到 SRAM 运行,那么这个 spl 要做什么事情呢?最主要的就是要初始化 DDR Controller,然后将真正的大 u-boot 从外部存储器读取到 DDR 中,然后跳转到大 u-boot;
如下图所示:
0)、上电后,BootROM 开始执行,初始化时钟,关闭看门狗,关 Cache,关中断等等,根据 Bootstrap Pin 来确定启动设备,初始化外设;
1)、使用外设驱动,从存储器读取 SPL;
---------------- 以上部分是 SoC 厂家的事情,下面是用户要做的事情 ----------------
2)、SPL 被读到 SRAM 执行,此刻,控制权以及移交到我们的 SPL 了;
3)、SPL 初始化外部 DDR;
4)、SPL 使用驱动从外部存储器读取 u-boot 并放到 DDR;
5)、跳转到 DDR 中的 u-boot 执行;
6)、加载内核;
原文链接:u-boot (3) —— spl_uboot spl-CSDN博客
五,bss段,data段、text段、堆heap和栈stack
1,程序的内存分配
2,基本概念
bss段:
bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。一般在初始化时bss 段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了。
bss是英文Block Started by Symbol的简称。
bss段属于静态内存分配,
data段:
数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。
数据段属于静态内存分配。
注意,如果你想要确保变量存储在"data段",你应该确保它是已初始化的,因为未初始化的全局变量和静态局部变量通常存储在".bss段"。
堆(heap):
堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。
当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);
当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
栈(stack):
栈又称堆栈,是用户存放程序临时创建的局部变量,
也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。
除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。
由于栈的先进先出(FIFO)特点,所以栈特别方便用来保存/恢复调用现场。
从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
一个位于.bss段,而另一个位于.data段,两者的区别在于:
全局的未初始化变量存在于.bss段中,具体体现为一个占位符;
全局的已初始化变量存于.data段中;
而函数内的自动变量都在栈上分配空间;
.bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);
.data却需要占用,其内容由程序初始化。因此造成了上述情况。
bss段(未手动初始化的数据)并不给该段的数据分配空间,只是记录数据所需空间的大小;
bss段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在数据段后面。
data段(已手动初始化的数据)则为数据分配空间,数据保存在目标文件中;
data段包含经过初始化的全局变量以及它们的值。当这个内存区进入程序的地址空间后全部清零。
六,重定位
1,基本概念
重定位:
就是 uboot 将自身拷贝到 DRAM 的另一个地方去继续运行(DRAM 的高地址处)。
链接地址:
在链接脚本里指定内存地址,编译时指定的代码运行时应该所处的内存地址。它是绝对地址(编译后的定值)。
运行地址:
当前代码运行时实际所处的地址。它是相对地址(相对于当前程序开始运行时的内存地址)。
存储地址:
代码烧写到ROM中的地址,比如NANDFLASH。
链接地址和运行地址的关系:
正常情况下 链接地址 = 运行地址,当运行地址等于链接地址时,程序肯定能运行,因为我们指定链接地址的初衷就是为了让代码能在我们指定的位置中运行。
位置无关码:
从本质上讲,位置无关码就是代码中跳转或者取值、赋值的目标地址都使用相对地址,位置有关码使用的是绝对地址。
2,重定位的两种方法
方法一:
链接地址是Addr_A,拷贝到Addr_A去运行,此时链接地址和运行地址相等,程序可以正常运行。
方法二:
链接地址是Addr_A,拷贝到Addr_B去运行,运行地址和链接地址就不一样了,这样在进行寻址就会出问题, 解决办法就是使用位置无关码,对.rel.dyn的定位问题进行修复,就是为了解决该问题。
.rel.dyn段存放了.text段中需要重定位地址(也就是搬移了__image_copy_start~__image_copy_end的代码后,需要对其中存的绝对地址进行更新,即绝对地址+偏移地址)的集合。