第9课 u-boot 分析 初探

U-boot 功能

U-boot 学习目标

我认为学习和应用u-boot对于刚接触的人来说最大的困难在于其功能比较全,文件(.S .c .h makefile文件)非常多,结构也比较复杂,难以理清各方面的关系。熟悉了一段时间之后,发现还是有很多的规律可以总结的。

一、首先要理解bootloader的作用,以及U-boot具体可以实现什么样的一些功能。

二、弄清U-boot的启动流程,这时候需要跟着整个程序走一遍,先不管实现这个流程的代码文件与文件结构。

三、熟悉整个U-boot的文件结构,重点是理解那些“移植到不同开发板上需要修改的”文件,以及它们之间的关系。重点这些文件是怎样与实际的硬件(开发板)对应和统一起来的。

四、进一步细化第二步和第三步,这是一个来回穿插的考查,考查在具体的实现整个程序流程和功能中,这些文件的结构与层次关系是怎样实施的。

五、动手针对一个开发板进行移植实验,可以参考别人的移植记录,但是通过自己的动手实验,会更加熟悉U-boot,并提高解决实际问题(排错)的能力。
![这里写图片描述

](https://img-blog.csdn.net/20150703214142031)

u-boot命令的实现。

1。定义命令
2.定义命令结构体

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写代码片

最近把boot的资料整理下,把我觉得boot比较核心的部分,完整的看了一遍,现在做个记号.我把我觉得我之前比较困惑的难点整理出来,也许大家一起讨论下,也许和我一样的新手就可以少走些弯路.
BOOT的核心就是relocate,目前见到的典型嵌入式系统,除了处理器,至少都有ROM(norflash,nandflash)RAM(SDRAM),一般把Bootloader代码放在norflash里面,而nandflash因为本身硬件原因不能随机访问,一般只是用来放应用程序.在系统加电或复位后,CPU通常由CPU制造商预先安排上地址取指令,arm体系下一般都是0x0地址取它的第一条指令,即PC = 0开始.
和boot紧密相关的个人觉得就是一下几点.
1.remap.
remap比较简单,和MMU的功能可以看做是等价的,只是一般remap地址估定为0x0 ,网上有个帖子叫<<ARM remap与重定位摘抄>>专门讲了它对remap的理解,对remap的作用是这样讲的: 当ARM处理器上电或者Reset之后,处理器从0x0取指。因此,必须保证系统上电时,0x0处有指令可以执行。所以,上电的时候,0x0地址处必定是ROM或者Flash(NOR)。但是,为了加快启动的速度,也方便可以更改异常向量表,加快中断响应速度,往往把异常向量表映射到更快、更宽(32bit/16bit)的RAM中。但是异常向量表的开始地址是由ARM架构决定的,必须位于0x0处,因此,必须把RAM映射到0x0。
文中提到了ARM处理器remap的三种情况,如下
1)如果处理器有专门的寄存器可以完成Remap。那么Remap是通过Remap寄存器的相应bit置1完成的。
如Atmel AT91xx
2)如果处理器没有专门的寄存器,但是memory的bank控制寄存器可以用来配置bank的起始地址,那么只要把RAM的起始地址编程为0x0,也可以完成remap。如samsung s3c4510 .
3)如果上面两种机制都没有,那么Remap就不要做了。因为处理器实现决定了SDRAM对应的bank地址是不能改变的。如Samsung S3c2410.
不过我的看法有点稍微不一样,如果上面两种机制都没有,那么Remap就不要做了,它给的典型例子是Samsung S3c2410 ,2410虽然sdram对应的bank地址不能改变,但它有MMU功能, MMU可以起到remap的作用,常用的最典型的应该是例子Samsung S3c44b0,它既没有mmu,又SDRAM对应地址有没办法改变.顺便补充下除了4510可以改变每个bank的地址,还有华邦的w90P740(arm7),呵呵,我现在用的U就是这款U,可以把bank的地址随意的设置.
2.relocate .
relocate (地址重定位),个人觉得这个是boot里面最麻烦也是最核心的部分,刚开始看boot代码的时候,它简直是我的恶梦,不知道大家分析boot的源码流程是否这样,也可能我大学不是计算机的,没学过编译原理(现在也没看过)对链接和加载一无所知,有两个星期非常痛苦,就是不懂人家boot里面的链接脚本为什么要那样写.网上关于uboot的帖子很多,但对链接加载这块,始终写的不详细,不知道是不是太过于基础了,高手都不愿意讲,最后自己找资料,发现其实一切痛苦的根源都是对链接和加载不太清楚造成的,但个人感觉boot除了初始化以外就是搬运程序,如何搬运?为什么要那样搬运都需要对硬件板的地址分布很清楚?而这些都是链接决定的,所以非弄清楚不可!
1.我们为什么需要relocate ? 经济方面,(nandflash和norflash 每兆价格相差悬殊),把boot代码放在norflash里面(为什么不放在nandflash里面,因为nandflash读需要驱动支持,norflash可以直接访问),boot通常很小,只需要占用几十k的空间,所以只需要很小的norflash芯片,这样很便宜,而把应用程序通常很大,所以用价格低廉nandflash来储存,实际应用,通过执行boot程序,把nandflash里面代码和数据搬运到内存中来执行,这样比程序直接放在norflash里执行,可以.另外还有运行速度方面的差别,程序在norflash里执行的速度远远小于在sdram中执行的速度,为了追求更高的速度,也需要relocate,让程序在sdram里面执行 .
2.关于加载域(VMA)和运行域(LMA)(加载域和运行域,加载域是程序代码在ROM、FLASH中的排列次序及地址安排,运行域是程序运行时代码在SRAM、SDRAM中地址安排;存储代码时按照加载域存放在FLASH中,运行时再从FLASH中取出代码到RAM运行域运行,一段代码的加载域和存储域可以不同),杜春雷在它那本经典的<<arm体系结构与编程>>一书专门有一章来讲加载域和运行域不一致的情况,但我当初接触了它的这些加载域和运行域后,看uboot的lds ,uboot的lds没有设置LMA,只是设置了VMA,为此我疑惑很久.直到耐心的看了那本链接器和加载器的书才豁然明白( http://bbs.chinaunix.net/viewthread.php?tid=817770 ),任何一个链接器和加载器的基本工作都非常简单: 将更抽象的名字与更底层的名字绑定起来,好让程序员使用更抽象的名字编写代码,链接器的就是把源文件进行符号解析,把解析出来的符号和地址的进行绑定,把全局变量,函数,标号等等这些符合和地址绑定起来.
3.boot上电后开始能够正确执行还有个很重要的原因,是要保证boot在系统加电或复位后最初执行的代码是跟地址无关的,(即在代码搬运前所执行的代码是与地址无关),地址无关即地址无关代码生成的这个映象文件可以被放在内存中的任何一个地址上运行。对于地址无关的代码, 寻址是基于pc值的, 在pc值上+/-一个偏移值, 得到运行地址,如跳转指令B.当我们执行完代码搬运,就需要跳到和地址相关的地方去执行,即我们的RAM中,一般是跳转到一个标号, 这时地址相关代码就开始运行了ldr pc,_start_armboot.因为在bin映象生成的时候,就已经把_start_armboot这个符号,和实际地址绑定在一起,当我们执行ldr pc,_start_armboot 程序就从在ROM中执行跳入到RAM中了,但前提是我们进行了代码搬移,如果没有代码搬运ldr pc,_start_armboot,RAM中没有代码程序就马上飞掉了,所有我们在在搬运之前不能寻址绝对地址有关代码,必须执行代码地址无关.


拿u-boot-1.1.4下的smdk2410来做例子,和smdk2410 board密切相关的就两个文件夹\board\smdk2410和\cpu\arm920t,里面核心文件就u-boot.lds , config.mk ,start.S .
ENTRY(_start)
SECTIONS
{
. = 0x00000000;//从0地址起始

. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;//为搬运代码提供的符号,来标明bss段地址,方便relocate
.bss : { *(.bss) }
_end = .; //定义整个image的结束地址
}
u-boot.lds 是链接脚本文件, 我刚开始看这个链接脚本文件时,我疑惑很久,不明白lds中VMA= LMA(资料上很多链接脚本包括我们公司项目里面自己写的lds脚本是通过AT命令设置过LMA,这样看起来地址空间分配更清晰),而且整个image 的VMA按照lds为基址为0x0,而2410芯片不能remap,0x0地址是ROM的区域,不是运行时RAM的地址,我的理解是代码段地址应该是指向该硬件板内存区域,设置 .text=TEXT_BASE 而不是lds中的.text=0x0 ,这个疑点弄的我当时很郁闷,想了很久也没想没有搞清楚u-boot这样链接脚本都能让boot跑起来,当我把编译出来的bin烧到norflash中,uboot居然跑起来了,同时发现了一个问题, u-boot.map 中发现 .text 是从config.mk 定义TEXT_BASE =0x33f80000 ,而不是lds设置的0x0,这又让我吃惊,没清楚是怎么会事,手上有介绍移植uboot的资料,但都对uboot链接这部分,写的不够详细,知道事config.mk文件搞的鬼,但把makefile文件看了几遍都没找不到是怎么回事(还是对makefile不熟啊!),最后把编译uboot的过程看了隐藏了个机关是
arm-linux-ld –Tu-boot-1.1.4\board\smdk2410\u-boot.lds –Ttext 0x33f80000
arm-linux-objcopy --gap-fill =0xff –O binary uboot ubtoot.bin
不知道uboot设计者为什么要在这里加一个–Ttext 而不是在lds就设置?而很多移植uboot的资料对lds文件都有所描述,但这个重要的细节似乎都漏掉了,不知道是不是因为太基础了,所以没有讲.
不过最后生成的bin 从上看arm-linux-objcopy --gap-fill =0xff –O binary uboot ubtoot.bin没有对链接生成的elf文件进行重定位,因此它的运行地址是config.mk 定义TEXT_BASE为基地址,顺序按照lds的顺序依次增加的,所以整个uboot最初运行的流程是
_start &#61664; reset &#61664; cpu_init_crit &#61664; relocate
这个部分就是完成初始化,设SVC32,关看门狗,关中断,设置时钟,初始化SDRAM(为代码搬运到SDRAM做准备),这些都很简单
relocate:
adr r0, _start
ldr r1, _TEXT_BASE
cmp r0, r1
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2
add r2, r0, r2
copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble copy_loop
看了下网上的帖子,adr指令,网上很多人被这这个指令弄郁闷,我看杜春雷的<<arm体系结构与编程>>P143讲,这个指令是基于PC或者寄存器的,读到是地址无关的,一般被编译器替换为SUB r0, pc,#offset ,不要理解为读取符合表中_start符号的地址(0x33f80000).在我们上电开始执行时,pc从0开始,所以现在r0值为0 +offset,不等于_TEXT_BASE(0x33f80000).接下来要用到链接时确定的符号地址了,_armboot_start(0x33f80000)., _bss_start(0x33f97954)这些可以在u-boot.map里面的看到, size of armboot =0x33f97954-0x33f80000 ,把_start:0x0 (norflsh)把.text ,.data的代码往SDRAM里_TEXT_BASE确定的地址: 0x33f80000搬运.s3c2410的SDRAM基地址是0x3000_0000,由于uboot支持的这个board SDRAM是64M,(0x3000_0000---0x3400_0000),所以把u-boot.bin搬运到内存的高端地址.然后跳到内存中执行,提高速度.
之后就relocate &#61664; stack_setup &#61664; clear_bss &#61664; ldr pc, _start_armboot ( ROM&#61664;RAM)
_start_armboot: .word start_armboot ( u-boot-1.1.4\lib_arm\board.c)
stack_setup , clear_bss设置堆栈清bss段,都是为进入C语言做初始化准备,通过对start_armboot链接后以及把这个函数地址已经绑定在RAM中,当执行完ldr pc, label 指令,程序将从标号绑定地址开始执行,从而实现了从地址无关程序到地址相关的转变,我们做代码搬移也是为了跳转做准备,如果没有搬移,直接访问地址相关,由于RAM中都是随机值,一跳转就马上飞了.当进入start_armboot C函数,剩下的都没什么难度了.可以慢分析源码搞定.2410没有remap寄存器, relocate时候要容易些,有remap寄存器的芯片在relocate时候进行remap会让情况更复杂些.不过原理都差不多.
在进入board.c后,uboot还做了一次代码搬运如下,大概如下图,不过分两种,一种是把pc机传的image通过串口或者网络传到内存开始执行,或者从nandflash里把应用搬到内存开始执行,不过原理都差不多.

正好公司内部给我们做了板级初始化培训,把硬件板初始流程注意要点整理出来,.和boot这部分初始化对比,可以发现硬件板初始化流程都差不多.比较头痛还是链接这部分,这方面的资料感觉太少了,没人可以指点,自己看这部分资料看的很痛苦.
【CPU核相关初始化】&#61664; 【Watchdog初始化】&#61664; 【GPIO初始化】&#61664; 【系统时钟初始化】&#61664; 【内存初始化】&#61664; 【模式初始化】&#61664; 【中断向量初始化】&#61664; 【MMU初始化】&#61664; 【Cache初始化】&#61664; 【总线初始化】&#61664; 【语言相关初始化】&#61664; 【设备相关初始化】
4.elf 格式和bin格式
executable and linking format (ELF)重定位,可以参与程序的链接(创建一个程序)和程序的执行(运行一个程序) ,主要链接,和执行,但介绍elf文件的资料很多,没时间仔细看和实际密切的就是调试程序时候都用elf格式调试,因为它包含了调试所需的各种符号, 固化的时候都是用的bin格式,是可执行映象,用objcopy 把elf 转换成bin ,不过网上介绍bin格式的资料很少,只是知道bin程序,只要把pc设置为bin映象的入口地址,就可以正确执行, objcopy 可以对elf 转换成bin再进行地址重定位,不过目前还没看见过这么干过,对于elf,和bin这些理解的都不系统,资料也很少,工作中,集成开发工具IDE又把这些设置都给屏蔽起来,有没有那个强人能写一个文档,把这些都系统的讲清楚就好了!

补充一个当时找资料看见对网上一个帖子,感觉写的很精辟的,关于地址无关的解释,网页地址被改成相当路径了,就没办法地址粘贴出来,现在把原文粘贴出来.
关键词: 地址无关
术语
地址无关: 编译地址不等于运行地址.
地址相关: 编译地址等于运行地址.
常见的一些Boot(如, U-Boot, VIVI)和Linux Kernel代码开始的一段是位置无关的, 意思就是说运行地址与编译地址无关. 如, Kernel编译地址是0xc0008000, 而运行地址是0x30008000.
为什么?
为什么代码的编译地址和运行地址会不相等呢? 原因主要有以下几种: 1) 对于Boot, 用于存放Boot代码的存储器容量小于代码量. 如, Boot片有4K, 而代码通常有50-60K. 这样, 通常会在前4K代码里, 让Boot把自己复制到RAM, 再接着运行.这里我们需要作出一个选择, 是让前面的代码与地址相关, 还是让后面的代码与地址相关呢? 显然我们会选择前面一段代码量小的与地址无关. 2) 对于Linux Kernel, 它是运行在虚拟地址空间的, 如0xc0008000, 但在MMU打开之前, 通常这个地址是
不存在的, 也就是说在MMU打开之前, Kernel的代码必须是地址无关的.
怎么办?
对于位置无关的代码, 寻址是基于pc值的, 在pc值上+/-一个偏移值, 得到运行地址.以ARM为例, 用adr来寻址, adr的实际上是一个伪指令, 在代码编译时, 会被编译器替换成对pc的+/-运算
这里要注意, 对pc的+/-运行显然是有一个地址范围的, 所以我们在上面选择代码量小的地址无关, 是很明智的.
而访问地址相关的代码, 只需要使用其它的寻址指令就行了. 但在这之前, 必须保证代码被放在正确的地址上, 所以通常都会有一个复制代码的过程, 然后就是跳转到一个标号, 地址相关代码就开始运行了.


