关键词不能为空

当前您在: 主页 > 英语 >

U-Boot启动过程--详细版的完全分析

作者:高考题库网
来源:https://www.bjmy2z.cn/gaokao
2021-02-09 10:02
tags:

-

2021年2月9日发(作者:cdx什么意思)


(



)U-Boot


启 动过程


--


详细版的完全分析





我们知道,


bootloader


是系统上电后最初加载运行的代码。它提供了处理器上电复位后 最


开始需要执行的初始化代码。





PC


机上引导程序一般由

< p>
BIOS


开始执行,然后读取硬盘中位于


MBR( Main Boot


Record



主引导记录


)


中的


Bootloade r(


例如


LILO


< br>GRUB),


并进一步引导操作系统的启动。




然而在嵌入式系统中通常没有像


BIOS


那样的固件程序,因此整个系统的加载启动就完


全由


bootloader


来完成。它主要的功能是加载与引导内核 映像




一个嵌入式的存储设备通过通常包括四个分区:



第一分区:存放的当然是


u-boot


第二个分区:存放着


u-boot


要传给系统内核的参数



第三个分区:是系统内核(


kernel




第四个分区:则是根文件系统



如下图所示:



u-boot


是一种普遍用于嵌入式系统中的


Bootlo ader




Bootloader


介绍




Bootloader


是进行嵌入式 开发必然会接触的一个概念,


它是嵌入式学院


<


嵌入式工程师职业


培训班


>


二 期课程中嵌入式


linux


系统开发方面的重要内容。本篇文章 主要讲解


Bootloader


的基本概念以及内部原理,


这部分内容的掌握将对嵌入式


linux


系 统开发的学习非常有帮助!



Bootloader

< p>
的定义:


Bootloader


是在操作系统运行 之前执行的一小段程序,通过这一小段


程序,


我们可以初始化硬 件设备、


建立内存空间的映射表,


从而建立适当的系统软硬件环 境,


为最终调用操作系统内核做好准备。


意思就是说如果我们要 想让一个操作系统在我们的板子


上运转起来,


我们就必须首先对 我们的板子进行一些基本配置和初始化,


然后才可以将操作


系统 引导进来运行。具体在


Bootloader


中完成了哪些操作 我们会在后面分析到,这里我们


先来回忆一下


PC


的体系结构:


PC


机中的引导加载程序是由


BIOS


和位于硬盘


MBR

中的


OS Boot Loader


(比如


LILO



GRUB


等)一起 组成的,


BIOS


在完成硬件检测和资源分配

< br>后,


将硬盘


MBR


中的


Boot Loader


读到系统的


RAM


中,


然后将控制权交给


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

< p>
不但依赖于


cpu


的体系结构,还依赖于嵌入式系 统板级设备的配置。对于


2


块不同的板子而言,即使他们


使用的是相同的处理器,要想让运行在一块板子上的


Bootload er


程序也能运行在另一块板


子上,一般也需要修改

< p>
Bootloader


的源程序。



Bootloader


的启动方式




Bootloader


的启动方式主 要有网络启动方式、磁盘启动方式和


Flash


启动方式。




1


、网络启动方式






1 Bootloader


网络启动方式示意图




如图


1


所示 ,里面主机和目标板,他们中间通过网络来连接,首先目标板的


DHCP/BIOS


通过


BOOTP


服务来为

< br>Bootloader


分配


IP


地址,配置网络参数,


这样才能支持网络传输功


能。我们使用的


u-boot


可以直接设置网络参数,因此这里就不用使用


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

< p>
上执行,


Bootloader


一般是存储在


Flash


芯片上的。


另外


Flash


上还存储着参数、


内核映像和文件系统。< /p>


这种启动方式与网络启动方式之间的不


同之处就在于,

< p>
在网络启动方式中,


内核映像和文件系统首先是放在主机上的,

< p>
然后经过网


络传输下载进目标板的,而这种启动方式中内核映像和文件系统 则直接是放在


Flash


中的,


这两点 在我们


u-boot


的使用过程中都用到了。

< br>


U-boot


的定义




U-boot


全称


Universal Boot Loader



是由


DENX


小组的开发的遵循

< p>
GPL


条款的开放源码


项目,

它的主要功能是完成硬件设备初始化、


操作系统代码搬运,


并提供一个控制台及一个


指令集在操作系统运行前操控硬件设备。


U-boot


之所以这么通用,


原因是他具有很多特点:


开放源代码、支持多种嵌入式操作系统内核、


支持多种处理器系列、 较高的稳定性、高度灵


活的功能设置、丰富的设备驱动源码以及较为丰富的开发调试文档 与强大的网络技术支持。


另外


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>


(一)


编译地址


< p>
32


位的处理器,它的每一条指令是


4


个字节,以


4


个字节存储顺序,


进行顺序执行,


CPU


是顺序执行的,只要没发生什么 跳转,它会


顺序进行执行行




编译器


会对每一条指令分配一个编译地址,


这 是编译器分配的,


在编译过程中分配的地址,


我们称

< p>
之为编译地址。



(二)


运行地址


:是指程序指令真正运行的地址,是由


用户指定


的,用户将运行地址



录到哪里



哪里就是运行的地址






比如有一个指令的编译地 址是


0x5


,实际运行的地址是


0x2 00


,如果用户将指令烧到


0x200


上,那么这条指令的运行地址就是


0x200






当编译地址和 运行地址不同的时候会出现什么结果?


结果是


不能跳转



编译后会产生跳


转地址,如果实际地址和编译 后产生的地址不相等,那么就不能跳转。




C


语言编译地址:都希望


把编译地址和实际运行 地址放在一起的


,但是


汇编代码因为不


需要做


C


语言到汇编的转换,


可以认为 的去写地址,


所以直接写的就是他的运行地址


这就


是为什么任何


bootloader


刚开始会有一段 汇编代码


,因为起始代码编译地


址和实际地址不相等,这段代码 和汇编无关,跳转用的运行地


址。





编译地址和运行地址如何来算呢?



1.


假如有两个编译地址


a=0x10



b=0x7



b

< p>
的运行地址是


0x300


,那么

< br>a


的运行地址


就是


b

< p>
的运行地址加上两者编译地址的差值,


a-b=0x10-0x7=0x3




< br>a


的运行地址就是


0x300+0x3=0x303





2.


假设


uboot


上两条指令的编译地址 为


a=0x33000007



b=0 x33000001


,这两条指


令都落在


bank6


上,现在要计算出他们对应的运行地址,要找出运行地址的始地址,这个< /p>


是由用户烧录进去的,假设运行地址的首地址是


0x0

< p>
,则


a


的运行地址为


0x 7



b



0x 1



就是这样算出来的。




为什么要分配编译地址?这样做有什么好处,有什么作用?




比如在函数


a


中定义了函数


b


,当执行到函数


b


时要进行指令跳转,要跳转到


b

< br>函数


所对应的起始地址上去,


编译时,

< br>编译器给每条指令都分配了编译地址,


如果编译器已经给


分配了地址就可以直接进行跳转,查找


b


函数跳转指令所对应的 表,进行直接跳转,因为


有个编译地址和指令对应的一个表,


如 果没有分配,


编译器就查找不到这个跳转地址,


要进

< p>
行计算,非常麻烦。




什么是《相对地址》?





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>



模式和



下载



模式


,


这种区


别仅对于开发人员才有意义。




但从最终用户的角度看


,Boot Loader

< p>
的作用就是


:


用来加载操作系统

< br>,


而并不存在所谓的


启动加载模式与下载工作模式的区别 。




(


)


启动加载


(Boot loa ding)


模式


:


这种模式也称为



自主



模式。




U-Boot


工作过程




也即



Boot Loader


从目标机上的某个固态存储设备上将操作系统加载到



RAM


中运行


,

整个过程并没有用户的介入。




这种模式是



Boot Loader


的正常工作模式


,


因此在嵌入式产品发 布的时侯


,Boot Loader


显然必须工作在这种模式下。




(二)下载


(Downloading)


模 式


:


在这种模式下


,

< br>目标机上的



Boot Loader


将通过串口连接或


网络连接等通信手段从主机


(Hos t)


下载文件


,


比如

< br>:


下载内核映像和根文件系统映像等。从主


机下载的文件 通常首先被



Boot Loader


保存


到目标机的


RAM




,


然后再被



BootLoader



到目标机上的


FLASH


类固态存储设备中。


Boo t Loader


的这种模式通常在第一次安装内核


与根文件 系统时被使用


;


此外


,


以后的系统更新也会使用



Boot Loader


的这种工作模式。


工作


于这种模式下的



Boot Loader


通常都会 向它的终端用户提供一个简单的命令行接口。这种


工作模式通常在第一次安装内核与跟文 件系统时使用。


或者在系统更新时使用。


进行嵌入式

< p>
系统调试时一般也让


bootloader


工作在 这一模式下。




UBoot


这样功能强大的



Boot Loader


同时支持这两种工作模式

< p>
,


而且允许用户在这


两种工作模式之间进行切换。




大多数



bootloader


都分为阶段



1(stage1)


和阶段



2(stage2)


两大部分


,uboot


也不例


外。依赖于



CPU


体系结构的代码


(

< p>



CPU


初始化代码 等


)


通常都放在阶段



1


中且通常用


汇编语言实现


,


而阶段



2


则通常用



C


语言来实现


,


这样可以实现复杂的功能


,


而且有更好的


可读性和移植性。






第一、大概总结性得的分析




系统启动的入口点。既然我们现在要分析


u-boot


的启动过程,就必须先找到


u-boot


最先实 现的是哪些代码,最先完成的是哪些任务。



< /p>


另一方面一个可执行的


image


必须有 一个入口点,并且只能有一个


全局入口点


,所以


要通知编译器这个入口在哪里。由此我们可以找到程序的入口点是在


/boar d/lpc2210/


中指定的,其中


ENTRY(_star t)


说明程序从


_start


开始


运行,而他指向的是


cpu/arm7tdmi/start.o< /p>


文件。



因为我们用的是


ARM7TDMI



cpu


架 构,


在复位后从地址


0x00000000

取它的第一条指令,


所以我们将


Flash


映射到这个地址上,



这样在系统加电后,

< p>
cpu


将首先执行


u-boot

< br>程序。


u-boot


的启动过程是多阶段实现的,分


了两个阶段。



依赖于

cpu


体系结构的代码(如设备初始化代码等)通常都放在


stage1


中,而且通常都是用


汇编语言来实现,以达到短小 精悍的目的。




stage2


则通常是用


C


语言来实现的,这样可以实现复 杂的功能,而且代码具有更好的可读


性和可移植性。



下面我们先详细分析下


stage1


中的代码, 如图


2


所示:






2 Start.s


程序流程





代码真正开始是在


_start


,设置


异常向量表


,这 样在


cpu


发生异常时



跳转到


/


cpu/arm7tdmi/interr upts


中去


执行相应



中断代码







interrupts

< p>
文件中大部分的异常代码都


没有实现具体的功能,


只是打印一些异常消



,其中关键的是


reset


中断代码,


跳到


reset


入口地址





reset


复位入口


之前有一些段的声 明




1.



reset


中,首先是将


cpu


设置为


svc32


模式 下,并屏蔽所有


irq



fiq




2.



u-boot


中除了


定时 器使用了中断外



其他的基本上都不需要使用中断



比如串口


通信和网络等通信等,在


u-boot


中只要完成一些简单的通信就可以了,所以在这里屏蔽掉


了所有的中断响应。



3.


初始化外部总线。这部分首先设置了


I/O

口功能,包括串口、网络接口等的设置,


其他


I/O


口都


设置为


GPIO


。然后设置


BCFG0~BCFG3


,即外部总线控制器。这里


bank0


对应


Flash

< p>


设置为


16


位宽度,< /p>


总线速度设为最慢,


以实现稳定的操作;


Bank1


对应


DRAM


< p>
设置和


Flash


相同;


Bank2


对应


RTL8019





4.


接下来是


cpu


关键设置,包括


系统重映射


(告诉处理器在系统发生中断的时候到外


部存储器 中去读取中断向量表)和


系统频率。




el_init


,设定


RAM


的时序,并将中断控制器清零。这些部分和特定的平台有


关,但大致的流 程都是一样的。




下 面就是


代码的搬移


阶段了。


为了获得更 快的执行速度




< /p>


通常把


stage2


加载到


RAM


空间中来执行,因此必须为加载


Boot L oader



stage2


准备好一段 可用的


RAM


空间范围。空间大小最好是


memory page


大小


(


通常 是


4KB)


的倍




一般而言,


1 M



RAM


空间已经足够了。



flash


中存储的


u-boot


可执行文件中,代码段、数据段以及

BSS


段都是首尾相连存储


的,




所以在计算搬移大小的时候就是利用了用


BSS


段的首地址减去代码的首地址,


这样 算


出来的就是实际使用的空间。





程序用一个循环将代码搬移到


0x8 1180000


,即


RAM


底端


1M


空间用来存储代码。





然后程序继续将中断 向量表搬到


RAM


的顶端。由于


sta ge2


通常是


C


语言执行代码,


所以还要建立堆栈去。




在堆栈区之前还要将


malloc


分配的空 间以及全局数据所需的空间空下来,他们的大小


是由宏定义给出的,可以在相应位置修改 。



基本内存分布图:





3


搬移后内存分布情况图





下来是


u-boot


启动的第二个阶段,是用


c


代码写的,




这部分是一些相对变化不大的部 分,我们针对不同的板子改变它调用的一些初始化函数,


并且通过设置一些


宏定义来改变初始化的流程





所以这些代码在移植的过程中并不需要修改,也是错 误相对较少出现的文件。




在文件的开始先是定义了一个


函数指针数组


,通过这个数组,程 序通过一个循环来按


顺序进行常规的初始化,并在其后通过一些宏定义来初始化一些特定 的设备。




在最后程序进入 一个循环,


main_loop



这个 循环接收用户输入的命令,以设


置参数或者进行启动引导




本篇文章将分析重点放在了前面的


sta rt.s


上,是因为这部分无论在移植还是在调试过程中


都是最 容易出问题的地方,


要解决问题就需要程序员对代码进行修改,


所以在这里简单介绍


了一下


start.s

的基本流程,希望能对大家有所帮助





第二、代码分析




2.2


阶段



1


介绍



uboot




stage1


代码通常放在



start.s


文件中


,


它用汇编语言写成


,


其主要代码部分如下


:


2.2.1


定义入口



由于一个可执行的



Image


必须有一个入口点


,


并且只能有一个全局入 口


,


通常这个入口放在



ROM(Flash)




0x0


地址


,


因此


,


必须通知编译器以使其知道这个入口

< br>,


该工作可通过修改连接器脚本来完成。



1. board/crane2410/:


ENTRY(_start)


==> cpu/arm920t/start.S: .globl _start


2. uboot


代码区


(TEXT_BASE = 0x33F80000)


定义在



board/crane2410/


U-Boot

< p>
启动内核的过程可以分为两个阶段,两个阶段的功能如下:





1

< br>)第一阶段的功能



