U-Boot详解
bootloader是系统上电后最初加载运行的代码。它提供了处理器上电复位后最开始需要执行的初始化代码。它主要的功能是加载与引导内核映像。
一个嵌入式的存储设备通过通常包括四个分区:
第一分区:存放的当然是u-boot
第二个分区:存放着u-boot要传给系统内核的参数
第三个分区:是系统内核(kernel)
第四个分区:则是根文件系统
如下图所示:
U-Boot是一个开源的系统引导,是用开引导启动内核的。它的整个工作可以分为两个部分,两部分合力完成把程序加载到内存中启动内核的工作。U-Boot是由根据Linux源代码改变删减发展而来的,所以U-Boot工作的两部分中的第一部分的源代码主要由汇编语言完成硬件的初始化,搭建C语言可以运行的环境,第二部分主要由C语言完成剩余其他工作。
start.S 过程
无论是什么语言C语言、汇编语言都需要一个标志,告诉系统,通过标志来找到文件可执行的入口位置。C语言规定的main( )为执行的起点。而我们这里u-boot.lds文件中是通过查找链接脚本中的ENTRY声明位置中的关键字符号,确定该符号所在文件,就是整个程序的起始位置。也就是说,通过ENTRY(_start)便可确定start.S文件是我们整个程序的起始代码所在文件。
而在进行配置(make smdk2410_config)和编译链接(make)后,会产生u-boot.lds文件
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start) //这里就是起始位置
start.S 源码分析
1. “config.h”、”version.h”是在include目录下,他们两个都是在配置过程中会自动生产的文件,在他们的文件中右包含了其他文件,包含很多配置过程需要调用的宏,把子文件封装在该文件中,增加了系统的可移植性。
#include <config.h>
#include <version.h>
2. “.globl ” 是个关键字,它的作用是声明全局标志,也就是说_start这个符号是可以被外部调用使用的。 _start是我们在上文提到的定义的标志起始代码的符号。
3. _start:标识这端代码的起点,也就是说当系统找到start时,就会从这个位置开始执行。
b 是跳转指令,意思就是跳转到reset标志处执行,然后返回时返回此处。当CPU复位后真正去执行的是reset处的代码。
4. 这部分是CPU处理器的异常处理向量表,设置一系列的中断向量,这些异常向量是根据硬件设计的。ARM支持7种异常中断。
当异常出现后,ARM会保存当前运行的下一条指令到寄存器LR中,将状态寄存器(CPSR)复制到备份的程序状态计数器(SPSR)中,根据这7中异常类型选择,通过设置PC程序计数器跳转执行7中异常向量所定义的异常处理程序。处理完返回。
.globl _start
_start: b reset
ldr pc, _undefined_instruction /* 未定义指令向量 ldr相当于mov操作*/
ldr pc, _software_interrupt /* 软件中断向量 */
ldr pc, _prefetch_abort /* 预取指令异常向量 */
ldr pc, _data_abort /* 数据操作异常向量 */
ldr pc, _not_used /* 未使用 */
ldr pc, _irq /* irq中断向量 */
ldr pc, _fiq /* fiq中断向量 */
/* 中断向量表入口地址 */
_undefined_instruction: .word undefined_instruction
/*就是在当前地址,即_undefined_instruction 处存放 undefined_instruction*/
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
/*word伪操作用于分配一段字内存单元(分配的单元都是字对齐的)*/
.balignl 16,0xdeadbeef
代码解释:
以_undefined_instruction为例,就是,此处分配了一个word=32bit=4字节的地址空间,里面存放的值是undefined_instruction。而此处_undefined_instruction也就是该地址空间的地址了。
用C语言来表达就是:_undefined_instruction = &undefined_instruction或*_undefined_instruction = undefined_instruction
在后面的代码,我们可以看到,undefined_instruction也是一个标号,即一个地址值,对应着就是在发生“未定义指令”的时候,系统所要去执行的代码。
所以:
ldr pc, 标号1
......
标号1:.word 标号2
......
标号2:
......(具体要执行的代码)
的意思就是,将地址为标号1中内容载入到pc,而地址为标号1中的内容,正好装的是标号2。
用C语言表达其实很简单:
PC = *(标号1) = 标号2
对PC赋值,即是实现代码跳转,所以整个这段汇编代码的意思就是:
跳转到标号2的位置,执行对应的代码。
上面代码运行过程:
1)异常向量表是硬件决定的,软件只是参照硬件的设计来实现它。
2)异常向量表中每种异常都应该被处理,否则真遇到了这种异常就跑飞了。但是我们在uboot中并未非常细致的处理各种异常。
3)复位异常处的代码是:b reset,因此在CPU复位后真正去执行的有效代码是reset处的代码,因此reset符号处才是真正的有意义的代码开始的地方。
注:ARM微处理器支持字节(8位)、半字(16位)、字(32位)3种数据类型向量跳转表,每条占四个字节(一个字),地址范围为0x0000 0000~0x0000 0020 。
ARM体系结构规定在上电复位后的起始位置,必须有8条连续的跳转指令,通过硬件实现。他们就是异常向量表。ARM在上电复位后,是从0x00000000开始启动的,在其实如果bootloader存,在执行下面第一条指令后,就无条件跳转到reset,下面一部分并没执行。设置异常向量表的作用是识别bootloader。以后系统每当有异常出现,则CPU会根据异常号,从内存的0x00000000处开始查表做相应的处理:
当一个异常出现以后,ARM会自动执行以下几个步骤:
1.把下一条指令的地址放到连接寄存器LR(通常是R14).----------------------保存位置
2.将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中--------------------保存CPSR
3.根据异常类型,强制设置CPSR的运行模式位
4.强制PC(程序计数器)从相关异常向量地址取出下一条指令执行,从而跳转到相应的异常处理程序中
ARM异常向量表
地址 | 异常 | 进入模式 | 描述 |
0x00000000 | 复位 | 管理模式 | 复位电平有效时,产生复位异常,程序跳转到复位处理程序处执行 |
0x00000004 | 未定义指令 | 未定义模式 | 遇到不能处理的指令时,产生未定义指令异常 |
0x00000008 | 软件中断 | 管理模式 | 执行SWI指令产生,用于用户模式下的程序调用特权操作指令 |
0x0000000c | 预存指令 | 中止模式 | 处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常 |
0x00000010 | 数据操作 | 中止模式 | 处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常 |
0x00000014 | 未使用 | 未使用 | 未使用 |
0x00000018 | IRQ | IRQ | 外部中断请求有效,且CPSR中的I位为0时,产生IRQ异常 |
0x0000001c | FIQ | FIQ | 快速中断请求引脚有效,且CPSR中的F位为0时,产生FIQ异常 |
.balignl 16,0xdeadbeef
这条指令的意思是地址对齐排布。为了提高访存效率,要求地址对齐。。也就是说如果当前地址不对齐16的倍数时,填充0xdeadbeef直到走到满足对齐16的倍数的位置继续操作。
其中关于所要填充的内容0xdeadbeef,本身没有真正的意义,但是很明显,字面上的意思是,(坏)死的牛肉。虽然其本身没有实际意义,但是其是在十六进制下,能表示出来的,为数不多的,可读的单词之一了。
5.这就是我们上文说的系统启动复位真正的start。也就是说,系统一启动系统实际上会通过ENTRY、b 跳转至这里真正的开始执行,系统上电启动后,默认的cpu的pc会执行0x0地址处.
设置CPSR为超级保护模式,让CPU运行在操作系统保护模式,为后续的操作来做准备,ARM系统共有用户模式、快速中断、外部中断、系统模式、超级保护模式等7种模式,而在SVC超级安全模式下,超级用户态,操作系统使用的保护模式。系统复位和软件中断时进入该模式,在这个模式下,系统中断被关闭。
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
MRS:Move to general Register from Spential register (加载特殊寄存器的值到通用寄存器)
MSR:Move to Spential register from general Register(存储通用寄存器的值到特殊寄存器)
BIC指令的格式为:BIC{条件}{S} 目的寄存器,操作数1,操作数2
BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器、被移位的寄存器、或一个立即数。
bic r0,r0,#0x1f
0x1f=11111b
其含义:清除r0的bit[4:0]位。
ORR指令的格式为:ORR{条件}{S} 目的寄存器,操作数1,操作数2
ORR指令用于在两个操作数上进行逻辑戒运算,并把结果放置到目的寄存器中。操作数1应该是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置操作数1的某些位。
orr r0,r0,#0xd3
0xd3=1101 0111
将r0与0xd3作算数或运算,然后将结果返还给r0,即把r0的bit[7:6]和bit[4]和bit[2:0]置为1。
CPSR 是当前的程序状态寄存器(Current Program Status Register)
SPSR 是保存的程序状态寄存器(Saved Program Status Register)
CPSR=0xD3的位域及含义
CPSR位域 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
位域含义 | I | F | M4 | M3 | M2 | M1 | M0 | |
0xD3 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
对应含义 | 关闭中断IRQ | 关闭快速中断FIQ | 设置CPU为SVC模式,这和上面代码注释中的“set the cpu to SVC32 mode”,也是一致的。 |
# if defined(CONFIG_S3C2400) /*关闭看门狗*/
# define pWTCON 0x15300000 /*看门狗寄存器*/
# define INTMSK 0x14400008 /*中断屏蔽寄存器*/
# define CLKDIVN 0x14800014 /*时钟分频寄存器*/
#else /* s3c2410与s3c2440下面4个寄存器地址相同 */
# define pWTCON 0x53000000 /* WATCHDOG控制寄存器地址 */
# define INTMSK 0x4A000008 /* INTMSK寄存器地址 */
# define INTSUBMSK 0x4A00001C /* INTSUBMSK寄存器地址 次级中断屏蔽寄存器*/
# define CLKDIVN 0x4C000014 /* CLKDIVN寄存器地址 ;时钟分频寄存器*/
# endif
//对与s3c2440开发板,以上代码完成了WATCHDOG,INTMSK,INTSUBMSK,CLKDIVN四个寄存器的地址的设置
更新中ing。。。。。。。。。。。。。。。。。。。
参考文章:U-Boot介绍以及start.S解析--https://blog.youkuaiyun.com/weixin_40921797/article/details/82586517
U-Boot启动过程--详细版的完全分析--https://blog.youkuaiyun.com/a925314862/article/details/71189907
Uboot16之start.S七种异常模式及向量表--https://blog.youkuaiyun.com/wangdapao12138/article/details/79721012