#这几天学习了U-Boot,主要是看韦东山老师的视频和书,他讲得很详细,今天结合自己的理解总结一下。

U-Boot有什么用
1、初始化,包括关看门狗、改变时钟、初始化存储控制器等等
2、下载文件,可以用来下载内核映象、文件系统等,可以作为升级使用
3、加载操作系统,把内核、文件系统等从flash中搬到RAM中,并启动

U-Boot配置
在make之前,先执行一个配置命令 make forlinx_nand_ram256_config这条命令,看了韦老师的视频,发现就是执行了以下命令
./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410 NAND ram256
实际上就是把后面8个参数传递给mkconfig这个文件,完成配置,然后再执行make就可以完成编译,具体看韦老师的书,讲得比较详细。

U-Boot源码分析
U-Boot分成二个阶段,第一阶段的文件为cpu/arm920t/start.S和board/samsung/smdk6410/lowlevel_init.S,前者是平台相关,后者是开发板相关。
第一阶段:
1、硬件设备初始化
2、加载Bootloader的第二阶段代码准备RAM空间
3、复制Bootloader的第二阶段代码到RAM空间中
4、设置好栈
5、跳转到第二阶段代码的C入口点
第二阶段
1、初始化本阶段要使用到的硬件设备
2、系统内存映射
3、U-Boot命令的格式
4、内核设置启动参数