?


硬件设备初始化



?


加载


U-Boot


第二阶段代码到

< br>RAM


空间



?


设置好栈



?


跳转到第二阶段代码入口





2


)第二阶段的功能



?


初始化本阶段使用的硬件设备



?


检测系统内存映射



?

< p>
将内核从


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/


中指定的


连接方式





看一下



文件,在


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 = .;



< p>


__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


就会去执行软中断的指令,这些异


常中断在

< p>
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


根据异常号在异常向量表中找到对应的异常向量,然后执行异常向

< p>
量处的跳转指令,


CPU


就跳转到对应的异常处理 程序执行。



当发生异常时,都将执行


u-boot-1.2.0/cpu/arm920t/interrupts.c


中定 义的中







start.S














代< /p>






u-boot-1.2.0/cpu/arm920t/interrupts.c

< p>
中定义。




其中复位异常向量的指令


“b


res et



决定了


U-Boot

< p>
启动后将自动跳转到标号



reset

< p>


处执行。



u-boot


存储器映射的定义



该段代码段主要是定义了


u-boot


需要 使用的一些映射区的


label


,比如用户

堆区、用户栈区、全局数据结构区等。如下图所示:


(


非常 经典的


u-boot


映射图


)



_TEXT_BASE:















.word


TEXT_BASE



