u-boot移植篇——了解u-boot

文章目录

  • 一、U-Boot
    • 1.1 如何下载
  • 二、u-boot是如何引导内核的
    • 2.1 u-boot连接脚本
      • 2.1.1 扫盲
    • 2.2 u-boot.lds

一、U-Boot

对于嵌入式玩家来说,uboot是再熟悉不过的了,它属于BootLoader的一种,而BootLoader是在操作系统内核运行之前运行,也就是引导硬件从上电到操作系统的过程,BootLoader就是这么一段小程序(当然在这之前还有一段存在于硬盘MBR中的启动代码,这就不描述了)。
宏观的看,BootLoader主要的工作就是初始化硬件设备、建立内存空间的映射表,最终创建一个适当的系统软硬件环境。
微观的看BootLoader程序会先初始化 DDR 等外设,然后将 Linux 内核从 flash(NAND、NOR FLASH、SD、MMC 等)拷贝到DDR 中,最后启动 Linux 内核。当然了,BootLoader的实际工作要复杂的多,但是它最主要的工作就是启动 Linux 内核。
BootLoader有很多,本篇主要讲用得最广泛的u-boot。

1.1 如何下载

正常来说,学习u-boot去了了解工作原理或者是用于移植适配工作,对于移植一般而言,芯片厂商都会提供支持自家芯片的uboot版本,因为是芯片厂商自己维护的,而且是经过定制的,所以支持会很全,用户可以很方便的到芯片产商的官网下载,比如TI、Xilinx等。
如果是纯粹想阅读源码,那么可以到uboot 官网去下载,如图所示:

u-boot移植篇——了解u-boot_第1张图片
u-boot移植篇——了解u-boot_第2张图片
这里提供的方法有几种,你可以通过git直接从denx下载,也可以通过https或ftp的方式下载;
u-boot移植篇——了解u-boot_第3张图片

git方式

u-boot移植篇——了解u-boot_第4张图片

u-boot ftp服务器

本篇以u-boot2018.01开始;

二、u-boot是如何引导内核的

2.1 u-boot连接脚本

很多朋友刚开始接触uboot可能会一头雾水,要怎么开始入手进行分析,干啃整套uboot源码明显是不现实的,要分析uboot的启动流程,需要先弄清楚uboot的作用,像前面所述,uboot初始化硬件,引导内核,那么怎么开始初始化呢?第一行程序在哪里呢?从哪里开始入手去分析?
其实有办法,那就是uboot的链接脚本,我们可以从u-boot.lds入手分析。

2.1.1 扫盲

网上有很多关于u-boot.lds的解析的文章,但是都没有说得清楚根源,这里简单得总结一下,大伙应该都知道编译分为四个步骤:

1. 预处理
2. 编译
3. 汇编
4. 链接

预处理阶段:像很多程序中都有包含头文件、有宏定义、有很多注释代码等等,在预处理过程中会把所有包含的头文件的内容插入到该程序的文本中,把所有的宏定义直接展开,同时删除注释代码,添加行号和文件表示等

可以通过以下指令得到预处理后的文件

# gcc -E xxx.c -o xxx.i

编译阶段:在该阶段中编译器开始检查代码的规范性即语法分析、语义分析等,也是将高级语言转化成汇编语言的过程

可以通过以下指令得到编译后的文件

# gcc -S xxx.i -o xxx.s 

汇编阶段:该阶段很多人搞混,以为汇编阶段就是把高级语言转成汇编代码,其实在上一步已经完成了,该阶段只要是将前面编译阶段生成.s文件转成二进制也就是计算机可以读懂的机器语言的过程

可以通过以下指令得到汇编后的文件

# gcc -c xxx.s -o xxx.o

链接阶段:指前面生成的二进制文件,该二进制文件还不可以直接执行,因为很多程序都使用到了比如标准库啊、链接了别的源代码啊等等,这个时候所有生成的.o文件都是离散的,需要一个规则将所有的库按部就班的绑在一起,这个过程就叫链接过程

可以通过一下指令的到链接后的文件

# gcc xxx.o -o xxx

2.2 u-boot.lds