ARM remap功能类似于MMU

BSS段为什么要清零

BSS段清零的原因是因为这个段是BSS

要说为什么要有BSS的话,历史就比较久远了。

BSS段我所知道的起源是Unix最初的时候(当然,不排除可能有更早的情况)。变量分两种:局部变量、全局变量。根据C语法的规定,局部变量不设置初始值的时候,其初始值是不确定的,局部变量(不含静态局部变量)的存储位置位于栈上,具体位置不固定。全局变量(和静态局部变量)有专门数据段存储,初始值是0,具体位置是固定的。其实说到底,就两种,一种是位置固定(数据段里),一种是位置不固定的(栈上)。

要知道,早期的计算机存储设备是很贵的,而很多时候,数据段里的全局变量都是0(或者没有初始值),那么存储这么多的0到目标文件里其实是没有必要的。所以为了节约空间,在生成目标文件的时候,就把没有初始值(实际就是0)的数据段里的变量都放到BSS段里,这样目标文件就不需要那么大的体积里(节约磁盘空间)。只有当目标文件被载入的时候,加载器负责把BSS段清零(一个循环就可以搞定)。

之后,这个规则慢慢的成为一个标准配置,大多数编译器也就都支持了BSS段。

然后解释几个问题:

Q:为什么局部变量初始值不是0?
A:局部变量初始值也可以是零(在某些语言中就是),但这实际上需要消耗硬件指令去完成,有些时候这种清零的动作意义不大,对于编译器来说也是一种负担,每次调用函数都要消耗指令去清零,负担太大。要知道全局变量在内存中只有一份,局部变量(非静态)可以是多份的,前者一次清零就可以了,后者多次清零,负担太大。

