-
(
一
)U-Boot
启
动过程
--
详细版的完全分析
我们知道,
bootloader
是系统上电后最初加载运行的代码。它提供了处理器上电复位后
最
开始需要执行的初始化代码。
在
PC
机上引导程序一般由
BIOS
开始执行,然后读取硬盘中位于
MBR(
Main Boot
Record
,
主引导记录
)
中的
Bootloade
r(
例如
LILO
或
< br>GRUB),
并进一步引导操作系统的启动。
然而在嵌入式系统中通常没有像
BIOS
那样的固件程序,因此整个系统的加载启动就完
全由
bootloader
来完成。它主要的功能是加载与引导内核
映像
一个嵌入式的存储设备通过通常包括四个分区:
第一分区:存放的当然是
u-boot
第二个分区:存放着
u-boot
要传给系统内核的参数
p>
第三个分区:是系统内核(
kernel
)
第四个分区:则是根文件系统
如下图所示:
u-boot
是一种普遍用于嵌入式系统中的
Bootlo
ader
。
Bootloader
介绍
Bootloader
是进行嵌入式
开发必然会接触的一个概念,
它是嵌入式学院
<
嵌入式工程师职业
培训班
>
二
期课程中嵌入式
linux
系统开发方面的重要内容。本篇文章
主要讲解
Bootloader
的基本概念以及内部原理,
p>
这部分内容的掌握将对嵌入式
linux
系
统开发的学习非常有帮助!
Bootloader
的定义:
Bootloader
是在操作系统运行
之前执行的一小段程序,通过这一小段
程序,
我们可以初始化硬
件设备、
建立内存空间的映射表,
从而建立适当的系统软硬件环
境,
为最终调用操作系统内核做好准备。
意思就是说如果我们要
想让一个操作系统在我们的板子
上运转起来,
我们就必须首先对
我们的板子进行一些基本配置和初始化,
然后才可以将操作
系统
引导进来运行。具体在
Bootloader
中完成了哪些操作
我们会在后面分析到,这里我们
先来回忆一下
PC
的体系结构:
PC
机中的引导加载程序是由
BIOS
和位于硬盘
MBR
中的
OS Boot Loader
(比如
LILO
和
GRUB
等)一起
组成的,
BIOS
在完成硬件检测和资源分配
< br>后,
将硬盘
MBR
中的
Boot Loader
读到系统的
RAM
p>
中,
然后将控制权交给
OS Boot
Loader
。
Boot Loader
的主要运行任务就是将内核映象从硬盘上读到
RAM
中,然后
跳转到内核的入
口点去运行,即开始启动操作系统。
在嵌入式系
统中,通常并没有像
BIOS
那样的固件程序
< br>(注:有的嵌入式
cpu
也会内嵌一段短小的启动程序)
,因此整个系统的加载启动任务就
完全由
Boot Loade
r
来完成。
比如在一个基于
ARM7T
DMI core
的嵌入式系统中,
系统在上
< br>电或复位时通常都从地址
0x00000000
处开始执
行,
而在这个地址处安排的通常就是系统的
Boot Load
er
程序。
(先想一下,
通用
PC
和嵌入式系统为何会在此处存在如此的差异呢?)
< br>
Bootloader
是基于特定硬件平台来实现的,
因此几乎不可能为所有的嵌入式系统建立一个
通用的
Bootl
oader
,
不同的处理器架构都有不同的
Bootloader
,
Bootloader
不但依赖于
cpu
的体系结构,还依赖于嵌入式系
统板级设备的配置。对于
2
块不同的板子而言,即使他们
使用的是相同的处理器,要想让运行在一块板子上的
Bootload
er
程序也能运行在另一块板
子上,一般也需要修改
Bootloader
的源程序。
Bootloader
的启动方式
Bootloader
的启动方式主
要有网络启动方式、磁盘启动方式和
Flash
启动方式。
p>
1
、网络启动方式
图
1
Bootloader
网络启动方式示意图
如图
1
所示
,里面主机和目标板,他们中间通过网络来连接,首先目标板的
DHCP/BIOS
p>
通过
BOOTP
服务来为
< br>Bootloader
分配
IP
地址,配置网络参数,
这样才能支持网络传输功
能。我们使用的
u-boot
可以直接设置网络参数,因此这里就不用使用
p>
DHCP
的方式动态分
配
< br>IP
了。
接下来目标板的
Boo
tloader
通过
TFTP
服务将内
核映像下载到目标板上,
然后通
过网络文件系统来建立主机与目
标板之间的文件通信过程,之后的系统更新通常也是使用
Boot
Loader
的这种工作模式。工作于这种模式下的
Boot
Loader
通常都会向它的终端用户
提供一个简单的命令行接
口。
2
、磁盘启动方式
这种方式主要是用在台式机和服务器上的,
< br>这些计算机都使用
BIOS
引导,
并且使用磁盘作
为存储介质,这里面两个重要的用来启动
li
nux
的有
LILO
和
GRUB
,这里就不再具体说明
了。
< br>
3
、
Flash
启动方式
这是我们最常
用的方式。
Flash
有
NOR
Flash
和
NAND
Flash
两种。
NOR Flash
可以支持
随机访问,所以代码可以直接在
Flash
上执行,
Bootloader
一般是存储在
p>
Flash
芯片上的。
另外
Flash
上还存储着参数、
内核映像和文件系统。<
/p>
这种启动方式与网络启动方式之间的不
同之处就在于,
在网络启动方式中,
内核映像和文件系统首先是放在主机上的,
然后经过网
络传输下载进目标板的,而这种启动方式中内核映像和文件系统
则直接是放在
Flash
中的,
这两点
在我们
u-boot
的使用过程中都用到了。
< br>
U-boot
的定义
U-boot
,
全称
Universal Boot Loader
,
p>
是由
DENX
小组的开发的遵循
GPL
条款的开放源码
项目,
它的主要功能是完成硬件设备初始化、
操作系统代码搬运,
并提供一个控制台及一个
指令集在操作系统运行前操控硬件设备。
U-boot
之所以这么通用,
原因是他具有很多特点:
p>
开放源代码、支持多种嵌入式操作系统内核、
支持多种处理器系列、
较高的稳定性、高度灵
活的功能设置、丰富的设备驱动源码以及较为丰富的开发调试文档
与强大的网络技术支持。
另外
u-boot
对操作系统和产品研发提供了灵活丰富的支持,主要表现在:可以引导压缩或
非压缩
系统内核,可以灵活设置
/
传递多个关键参数给操作系统,适合
系统在不同开发阶段
的调试要求与产品发布,支持多种文件系统,支持多种目标板环境参
数存储介质,采用
CRC32
校验,可校验内核及镜像文件是否
完好,提供多种控制台接口,使用户可以在不需
要
ICE
的情况下通过串口
/
以太网
< br>/USB
等接口下载数据并烧录到存储设备中去
(这个功
能在
实际的产品中是很实用的,尤其是在软件现场升级的时候),以及提供丰富的设备驱
动等。
u-boot
源代码的目录结构
1
、
boa
rd
中存放于开发板相关的配置文件,每一个开发板都以子文件夹的形式出现。
2
、
Commom<
/p>
文件夹实现
u-boot
行下支持的命令
,每一个命令对应一个文件。
3
、<
/p>
cpu
中存放特定
cpu
架构相关的目录,每一款
cpu
架构都对应了一个子目
录。
4
、
D
oc
是文档目录,有
u-boot
非常
完善的文档。
5
、
< br>Drivers
中是
u-boot
支持的各种设备的驱动程序。
6
、
Fs
是支持的文件系统,其中最常用的是
JFFS2
文件系统。
7
、
Include
文件夹是
u-boot
使用的头文件,还有各种硬件平台支持的汇编文件,系统配置
文件和文件系统支持的文件。
8
、
Net
是与网络协议相关的代码,
bootp
协议、
TFTP
协
议、
NFS
文件系统得实现。
9
、
Tooles
是
生成
U-boot
的工具。
对
u-boot
的目录有了一些了解后,分析启动代码的过程就方便多了,其中比较重要的目录
就是
/board
、
/cpu
、
/drivers
和
/i
nclude
目录,如果想实现
u-boot
< br>在一个平台上的移植,就
要对这些目录进行深入的分析。
---------------------------------------
--------------------------------------------------
-----------------------------
----------
-----------
---------------------------
--------------------------------------------------
-----------------------------------------
---------------------
什么是《编译地址》?什么
是《运行地址》?
< br>
(一)
编译地址
:
32
位的处理器,它的每一条指令是
4
个字节,以
4
个字节存储顺序,
进行顺序执行,
CPU
是顺序执行的,只要没发生什么
跳转,它会
顺序进行执行行
,
编译器
会对每一条指令分配一个编译地址,
这
是编译器分配的,
在编译过程中分配的地址,
我们称
之为编译地址。
(二)
运行地址
:是指程序指令真正运行的地址,是由
用户指定
的,用户将运行地址
烧
录到哪里
,
哪里就是运行的地址
。
比如有一个指令的编译地
址是
0x5
,实际运行的地址是
0x2
00
,如果用户将指令烧到
0x200
上,那么这条指令的运行地址就是
0x200
,
当编译地址和
运行地址不同的时候会出现什么结果?
结果是
不能跳转
,
编译后会产生跳
转地址,如果实际地址和编译
后产生的地址不相等,那么就不能跳转。
C
语言编译地址:都希望
把编译地址和实际运行
地址放在一起的
,但是
汇编代码因为不
需要做
C
语言到汇编的转换,
可以认为
的去写地址,
所以直接写的就是他的运行地址
这就
是为什么任何
bootloader
刚开始会有一段
汇编代码
,因为起始代码编译地
址和实际地址不相等,这段代码
和汇编无关,跳转用的运行地
址。
编译地址和运行地址如何来算呢?
1.
假如有两个编译地址
a=0x10
,
b=0x7
,
b
的运行地址是
0x300
,那么
< br>a
的运行地址
就是
b
的运行地址加上两者编译地址的差值,
a-b=0x10-0x7=0x3
,
< br>a
的运行地址就是
0x300+0x3=0x303
p>
。
2.
假设
uboot
上两条指令的编译地址
为
a=0x33000007
和
b=0
x33000001
,这两条指
令都落在
bank6
上,现在要计算出他们对应的运行地址,要找出运行地址的始地址,这个<
/p>
是由用户烧录进去的,假设运行地址的首地址是
0x0
,则
a
的运行地址为
0x
7
,
b
为
0x
1
,
就是这样算出来的。
为什么要分配编译地址?这样做有什么好处,有什么作用?
比如在函数
a
中定义了函数
b
,当执行到函数
b
时要进行指令跳转,要跳转到
b
< br>函数
所对应的起始地址上去,
编译时,
< br>编译器给每条指令都分配了编译地址,
如果编译器已经给
分配了地址就可以直接进行跳转,查找
b
函数跳转指令所对应的
表,进行直接跳转,因为
有个编译地址和指令对应的一个表,
如
果没有分配,
编译器就查找不到这个跳转地址,
要进
行计算,非常麻烦。
什么是《相对地址》?
以
NOR
Flash
为例,
NOR Falsh
是映射到
bank0
上面,
SDRAM
是映射到
bank6
上
面
,
uboot
和内核最终是
在
SDRAM
上面运行,最开始我们是从
Nor Flash
的
零地址开始往
后烧录
,
uboot
中
至少有一段代码编译地址和运行地址是不一样的
,编译
uboot
或内核时,
都会将
编译地址
放入到
SDRAM
中
,
他们最终都会在
SDRAM
中执行,
< br>刚开始
uboot
在
Nor <
/p>
Flash
中运行,运行地址是一个低端地址,是
bank0
中的一个地址,但编译地址是
bank6<
/p>
中的地址,这样就会导致绝对跳转指令执行的失败
,
所以就引出了相对地址的概念
。
那么什么是相对地址呢?
至少在
bank0
中
< br>uboot
这段代码要知道不能用
b+
< br>编译地址这样的方法去跳转指令,因
为这段代码的编译地址和运行地址不一样,<
/p>
那如何去做呢?
要去计算这个指令运行的真实地址,计算出来后再做跳转,应该是
b+
运行地址,不能出
现
b+
编译地址,而是
b+
运行地址,而运行地址是算出来的。<
/p>
_TEXT_BASE:
.word TEXT_BASE //0x33F80000,
< br>在
board/
中
这段话表示,用户告诉编译器编译地址的起始地址
大多数
Boot Loader <
/p>
都包含两种不同的操作模式
:
启动加载<
/p>
模式和
下载
p>
模式
,
这种区
p>
别仅对于开发人员才有意义。
但从最终用户的角度看
,Boot Loader
的作用就是
:
用来加载操作系统
< br>,
而并不存在所谓的
启动加载模式与下载工作模式的区别
。
(
一
)
启动加载
(Boot loa
ding)
模式
:
这种模式也称为
p>
自主
模式。
U-Boot
工作过程
也即
Boot Loader
从目标机上的某个固态存储设备上将操作系统加载到
RAM
中运行
,
整个过程并没有用户的介入。
这种模式是
Boot Loader
的正常工作模式
,
因此在嵌入式产品发
布的时侯
,Boot Loader
显然必须工作在这种模式下。
p>
(二)下载
(Downloading)
模
式
:
在这种模式下
,
< br>目标机上的
Boot Loader
将通过串口连接或
网络连接等通信手段从主机
(Hos
t)
下载文件
,
比如
< br>:
下载内核映像和根文件系统映像等。从主
机下载的文件
通常首先被
Boot Loader
保存
到目标机的
RAM
中
,
然后再被
BootLoader
写
到目标机上的
FLASH
类固态存储设备中。
Boo
t Loader
的这种模式通常在第一次安装内核
与根文件
系统时被使用
;
此外
,
以后的系统更新也会使用
Boot Loader
的这种工作模式。
工作
于这种模式下的
Boot Loader
通常都会
向它的终端用户提供一个简单的命令行接口。这种
工作模式通常在第一次安装内核与跟文
件系统时使用。
或者在系统更新时使用。
进行嵌入式
系统调试时一般也让
bootloader
工作在
这一模式下。
UBoot
这样功能强大的
Boot Loader
同时支持这两种工作模式
,
而且允许用户在这
两种工作模式之间进行切换。
大多数
bootloader
都分为阶段
1(stage1)
和阶段
2(stage2)
两大部分
,uboot
也不例
外。依赖于
CPU
体系结构的代码
(
如
CPU
初始化代码
等
)
通常都放在阶段
1
中且通常用
汇编语言实现
,
而阶段
2
则通常用
C
语言来实现
,
这样可以实现复杂的功能
,
而且有更好的
可读性和移植性。
第一、大概总结性得的分析
系统启动的入口点。既然我们现在要分析
u-boot
的启动过程,就必须先找到
u-boot
最先实
现的是哪些代码,最先完成的是哪些任务。
<
/p>
另一方面一个可执行的
image
必须有
一个入口点,并且只能有一个
全局入口点
,所以
要通知编译器这个入口在哪里。由此我们可以找到程序的入口点是在
/boar
d/lpc2210/
中指定的,其中
ENTRY(_star
t)
说明程序从
_start
开始
p>
运行,而他指向的是
cpu/arm7tdmi/start.o<
/p>
文件。
因为我们用的是
ARM7TDMI
的
cpu
架
构,
在复位后从地址
0x00000000
取它的第一条指令,
所以我们将
Flash
映射到这个地址上,
这样在系统加电后,
cpu
将首先执行
u-boot
< br>程序。
u-boot
的启动过程是多阶段实现的,分
p>
了两个阶段。
依赖于
cpu
体系结构的代码(如设备初始化代码等)通常都放在
stage1
中,而且通常都是用
汇编语言来实现,以达到短小
精悍的目的。
而
stage2
则通常是用
C
语言来实现的,这样可以实现复
杂的功能,而且代码具有更好的可读
性和可移植性。
下面我们先详细分析下
stage1
中的代码,
如图
2
所示:
图
2
Start.s
程序流程
代码真正开始是在
_start
,设置
异常向量表
,这
样在
cpu
发生异常时
就
跳转到
/
cpu/arm7tdmi/interr
upts
中去
执行相应
得
中断代码
。
在
interrupts
文件中大部分的异常代码都
没有实现具体的功能,
只是打印一些异常消
息
,其中关键的是
reset
中断代码,
跳到
reset
入口地址
。
reset
复位入口
之前有一些段的声
明
。
1.
p>
在
reset
中,首先是将
cpu
设置为
svc32
模式
下,并屏蔽所有
irq
和
fiq
。
2.
在
u-boot
中除了
定时
器使用了中断外
,
其他的基本上都不需要使用中断
,
比如串口
通信和网络等通信等,在
u-boot
中只要完成一些简单的通信就可以了,所以在这里屏蔽掉
了所有的中断响应。
3.
初始化外部总线。这部分首先设置了
I/O
口功能,包括串口、网络接口等的设置,
其他
I/O
口都
设置为
GPIO
。然后设置
BCFG0~BCFG3
,即外部总线控制器。这里
bank0
对应
Flash
,
设置为
16
位宽度,<
/p>
总线速度设为最慢,
以实现稳定的操作;
Bank1
对应
DRAM
,
设置和
Flash
相同;
Bank2
对应
RTL8019
。
p>
4.
接下来是
cpu
关键设置,包括
系统重映射
(告诉处理器在系统发生中断的时候到外
部存储器
中去读取中断向量表)和
系统频率。
el_init
,设定
RAM
的时序,并将中断控制器清零。这些部分和特定的平台有
关,但大致的流
程都是一样的。
下
面就是
代码的搬移
阶段了。
为了获得更
快的执行速度
,
<
/p>
通常把
stage2
加载到
RAM
空间中来执行,因此必须为加载
Boot L
oader
的
stage2
准备好一段
可用的
RAM
空间范围。空间大小最好是
memory page
大小
(
通常
是
4KB)
的倍
数
一般而言,
1
M
的
RAM
空间已经足够了。
flash
中存储的
u-boot
可执行文件中,代码段、数据段以及
BSS
段都是首尾相连存储
的,
所以在计算搬移大小的时候就是利用了用
p>
BSS
段的首地址减去代码的首地址,
这样
算
出来的就是实际使用的空间。
程序用一个循环将代码搬移到
0x8
1180000
,即
RAM
底端
1M
空间用来存储代码。
然后程序继续将中断
向量表搬到
RAM
的顶端。由于
sta
ge2
通常是
C
语言执行代码,
所以还要建立堆栈去。
p>
在堆栈区之前还要将
malloc
分配的空
间以及全局数据所需的空间空下来,他们的大小
是由宏定义给出的,可以在相应位置修改
。
基本内存分布图:
图
3
搬移后内存分布情况图
下来是
u-boot
启动的第二个阶段,是用
c
代码写的,
这部分是一些相对变化不大的部
分,我们针对不同的板子改变它调用的一些初始化函数,
并且通过设置一些
宏定义来改变初始化的流程
,
所以这些代码在移植的过程中并不需要修改,也是错
误相对较少出现的文件。
在文件的开始先是定义了一个
函数指针数组
,通过这个数组,程
序通过一个循环来按
顺序进行常规的初始化,并在其后通过一些宏定义来初始化一些特定
的设备。
在最后程序进入
一个循环,
main_loop
。
这个
循环接收用户输入的命令,以设
置参数或者进行启动引导
。
p>
本篇文章将分析重点放在了前面的
sta
rt.s
上,是因为这部分无论在移植还是在调试过程中
都是最
容易出问题的地方,
要解决问题就需要程序员对代码进行修改,
所以在这里简单介绍
了一下
start.s
的基本流程,希望能对大家有所帮助
第二、代码分析
2.2
阶段
1
介绍
uboot
的
stage1
代码通常放在
start.s
p>
文件中
,
它用汇编语言写成
,
其主要代码部分如下
:
2.2.1
定义入口
由于一个可执行的
Image
p>
必须有一个入口点
,
并且只能有一个全局入
口
,
通常这个入口放在
ROM(Flash)
的
0x0
地址
,
因此
,
必须通知编译器以使其知道这个入口
< br>,
该工作可通过修改连接器脚本来完成。
1. board/crane2410/:
ENTRY(_start)
==>
cpu/arm920t/start.S: .globl _start
2.
uboot
代码区
(TEXT_BASE =
0x33F80000)
定义在
board/crane2410/
U-Boot
启动内核的过程可以分为两个阶段,两个阶段的功能如下:
(
1
< br>)第一阶段的功能
?
硬件设备初始化
?
加载
U-Boot
第二阶段代码到
< br>RAM
空间
?
设置好栈
?
跳转到第二阶段代码入口
(
2
)第二阶段的功能
?
初始化本阶段使用的硬件设备
?
检测系统内存映射
?
将内核从
Flash
读取到
RAM
中
?
为内核设置启动参数
?
调用内核
1.1.1
U-Boot
启动第一阶段代码分析
第一阶段对应的文件是
cpu/arm920t/start.S
和
board/sa
msung/mini2440/lowlevel_init.S
。
< br>
U-Boot
启动第一阶段流程如下:
详细分析
图
2.1
U-Boot
启动第一阶段流程
根据
cpu/arm920t/
中指定的
连接方式
:
看一下
p>
文件,在
board/smdk2410<
/p>
目录下面,
是告诉编译器这些段
改怎么划分,
GUN
编译过的段,最基本的三个
段是
RO
,
RW
,
ZI
,
RO
表示只读,对应于
具体的指代码段,
RW
是数据段,
ZI
是归零段,就是全局变量的那段。
Uboot
代码这么多,
如何保证
start.s
会第一个执行,编译在最开始呢?就是通过
< br>
链接文件进行
OUTPUT_FORMAT(
指定输出可执行文件是
elf
格式
,
31
位
arm
指令,小端
OUTPUT_ARCH(arm)
指定输出可执行文件的平台为
ARM
ENTRY(_start)
指定输出可执行文件的起始代码为
_start
SECTIONS
{
. =
0x00000000;
//
起始地址
. = ALIGN(4);
//4
字节对齐
.text :
//
指定代码段,<
/p>
.text
的基地址为
0x33f800
00
{
cpu/arm920t/start.o (.text) //
这里把
start.o
放在第一位就表示把
start.s
编
译时放到
最开始,这就是为什么把
uboot
烧到起始地址上它肯定运行
的是
start.s
*(.text)
其他代码部分
}
. = ALIGN(4);
//
前面的
“.”
代表当前值,是计算一个当前的值,是计算上
面占用的整个空间,再加一个单元就表示它现在的位置
.rodata : { *(.rodata) }
指定只读数据段
. =
ALIGN(4);
.data : { *(.data) }
指定读写数据段
. =
ALIGN(4);
.got : { *(.got) }
只读
go
t
段,
got
段式是
< br>uboot
自定义的一个段,非标准段
. = .;
__u_boot_cmd_start =
.;
把
__u_boot_cmd_st
art
赋值为当前位置,即起始位置
.u_boot_cmd : { *(.u_boot_cmd) }
指定<
/p>
u_boot_cmd
段,
uboot<
/p>
把所有的
uboot
命令放
在该段
__u_boot_cmd_end =
.;
把
__u_boot_cmd_end
赋值为当前位置,即结束位置
. = ALIGN(4);
__bss_start
= .; /
把
__bss_start
赋值为当前位置,即
bss
段的开始位置
.bss : { *(.bss) }
指定
bss
段
_end = .;
把
__end
赋值为当前位置,即
end
段的结束位置
}
第一个链接的是
cpu
/arm920t/start.o
,因此
的入口代码在
cpu/arm920t/start.o
中
,其源代码在
cpu/arm920t/start.S
中。下
面我们来分析
cpu/arm920t/start.S
的执行
。
硬件设备初始化
设置异常向量
下面代码是系统启动后
U-boot
上
电后运行的第一段代码,它是什么意思?
u-boot
对应的第一阶段代码放在
cpu/arm920t/start.S
文件中,入口代码如下:
.
.globl _start
/*
声明一个符号可被其它文件引用
,相当于声明了一个全
局变量
*/
_start:
b
reset
ldr
pc,
_undefined_instruction
表示把
_undefined_instruction
存放的数值存放到
pc
指
针上
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
表示未定义的这个异常是由
.word
来定义的,它表示定义一
个字,一个
32
位的数
.
word
后面的数:表示把该标
识的编译地址写入当前地址,标识是不占用任何指令的。
_software_inte
rrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
.balignl 16,0xdeadbeef
他们是
系统定义的异常
,
一上电程序跳转到
reset
异常处执行相应的
汇编指令
,
下面定
义出的都是不同的异常,比如软件发生软中断时,
CPU
就会去执行软中断的指令,这些异
常中断在
CUP
中地址是从
0
开始
,每个异常占
4
个字节
ldr pc, _undefined_in
struction
表示把
_undefined_instr
uction
存放的数
值存放到
pc<
/p>
指针上
_undefined_instruction
: .word undefined
_instruction
表示未定义的这
个异常是由
.word
来定义的,它表示定义一个字,一个
32
位的数
.
< br>word
后面的数:表示把该标识的编译地址写入当前地址,标识是不占用任何指
令的。把
标识存放的数值
copy
到指
针
pc
上面,
那么标识上存放的值是什
么?
是由
.word undefi
ned_instruction
来指定的,
pc
就代表你运行代码的地址,她就实现
了
CPU
要做一次跳转时的工作
。
以上代码设置了
ARM
异常向量表,各个异常向量介绍如下:
表
2.1
ARM
异常向量表
地址
异常
进入模式
管理模式
描述
复位电平有效时,产生复位异常,程序跳转到复位处理程序处执行
0x00000000
复位
0x00000004
未定义指令
未定义模式
遇到不能处理的指令时,产生未定义指令异常
0x00000008
0x0000000c
0x00000010
0x00000014
0x00000018
0x0000001c
软件中断
预存指令
数据操作
未使用
IRQ
FIQ
管理模式
中止模式
中止模式
未使用
IRQ
FIQ
执行
SWI
< br>指令产生,用于用户模式下的程序调用特权操作指令
处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常<
/p>
处理器数据访问指令的地址不存
在,或该地址不允许当前指令访问时,产生数据中止异常
未使用
外部中断请求有效,且
CPSR
中的
I
位为
0
时,产生
< br>IRQ
异常
快速中断请求引脚有效,且
CPSR
中的
F
位为
0
时,产生<
/p>
FIQ
异常
在
cpu/arm920t/start.S
中还有这些异常
对应的异常处理程序。
当一个异常产
生时,
CPU
根据异常号在异常向量表中找到对应的异常向量,然后执行异常向
量处的跳转指令,
CPU
就跳转到对应的异常处理
程序执行。
当发生异常时,都将执行
u-boot-1.2.0/cpu/arm920t/interrupts.c
中定
义的中
断
函
数
,
即
start.S
中
要
跳
转
的
这
些
中
断
子
程
序
的
代<
/p>
码
,
均
在
u-boot-1.2.0/cpu/arm920t/interrupts.c
中定义。
其中复位异常向量的指令
“b
res
et
”
决定了
U-Boot
启动后将自动跳转到标号
“
reset
”
处执行。
u-boot
存储器映射的定义
p>
该段代码段主要是定义了
u-boot
需要
使用的一些映射区的
label
,比如用户
堆区、用户栈区、全局数据结构区等。如下图所示:
(
非常
经典的
u-boot
映射图
)
_TEXT_BASE:
.word
TEXT_BASE
声明
_TEXT_BASE
标号并用<
/p>
TEXT_BASE
来初始化
(
把
TEXT_BASE
的值存储在当前位置即标
号的位置
)
,
TEXT_BASE
p>
在
u-boot-1.2.0/board/smdk2410/<
/p>
中定义,它的值为
0x33F80000
。
.globl _armboot_start
_armboot_start:
.word _start
声明
_a
rmboot_start
为全局变量,并用
_start
p>
来初始化,
_start
定义
在
board/smdk2410/
中,在
FLASH
中运行时,
_start
的地址为
0x0
;在
SD
RAM
中运
行时,
_start
的地址为
0x33F80000
。
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word
_end
__bss_start
和
_end
是在链接脚本
board/smdk2410/
中给出
定义的,在编译
u-boot
的时候产生的,声
明
_bss_start
和
_bss_
end
为全局变量,并用
_bss_start
和
_bss_end
来初始化。
#ifdef CONFIG_USE_IRQ
.globl IRQ_STACK_START
IRQ_STACK_START:
.word
0x0badc0de
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
声明
IR
Q_STACK_START
和
FIQ_STACK_STAR
T
为全局变量,如果宏定义了
CONFIG_USE_IRQ<
/p>
在
cpu/arm920t/cpu.c
中的
cpu_init()
函数将用到这两个全局变量。
代码的解析:
1)
声明
_TEXT_BASE
标号并用
T
EXT_BASE
来初始化
(
把
TEXT_BASE
的
值
存
储
在
当
前
位
置
即
标<
/p>
号
的
位
置
)
,
TEXT_BASE
u-boot-1.2.0/board/smdk2410/
中定义,它的值为<
/p>
0x33F80000
。
2)
声明
_armboot_start
为全局变量,并用
_start
来初始化,
_start
定义在
board/smdk2
410/
中,
在
FLASH
中运行时,
_start
的地址为
0x0
;
在
SDRAM
中运行时,
_start
的地址为
0x33F80000
。
3)__bss_start
和
_end
是在链接脚本
board/smdk2410/
中给出定义
的,
在编译
u-boot
的时候产生的
,声明
_bss_start
和
_bs
s_end
为全局变量,并用
_bss_start
和
_bss_end
来初始化。
< br>
4)
声明
IRQ_STACK
_START
和
FIQ_STACK_START
为全局变量,如果宏定义了
CONFIG_USE_IRQ
< br>在
cpu/arm920t/cpu.c
中的
cpu_init()
函数将用到这两个全局变量。
(
2
)
CPU
进入
SVC
模式
reset:
mrs r0, cpsr
p>
MRS
指令是唯一可以直接读取
CPSR<
/p>
和
SPSR
寄存器的指令
bic
r0, r0, #0x1f
/*
工作模式位清零
*/
orr
r0, r0, #0xd3
<
/p>
/*
工作模式位设置为
“10011”<
/p>
(管理模式),并将中断禁止
位和快中断禁止位置
1 */
在
msr cpsr, r0
以上代码将
CPU
p>
的工作模式位设置为管理模式,即设置相应的
CPSR
程序状态字,
并将中断禁止位和快中断禁止位置一,从而屏蔽了
IRQ
和
FIQ
中断。
p>
操作系统先注册一个总的
中断,然后去查是由哪个中断源产生的中断,再去查用户注
册的中断表,查出来后就去执
行用户定义的用户中断处理函数。
(
3
)设置控制寄存器地址
# if
defined(CONFIG_S3C2400)
#
define pWTCON
0x15300000
/*;
看门狗寄存器
*/
#
define INTMSK
0x14400008
/*;
中断屏蔽寄存器
*/
#
define CLKDIVN
0x14800014
/*;
时钟分频寄存器
*/
#else
/* s3c2410
与
s3c2440
下面
4
p>
个寄存器地址相同
*/
#
define pWTCON 0x53000000
/*
WATCHDOG
控制寄存器地址
*/
#
define
INTMSK
0x4A000008
/*
INTMSK
寄存器地址
*/
#
define INTSUBMSK
0x4A00001C
/*
INTSUBMSK
寄存器地址
次级中断屏蔽寄存器
*/
#
define CLKDIVN
0x4C000014
/*
CLKDIVN
寄存器地址
;
时钟分频寄存
器
*/
# endif
对与
s3c2440<
/p>
开发板,以上代码完成了
WATCHDOG
,
INTMSK
,
INTSUBMS
K
,
CLKDIVN
四个寄存器的地址
的设置。各个寄存器地址参见参考文献
[4]
。
(
4
p>
)关闭看门狗
ldr
r0, =pWTCON
< br>/*
将
pwtcon
寄存器地址
赋给
R0*/
mov
r1, #0x0
/*r1
的内容为
0*/
str
r1,
[r0]
/*
看门狗控制
器的最低位为
0
时,看门狗不输出复位信号
*/
以上代码
向看门狗控制寄存器写入
0
,
关闭看门
狗。
否则在
U-Boot
启动过程中,
CPU
将不断重启
。
< br>
为什么要关看门狗?
就是防止,不同得
两个以上得
CPU
,进行喂狗的时间间隔问题:
说白了
,就是你运
行的代码如果超出喂狗时间,而你不
关狗,就会导致,你代码还没运行完又得去喂狗,
就这
样反复得
重启
CPU
,那你代码永远也运行不完,所以,得先关看门狗得
原因,就是这样。
关狗
---
详细的原因:
关闭看门狗,关闭中断,
所谓的
喂狗
< br>是每隔一段时间给某个寄存器置位而已,在实际中
会专门
启动一个线程或进程会专门喂狗,当上层软件出现故障时就会停止喂狗
,
停止喂狗之后,
cpu
会自动复位,
一般都在
外部<
/p>
专门有一个看门狗
,
做一个外部的电路,
不在
cpu
内部使用看门狗,
cpu
内部的看门狗是复位的
cpu
当开发板很复杂时,有好几个<
/p>
cpu
时,就不能完全让板子复位,但我们通常都让整个
板子复位。
看门狗每隔短时间就会喂狗,
问题是
在两次喂狗之间的时间间隔内,
运行的代码
的时间是否够用,<
/p>
两次喂狗之间的代码是否在两次喂狗的时间延迟之内,
如果在延迟
之外的
话,代码还没改完就又进行喂狗,代码永远也改不完
<
/p>
(
5
)屏蔽中断
mov
r1, #0xffffffff
/*
屏蔽所有中断,
某位被置
1
则对应的中断被屏蔽
*/
/*
寄存
器中的值
*/
ldr
r0,
=INTMSK
/*
将管理中断的寄存器地址赋
给
ro*/
str
r1, [r0] /*
将全
r1
的值赋给
ro
地址中的内容
*/
< br>INTMSK
是主中断屏蔽寄存器,
每一位对应
SRCPND
(中断源引脚寄存器)
中的一位,
表明
SRCPND
相应位代表的中断请
求是否被
CPU
所处理。
根据参考文献
4<
/p>
,
INTMSK
寄存器是一个
32
位的寄存器,每位对应一个中断,向其
中写入
0xffffffff
就将
INTMS
K
寄存器全部位置一,从而屏蔽对应的中断。
#
if defined(CONFIG_S3C2440)
ldr
r1,
=0x7fff
ldr
r0, =INTSUBMSK
str
r1,
[r0]
# endif
INTSUBMSK
每一位对应
SUB
SRCPND
中的一位,
表明
SUBS
RCPND
相应位代表的
中断请求是否被
CPU
所处理。
<
/p>
根据参考文献
4
,
INTSUBMSK
寄存器是一个
32
位的寄存器,
但是只使用了低
15
位
。
向其中写入
0x7fff
就是将
p>
INTSUBMSK
寄存器全部有效位
(低
15
位)
置一,
从而屏蔽对应
的中断。
屏蔽所有中断,为什么要关中断?
中断处理中
ldr pc
是将代码的编
译地址放在了指针上,
而这段时间还没有搬移代码,
所以编
p>
译地址上面没有这个代码,如果进行跳转就会跳转到空指针上面
<
/p>
(
6
)设置
CL
KDIVN
/*
FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr
r0,
=CLKDIVN
mov
r1, #3
str
r1, [r0]
#endif
设置时钟分频,为什么要设置时钟?
起始可以不设,
系统能不能跑起来和频率没有任何关系,
频率的设置是要让外围的设备能承
受所设置的频率,如果频率过高则会
导致
cpu
操作外围设备失败
说白了:
设置频率,就为了
CPU
能去操作外围设备
(
7
)关闭
MMU
(存储器管理单元)
p>
,
cache
------
(也就是做
bank
的设置)
接着往下看:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl
cpu_init_crit /*
;
跳转并把转移后面紧接的一条指令地址保存到链接寄存器
LR(R14)
中,以此来完成子程序的调用
*/ bl:
跳转指
令
同时保存当前
PC
到
R14
中
#endif
cpu_init_crit
< br>这段代码在
U-Boot
正常启动时才需要执行,
若将
U-Boot
从
RAM
中
启动则应该注释掉这段代码
。
下面分析一下
cpu_init_crit
到底做了什么:
320
#ifndef
CONFIG_SKIP_LOWLEVEL_INIT
321
cpu_init_crit:
325
mov
r0,
#0
326
mcr
p15, 0, r0, c7, c7, 0
/*
向
c7
写入
0
将使
ICache
与
DCache
p>
无效
*/
327
mcr p15, 0, r0, c8, c7,
0
/*
向
c8
写入
0
将使
TLB
失效
*/
p>
MCR
指令将
ARM
处理器的寄存器中的数据传送到协处理器的寄存器中。如果协处理器不能
成功地执行该
操作,将产生未定义的指令异常中断。
328
329
/*
330
* disable MMU stuff and caches
331
*/
332
mrc p15,
0, r0, c1, c0, 0
/*
读出控制
寄存器到
r0
中
*/
333
bic
r0, r0, #0x00002300
@ clear bits 13, 9:8 (--V- --RS)
334
bic
r0, r0, #0x00000087
@
clear bits 7, 2:0 (B--- -CAM)
335
orr
r0, r0,
#0x00000002
@ set bit 2 (A) Align
336
orr
r0, r0, #0x00001000
@ set
bit 12 (I) I-Cache
337
mcr p15, 0, r0, c1, c0, 0
/*
保存
r0
到控制寄存器
*/
MRC<
/p>
指令将协处理器的寄存器中数值传送到
ARM
处理器的寄存器中
338
339
/*
340
*
before relocating, we have to setup RAM timing
341
*
because memory timing is board-dependend, you will
342
* find
a lowlevel_init.S in your board directory.
343
*/
344
mov
ip, lr
345
346
bl
p>
lowlevel_init
调用子程序
l
owlevel_init
,在调用之前把
lr
寄存器中存储
的返回地址保存在
scratch
寄存器
(ip)
中,当程序返回时再恢复它。
348
mov
lr, ip
349
mov
pc, lr
350
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
代
码中的
c0
,
c1
,
c7
,
c8
都是
ARM920T
的协处理器
C
P15
的寄存器。其中
c7
是
cache
控制寄存器,
c8
< br>是
TLB
控制寄存器。
325~
327
行代码将
0
写入
c7
、
c8
,
使
Cache
,
TLB
内容无效。
第
332~337
行代码关闭了
MMU
。这是通过修改
CP15
的<
/p>
c1
寄存器来实现的,先看
CP15
p>
的
c1
寄存器的格式(仅列出代码中用到的
位):
表
2.3 CP15
的
c1
寄存器格式(部分)
15
1
13
1
11
1
9
7
6
5
0
4
2
0
8
4
3
2
1
.
.
V
I
.
.
R
S
B
.
.
.
.
C
A
M
各个位的意义如下:
V :
表示异常向量表所在的位置,
0
p>
:
异常向量在
0x00000000
;
1
:
异常向量在<
/p>
0xFFFF0000
I :
0
:关闭
ICaches
;
1
:开启
ICaches
R
、
S :
用来与页表中的描述符一起确定内存的
访问权限
B :
0
:
CPU
为小字节序;
1
:
CPU
为大字节序
C :
0
:关闭
< br>DCaches
;
1
:开启
p>
DCaches
A :
0
:数据访问时不进行地址对齐检查;
1
:数据访问
时进行地址对齐检查
M :
0<
/p>
:关闭
MMU
;
1
:开启
MMU
p>
332~337
行代码将
c1
的
M
位置零,关闭了
p>
MMU
。
为什么
要关闭
catch
和
MMU
呢?
catch
和
MMU
是做什么用的?
MMU
是
Memory
Management Unit
的缩写,中文名是
内存管理<
/p>
单元,它是中央处理器
(
CPU
)中用来管理虚拟存储器、物理存储器的控制线路
同时也负责
虚拟地址
映射为
物理地址
,以及提供硬件机制的内存访问
授权
概述:
一,关
catch
catch
和
MMU
是通过
CP15
管理的,刚上电的时候,
CPU
还不能管理他们
上电的时候
MMU
必须关闭,指令
catch
可关
闭,可不关闭,但数据
catch
一定要关闭
< br>
否则可能导致刚开始的代码里面,去取
数据的时候,从
catch
里面取,而这时候
< br>RAM
中数据还没有
catch
过来,导致数据预取异常
二:关
MMU
因为
MMU
是
;
把虚拟地址转化为物理地址得作用
而目的是设置控制寄存器,
而控制寄存器本来就是实地址
(物理地址)
,<
/p>
再使能
MMU
,
不就是多此一举了吗?
详细分析
---
Catch
是
cpu
内部的一个
2
级<
/p>
缓存,它的作用是将常用的数据和指令放在
cpu
内部,
MMU
是用来把虚实地址转换为物理地址用的<
/p>
我们的目的
:
是
设置控制的寄存器
,<
/p>
寄存器都是实地址
(物理地址)
,
如果既要开启
MMU
又要做虚实地址转换的话
,中间还多一步,多此一举了嘛
?
先要把实地址转换成虚
地址,
然后再做设置,
但对
uboot
而言就是起到一个简单的初始
化的作用和引导操作系统,如果开
启
MMU
的话,很麻烦,也没必要,所以关闭
< br>MMU.
说到
catch
就必须提到一个关键字
Volatile
,以后在设置寄存
器时会经常遇到,
他的
本质
:是告诉编
译器不要对我的代码进行优化,作用是让编写者感觉不倒变量的变化情况
(也就是说,让
它执行速度加快吧)
优化的过程
:
是将
常用的代码取出来放
到
catch
中
,
它
没有
从实际的
物理地址去取
p>
,
它直接从
cpu
的缓
存中
去取
,但常用的代码就是为了
感觉一些常用变量的变化
< br>优化原因:
如果正在取数据的时候发生跳变,那么就感觉不到变量的变化了,所以
在这种情况下要用
Volatile
关
键字告诉编译器不要做优化,
每次从实际的物理地址中去取指
令
,
这就是为什么关闭
catch
关闭<
/p>
MMU
。
但在
C
语言
中是不会关闭
catch
和
MMU
p>
的,会打开,如果编写者要感觉外界变化,
或变化太快,从
catch
中取数据会有误差,就加一个关键字
Volatile
。
(
8
)初始化
RAM
控制寄存
器
bl lowlevel_init
下来初始化各个
bank
,
把各个
ban
k
设置必须搞清楚,
对以后
移植复杂的
uboot
有很大帮助
设置完毕后拷贝
uboot
代码到
4k
空间,拷贝完毕后执行内存中的
uboot
代码
其中的
lowlevel_init
就完成了内存初始化的工作,由于内存初始化是依赖于开
发板的,
因此
lowlevel_init
的代码一般放在
board
下面相应的目录中。对于
mini2440
,
lowlevel_in
it
在
board/samsung/mini2440/lo
wlevel_init.S
中定义如下:
45
#define BWSCON
0x48000000
/*
13
个存储控制器的开始地址
*/
… …
129
_TEXT_BASE:
130
.word
TEXT_BASE
0x33F80000,
board/
中这段话表示,
用户告诉
编译器编译地址的起始地址
132 .globl
lowlevel_init
133
lowlevel_init:
137
ldr
r0,
=SMRDATA
SMRDATA
为这
13
个寄存器的值的开始地址
138
ldr
r1, _TEXT_BASE
139
sub
r0, r0,
r1
/*
SMRDATA
减
_TEXT_BA
SE
就是
13
个寄存器的偏移地
址
*/
140
ldr
r1,
=BWSCON
141
add
r2,
r0, #13*4
142
0:
143
ldr
r3, [r0], #4
/*
将
13
个寄存器的值逐一赋值给对应的寄存器
*/
144
str
r3, [r1], #4
执行指令后
r1
地址为
r1+4
145
cmp
r2, r0
146
bne
0b
147
148
/* everything is fine now */
149
mov
pc, lr
150
151
.ltorg
-
-
-
-
-
-
-
-
-
上一篇:kindle5使用方法手册(kindle4-5全面FAQ)
下一篇:英语综合训练题