声明


_TEXT_BASE


标号并用< /p>


TEXT_BASE


来初始化


(



TEXT_BASE


的值存储在当前位置即标 号的位置


)



TEXT_BASE



u-boot-1.2.0/board/smdk2410/< /p>


中定义,它的值为


0x33F80000




.globl _armboot_start




_armboot_start:











.word _start





声明


_a rmboot_start


为全局变量,并用


_start


来初始化,


_start


定义



board/smdk2410/


中,在


FLASH


中运行时,


_start

< p>
的地址为


0x0


;在


SD RAM


中运


行时,


_start


的地址为


0x33F80000


< p>


.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

< p>
为全局变量,并用


_start


来初始化,


_start


定义在


board/smdk2 410/


中,



FLASH

< p>
中运行时,


_start


的地址为


0x0




SDRAM


中运行时,


_start


的地址为

< p>
0x33F80000




3)__bss_start



_end

是在链接脚本


board/smdk2410/


中给出定义 的,


在编译


u-boot


的时候产生的 ,声明


_bss_start



_bs s_end


为全局变量,并用


_bss_start

< p>


_bss_end


来初始化。

< br>


4)


声明


IRQ_STACK _START



FIQ_STACK_START


为全局变量,如果宏定义了


CONFIG_USE_IRQ