Q:如果BSS不清零可不可以?
A:可以,如果编译器规定BSS段不清零,也是可以的,但这样的话C语言语法就要改了:未初始化的全局变量和静态局部变量,其值是未知的。甚至其它语言也要跟着改语法。

所以,BSS段清零的原因是因为这个段是BSS

现在存储介质这么便宜了,是不是BSS已经没有必要了?当然不是了,介质便宜仅限于PC和数码产品这一块,嵌入式行业永远都不存在存储介质没有限制的情况,而且如果一个EXE文件动不动就几十M,谁能受得了?

可见,虽然LDR是把基于PC的一个存储单元LPOOL的内容加载到PC中,但该存储单元中保存的却是链接时所决定的main函数入口的绝对地址,所以main函数实际所在的段不是位置无关。  

② 位置无关的常量访问。在应用程序中,经常要读写相关寄存器以完成必要的硬件初始化。为增强程序的可读性,利用EQU伪指令对一些常量进行赋值,但在访问过程中,必须实现位置无关性。下面以PXA270的GPIO初始化介绍位置无关的常量访问方法。
GPIO_BASE EQU 0x40e00000; GPIO基址寄存器地址
GPDR0 EQU 0x00c; 相对于GPIO基址寄存器的偏移量
init_GPDR0 EQU 0xfffbfe00; 寄存器GPDR0初值
LDR R1, =GPIO_BASE  
LDR R0, =init_GPDR0
STR R0, [R1, #GPDR0]
上述汇编代码段经编译后的结果为:
LDR R1, [PC, OFFSET_TO_GPIO_BASE]
LDR R0, [PC, OFFSET_TO_init_GPDR0]
STR R0, [R1, #0xc]
GPIO_BASE
DCD 0x40e00000 
GPDR0
DCD 0x00c 
init_GPDR0
DCD 0xfffbfe00  
可见,LDR伪指令实际上使用基于PC的偏移量来对符号常量GPIO_BASE和 init_GPDR0进行引用,因而是位置无关的。由此可以得出如下结论:使用LDR伪指令将一个常量读取到非PC的其他通用寄存器中可实现位置无关的常 量访问;但将一个地址值读取到PC中进行程序跳转时,跳转目标则是位置相关的。

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值