前面针对编译过程进行了简略的介绍,在链接阶段的介绍中提到一个链接规则,这里的**.lds**文件就是一个链接脚本,链接脚本的主要作用相信看了前面的解释应该都清楚,主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局 ,博客园中有一篇详细的解读,大伙有兴趣的话可以自行去研究,这里主要讲解uboot就不过于发散了。
怎么入口看uboot源码呢,首先可以通过u-boot.lds看起,这里有必要说明的是u-boot.lds有两个,一个在arch/arm/cpu/u-boot.lds,一个是根目录下的u-boot.lds,前者不是最终使用的链接脚本,后者才是以前者为基础生成的一套完整的链接脚本,需要先编译uboot后才生成得链接文件,这里有必要区分一下,根目录下的链接脚本如下:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(4);
 .text :
 {
  *(.__image_copy_start)
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text*)
  *(.text*)
 }
 . = ALIGN(4);
 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
 . = ALIGN(4);
 .data : {
  *(.data*)
 }
 . = ALIGN(4);
 . = .;
 . = ALIGN(4);
 .u_boot_list : {
  KEEP(*(SORT(.u_boot_list*)));
 }
 . = ALIGN(4);
 .__efi_runtime_start : {
  *(.__efi_runtime_start)
 }
 .efi_runtime : {
  *(efi_runtime_text)
  *(efi_runtime_data)
 }
 .__efi_runtime_stop : {
  *(.__efi_runtime_stop)
 }
 .efi_runtime_rel_start :
 {
  *(.__efi_runtime_rel_start)
 }
 .efi_runtime_rel : {
  *(.relefi_runtime_text)
  *(.relefi_runtime_data)
 }
 .efi_runtime_rel_stop :
 {
  *(.__efi_runtime_rel_stop)
 }
 . = ALIGN(4);
 .image_copy_end :
 {
  *(.__image_copy_end)
 }
 .rel_dyn_start :
 {
  *(.__rel_dyn_start)
 }
 .rel.dyn : {
  *(.rel*)
 }
 .rel_dyn_end :
 {
  *(.__rel_dyn_end)
 }
 .end :
 {
  *(.__end)
 }
 _image_binary_end = .;
 .bss_start __rel_dyn_start (OVERLAY) : {
  KEEP(*(.__bss_start));
  __bss_base = .;
 }
 .bss __bss_base (OVERLAY) : {
  *(.bss*)
   . = ALIGN(4);
   __bss_limit = .;
 }
 .bss_end __bss_limit (OVERLAY) : {
  KEEP(*(.__bss_end));
 }
 /DISCARD/ : { *(.dynsym) }
 /DISCARD/ : { *(.dynbss*) }
 /DISCARD/ : { *(.dynstr*) }
 /DISCARD/ : { *(.dynamic*) }
 /DISCARD/ : { *(.plt*) }
 /DISCARD/ : { *(.interp*) }
 /DISCARD/ : { *(.gnu*) }
 /DISCARD/ : { *(.ARM.exidx*) }
 /DISCARD/ : { *(.gnu.linkonce.armexidx.*) }
}

链接脚本可以决定生成的uboot镜像中所有的.o文件和.a文件的链接地址。

  • 前两行指的是指定输出格式、小端模式的ELF模式和输出架构,如本篇中平台为ARM,不做解析;
  • 第3行, ENTRY(_start) :将符号_start的值设置成入口地址,_start 在文件 arch/arm/lib/vectors.S 中有定义;
  • 从第4行开始进入正文,第6-7行将定位器符号设置为0x00000000,"."代表当前位置,并且按4字节对齐;
  • 第10行,使用grep -nr "__image_copy_start"在uboot根目录下检索,可以看到如下:
    u-boot移植篇——了解u-boot_第5张图片
  • uboot.map是uboot的映射文件,可以通过该文件看到某个文件或者函数被链接到什么地址,打开uboot.map文件后如下:
    u-boot移植篇——了解u-boot_第6张图片
  • u-boot.map这个文件很长,我们可以通过检索想要找的函数链接位置,比如检索__image_copy_start,从1256行和1258行可以看出,.text和__image_copy_start的起始地址都为0x400000;
  • 回到u-boot.lds源码部分,第11行是vectors 段, vectors 段保存中断向量表。从上图第1262行可以看出, vectors段的起始地址也是0x400000,说明整个uboot 的起始地址就是0x400000;
  • 第12行,arch/arm/cpu/armv7/start.oarch/arm/cpu/armv7/start.s编译出来的代码放到了中断向量表后面;
  • 第13行为大家耳熟能详的text段,即其他代码段;像后续还有第16行的只读代码段、第18行的数据段等等,这些位置都是由该连接脚本决定的;
  • 第71~82行,.bss_start标号指向bss段的开始位置以及结束的位置,bss段值用于存放程序中未初始化的全局变量的内存区域,一般该段中的变量会由系统初始化为0;
  • 这里面标定的一些变量在后续分析还会继续出现,比如前面提到的__image_copy_start,该变量地址从0x400000开始,标志uboot拷贝的首地址;__image_copy_end顾名思义就是uboot拷贝的结束地址,后面做了一个表格以供参考:
变量 描述
__image_copy_start uboot拷贝的首地址
__image_copy_end uboot拷贝的结束地址
_image_binary_end 二进制镜像结束地址
__bss_start bss 段开始地址
__bss_end bss 段结束地

以上的变量可以通过上述的方法到u-boot.map文件中进行检索,除了__image_copy_start地址不变,其余的地址会根据uboot的修改,导致地址变化是很正常的,这与编译出来的文件大小、多少有关,与uboot配置以及编译器优化等级都有关联,非必要情况下不要过于纠结这个数值的大小。

以上简述了uboot的作用以及如何入手去开展uboot的学习,后面将进行细致的分析。

以上是个人工作学习的回顾总结,有帮助的可以点个赞!

你可能感兴趣的:(u-boot,linux,u-boot)