< br>在


cpu/arm920t/cpu.c


中的

< p>
cpu_init()


函数将用到这两个全局变量。





2



CPU


进入


SVC

模式



reset:



mrs r0, cpsr












MRS


指令是唯一可以直接读取


CPSR< /p>



SPSR


寄存器的指令




bic


r0, r0, #0x1f


/*


工作模式位清零



*/



orr


r0, r0, #0xd3


< /p>


/*


工作模式位设置为


“10011”< /p>


(管理模式),并将中断禁止


位和快中断禁止位置


1 */




msr cpsr, r0





以上代码将


CPU


的工作模式位设置为管理模式,即设置相应的


CPSR


程序状态字,


并将中断禁止位和快中断禁止位置一,从而屏蔽了


IRQ



FIQ


中断。




操作系统先注册一个总的 中断,然后去查是由哪个中断源产生的中断,再去查用户注


册的中断表,查出来后就去执 行用户定义的用户中断处理函数。




3


)设置控制寄存器地址



# 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< /p>


开发板,以上代码完成了


WATCHDOG



INTMSK



INTSUBMS K



CLKDIVN


四个寄存器的地址 的设置。各个寄存器地址参见参考文献


[4]





4


)关闭看门狗




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>



当开发板很复杂时,有好几个< /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


寄存器是一个

< p>
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


就是将


INTSUBMSK


寄存器全部有效位


(低


15


位)


置一,


从而屏蔽对应


的中断。




屏蔽所有中断,为什么要关中断?



中断处理中


ldr pc


是将代码的编 译地址放在了指针上,


而这段时间还没有搬移代码,


所以编


译地址上面没有这个代码,如果进行跳转就会跳转到空指针上面


< /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

< p>
能去操作外围设备




7


)关闭


MMU


(存储器管理单元)



cache


------

< p>
(也就是做


bank


的设置)



接着往下看:



#ifndef CONFIG_SKIP_LOWLEVEL_INIT



bl


cpu_init_crit /*


;

跳转并把转移后面紧接的一条指令地址保存到链接寄存器


LR(R14)

< p>
中,以此来完成子程序的调用


*/ 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


无效


*/


327



mcr p15, 0, r0, c8, c7, 0


/*



c8


写入


0


将使


TLB


失效



*/



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


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



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



异常向量在


0x00000000



1



异常向量在< /p>



0xFFFF0000


I :


0


:关闭


ICaches



1


:开启


ICaches


R



S :


用来与页表中的描述符一起确定内存的



访问权限



B :


0



CPU


为小字节序;


1




CPU


为大字节序



C :


0


:关闭

< br>DCaches



1


:开启


DCaches


A :


0

< p>
:数据访问时不进行地址对齐检查;


1


:数据访问 时进行地址对齐检查



M :


0< /p>


:关闭


MMU



1


:开启


MMU



332~337


行代码将


c1




M


位置零,关闭了


MMU




为什么 要关闭


catch



MMU

< p>
呢?


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


< p>
cpu


内部的一个


2


级< /p>


缓存,它的作用是将常用的数据和指令放在


cpu


内部,


MMU


是用来把虚实地址转换为物理地址用的< /p>




我们的目的


:



设置控制的寄存器


,< /p>


寄存器都是实地址


(物理地址)



如果既要开启


MMU


又要做虚实地址转换的话 ,中间还多一步,多此一举了嘛


?





先要把实地址转换成虚 地址,


然后再做设置,


但对


uboot


而言就是起到一个简单的初始


化的作用和引导操作系统,如果开 启


MMU


的话,很麻烦,也没必要,所以关闭

< br>MMU.





说到


catch

就必须提到一个关键字


Volatile


,以后在设置寄存 器时会经常遇到,


他的


本质


:是告诉编 译器不要对我的代码进行优化,作用是让编写者感觉不倒变量的变化情况


(也就是说,让 它执行速度加快吧)




优化的过程



是将


常用的代码取出来放 到


catch




没有


从实际的


物理地址去取



它直接从


cpu


的缓


存中


去取


,但常用的代码就是为了 感觉一些常用变量的变化



< br>优化原因:


如果正在取数据的时候发生跳变,那么就感觉不到变量的变化了,所以


在这种情况下要用


Volatile


关 键字告诉编译器不要做优化,


每次从实际的物理地址中去取指


令 ,


这就是为什么关闭


catch


关闭< /p>


MMU






但在


C


语言 中是不会关闭


catch



MMU


的,会打开,如果编写者要感觉外界变化,


或变化太快,从


catch


中取数据会有误差,就加一个关键字


Volatile





8


)初始化


RAM


控制寄存 器




bl lowlevel_init


下来初始化各个

< p>
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

-


-


-


-


-


-


-


-



本文更新与2021-02-09 10:02,由作者提供,不代表本网站立场,转载请注明出处:https://www.bjmy2z.cn/gaokao/620489.html

U-Boot启动过程--详细版的完全分析的相关文章

  • 爱心与尊严的高中作文题库

    1.关于爱心和尊严的作文八百字 我们不必怀疑富翁的捐助,毕竟普施爱心,善莫大焉,它是一 种美;我们也不必指责苛求受捐者的冷漠的拒绝,因为人总是有尊 严的,这也是一种美。

    小学作文
  • 爱心与尊严高中作文题库

    1.关于爱心和尊严的作文八百字 我们不必怀疑富翁的捐助,毕竟普施爱心,善莫大焉,它是一 种美;我们也不必指责苛求受捐者的冷漠的拒绝,因为人总是有尊 严的,这也是一种美。

    小学作文
  • 爱心与尊重的作文题库

    1.作文关爱与尊重议论文 如果说没有爱就没有教育的话,那么离开了尊重同样也谈不上教育。 因为每一位孩子都渴望得到他人的尊重,尤其是教师的尊重。可是在现实生活中,不时会有

    小学作文
  • 爱心责任100字作文题库

    1.有关爱心,坚持,责任的作文题库各三个 一则150字左右 (要事例) “胜不骄,败不馁”这句话我常听外婆说起。 这句名言的意思是说胜利了抄不骄傲,失败了不气馁。我真正体会到它

    小学作文
  • 爱心责任心的作文题库

    1.有关爱心,坚持,责任的作文题库各三个 一则150字左右 (要事例) “胜不骄,败不馁”这句话我常听外婆说起。 这句名言的意思是说胜利了抄不骄傲,失败了不气馁。我真正体会到它

    小学作文
  • 爱心责任作文题库

    1.有关爱心,坚持,责任的作文题库各三个 一则150字左右 (要事例) “胜不骄,败不馁”这句话我常听外婆说起。 这句名言的意思是说胜利了抄不骄傲,失败了不气馁。我真正体会到它

    小学作文