标签:
无标签
A......Bootloader概述
Bootloader是系统上电后运行的第一段软件代码,回忆一下PC的体系结构我们可以知道,PC机的引导加载程序由BIOS(本质就是一段固件程序)和位于硬盘的MBR中的引导程序一起组成.BIOS在完成硬件检测和和资源分配后,将硬盘的MBR中引导程序读到系统中的RAM,然后将控制权交给引导程序.引导程序的主要运行任务就是将内核映象从硬盘上读到RAM中,然后跳转到内核的入口点去运行,也即开始启动操作系统.
而在嵌入式系统中,通常并没有象BIOS那样的固件程序(有的嵌入式系统也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由Bootloader完成了.比如一个基于ARM7TDMI core的嵌入式系统中,系统上电或复位时都从地址0x00000000开始执行,而在这个地址处安排的通常就是系统的Bootloader程序. 简单地说Bootloader就是操作系统内核或者用户应用程序运行之前运行的一小段程序.通过这段小程序,我们可以初始化硬件设备,建立内存空间的映射图(有的CPU没有内存映射功能如S3C44B0),从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核或用户应用程序准备好正确的环境.对于一个嵌入式系统来说,可能有的包括操作系统,有的小型系统也可以只包括应用程序,但是在这之前都需要Bootloader为他准备一个正确的环境.通常Bootloader是依赖于硬件而实现的,特别是在嵌入式领域,为嵌入式系统建立一个通用的Bootloader是非常困难的. 当然我们可以归纳出一些通用的概念来,以便我们了解特定Bootloader的设计与实现.
u Bootloader的移植和修改
每种不同的CPU体系结构都有不同的Bootloader.除了依赖CPU的体系结构外,Bootloader实际上也依赖于具体的嵌入式系统的板级设备的配置,比如板卡的硬件地址分配,RAM芯片的类型,其他外设的类型等.这也就是说对于两块不同的嵌入式板而言,即使它们是基于同一CPU而构建的,如果它们的硬件资源和配置不一样的话,要想运行在一块板子上的Bootloader程序也能运行在另一块板子上,也还是需要做一些必要的修改.
u Bootloader的安装
系统加电或复位后,所有的CPU通常都从CPU制造商预先安排的地址上取指令.比如S3C44B0在复位后都从地址0X00000000取它的第一条指令.而嵌入式系统通常都有某种类型的固态存储设备,比如ROM,RAM,FLASH等被安排在这个起始地址上,因此在系统加电后,CPU将首先执行Bootloader程序,也就是说对于S3C44B0这套系统来说,Bootloader是从0地址开始存放的,而这段起始地址需要采用可引导的固态存储设备如FLASH.
u 用来控制Bootloader的设备或机制
串口通讯是最简单也是最廉价的一种双机通讯设备,所以往往在Bootloader中主机和目标机之间都通过串口建立连接,Bootloader程序在执行时通常会通过串口来进行I/O,比如:输出打印信息到串口,从串口读取用户控制字符等.当然如果认为出口的通讯速度不够快,也可以采用网络或者USB通讯,那么相应在Bootloader中就需要编写各自的驱动.
u Bootloader的启动过程
多阶段的Bootloader能够实现更为复杂的功能,以及更好的可移植性.从固态存储设备上启动的Bootloader大都是2阶段的启动过程,也即启动程序可以分为state1和state2两部分.
u Bootloader的操作模式
大多数Bootloader都包含两种不同的操作模式."启动加载"模式和"下载"模式,这种区别对于开发人员才有意义.但从终端用户的角度看,Bootloader的作用就是用来加载操作系统,而并不存在所谓的启动加载模式和下载工作模式的区别.
启动加载(boot loading)模式,也称为自主autonomous模式,也即bootloader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入.这种模式是bootloader的正常工作模式.因此在嵌入式产品发布的时候,bootloader显然必须工作在这种模式下.
下载(down loading)模式: 在这种模式下,目标机上的bootloader将通过串口连接或网络连接通讯手段从主机下载文件,比如下载应用程序,数据文件,内核映象等.从主机下载的文件通常首先将bootloader保存到目标机的RAM中然后再被Bootloader写到目标机的固态存储设备中.Bootloader的这种模式通常在系统更新时使用.工作于这种工作模式下的Bootloader通常都会向它的终端客户提供一个简单的命令行接口.在教学系统中提供的Bootloader中没有实现自主模式,可以通过修改代码来实现该功能.
u Bootloader与主机之间进行文件传输所用的通讯设备和协议
最常见的情况就是,目标机上的Bootloader通过串口与主机之间进行文件传输,传输可以简单的采用直接数据接收,当然在串口上也可以采用xmode/ymode/zmode协议以及在以太网上采用的TPTP协议.此外,在论及这个话题时,主机方所用的软件也要考虑.比如,在通过以太网连接和TFTP协议来下载文件时,主机方必须有一个软件用来提供TFTP服务.
B......Bootloader的主要任务和典型结构框架
假定内核映象和根文件系统映象都被加载到RAM中运行.之所以提出这样一个假设前提是因为,在嵌入式系统中内核映象和根文件系统映象也可以直接在ROM或FLASH这样的固态存储设备中直接运行.但这种做法无疑是以牺牲运行速度为代价的.从操作系统的角度看,Bootloader的总目标就是正确地调用内核来执行.
另外由于Bootloader的实现依赖于CPU的体系结构,因此大多数Bootloader 都分为state1和state2两部分.依赖于CPU体系结构的代码,比如设备初始化代码等,通常都放在state1中,而且通常都用汇编语言来实现,以达到短小精悍的目的.而state2通常由C语言来实现,这样可以实现更复杂的功能.而且代码会有更好的可读性与可移植性.
Bootloader的state1通常包含下列步骤(以执行的先后顺序)
.硬件设备初始化
.为加载Bootloader的state2准备RAM空间
.COPY Bootloader 的state2到RAM空间
.设置好堆栈
.跳转到state2的C入口点
Bootloader的 state2通常包含下列步骤
.初始化本阶段要使用到的硬件设备
.检测系统内存映射(Memory Map)
.将kernel映象和根文件系统映象从flash读到RAM空间中
.为内核设置启动参数
.调用内核
Bootloader的State1
.基本的硬件初始化:这是Bootloader一开始就执行的操作,其目的是为state2的执行以及随后的kernel的执行准备好一些基本的硬件环境.它通常包含以下步骤
1.屏蔽所有中断.为中断提供服务往往是OS设备驱动程序的责任,因此在Bootloader的执行全过程中可以不必响应任何中断.中断屏蔽可以通过写CPU的中断屏蔽寄存器或状态寄存器(比如ARM的CPSR)来完成.
2.设置CPU的速度和时钟频率.
3.RAM的初始化.包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等.
4.初始化LED.典型的通过GPIO来驱动LED,其目的是表明系统的状态是OK还是ERROR.如果板子上没有LED,那也可以通过初始化UART向串口打印bootloader的logo字符信息来完成这一点.
5.关闭CPU内部指令/数据CACHE.
为加载state2准备RAM空间.为了获得更快的执行速度,通常把state2加载到RAM空间来执行,因此必须为加载Bootloader的state2准备好一段可用的RAM空间范围.由于state2通常是C语言执行代码,因此在考虑空间大小时除了state2可执行的映象大小外,还必须把堆栈空间也考虑进来.此外,空间大小最好是memory page大小(通常是4KB的整数倍).一般而言,1M的RAM空间已经足够了.具体的地址范围可以任意安排,比如blob就将它的state2可执行映象安排从系统RAM起始地址0XC0200000开始的1M空间内执行.但是,将state2安排到整个RAM空间的最顶1M RAM是一种值得推荐的方法.
为了后面的叙述方便,这里把所安排的RAM空间范围大小记为stage2_size,把起始地址和终止地址分别记为stage2_start和stage2_end,因此stage2_end=stage2_start + stage2_size ; 另外还必须确保所安排的地址范围的的确确是可读写的RAM空间,因此必须对你所安排的空间进行测试.具体的测试方法可以采取类似blob的方法,也即:以memory page为基本的测试单元,测试每个memory page开始的两个字是否可以被读写.为了后面的叙述方便,我们记这个检测方法为:test_mempage.其具体步骤如下:
1.先保存memory page一开始两个字的内容.
2.向这两个字中写入任意的数据.比如向第一个字写入0x55,向第二个字写入0xaa.
3.然后立即将这两个字的内容读回.显然我们读到的内容应该分别是0x55和0xaa,如果不是,则说明这个memory page所占据的一段地址范围不是一段有效的RAM空间.
4.再向这两个字中写入任意的数字,比如向第一个字写入0xaa,向第二个字写入0x55.
5.然后立即将这两个字的内容读回,显然我们读到的内容应该分别为0xaa和0x55,如果不是,则说明这个memory page所占据的一段地址范围不是一段有效的RAM空间.
6.恢复这两个字的原始内容,测试完毕.
为了得到一段干净的RAM地址空间,我们也可以将所安排的RAM空间进行清零操作.
拷贝stage2到RAM中,拷贝时要确定两点: 1.stage2的可执行映象在固态存储设备的存放起始地址和终止地址.2.RAM空间的起始地址.
设置堆栈指针SP 堆栈指针的设置是为了执行C语言代码做好准备. 通常我们可以把SP的值设置为stage2_end-4,也即在1M RAM空间的最顶端,堆栈向下生长.此外在设置堆栈指针SP之前,也可以关闭LED灯,以提示用户我们准备跳转到stage2.
跳转到stage2的C入口. 在上述一切就绪后,就可以跳转到bootloader的stage2去执行了.比如在RAM系统中,可以通过修改PC寄存器为合适的地址来实现.
Bootloader的stage2.
正如前面所述,stage2的代码通常用C语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性.但是与普通C语言应用程序不同的是,在编译和连接Bootloader这样的程序时,我们不能使用glibc库中的任何支持函数.其原因是显而易见的.这就给我们带来了一个问题,那就是从哪里跳转到main()函数呢? 直接把main函数的起始地址作为整个stage2执行映象的入口点或许是最直接的想法.但是这样做有两个缺点:1.无法通过main函数传递函数参数.2.无法处理main函数返回的情况.一种更为巧妙的方法是利用trampoline(弹簧床)的概念.也即用汇编语言写一段小程序,并将这段小程序作为stage2可执行映象的执行入口点.然后我们在trampoline汇编小程序中用CPU跳转指令跳入main函数中去执行.而当main函数返回时,CPU执行路径显然再次回到我们的trampoline程序.简而言之,这种方法的思想就是:用这段trampoline小程序作为main函数的外部包裹. 下面给出一个简单的 trampoline 小程序.
.text
.globl _trampoline
trampoline:
bl main
/*if main ever returns we just call it again*/
b _trampolilne
可以看出,当 main() 函数返回后,我们又用一条跳转指令重新执行 trampoline 程序,当然也就重新执行main()函数了,这也就是trampoline(弹簧床)的意思所在了.
初始化本阶段的硬件设备.
这通常包括:1.初始化至少一个串口,以便和终端客户进行I/O输出信息.2.初始化计时器等. 在初始化这些设备之前也可以重新把LED灯点亮,以表明我们已经进入main函数执行.设备初始化完以后可以输出一些打印信息,程序名字字符串,版本号等.
检测系统的内存映射
所谓内存影射就是指在整个4GB物理地址空间中有哪些地址范围被分配用来寻址系统的RAM单元.
系统分类:
ARM | 用户分类:
无分类 | 来源:
无分类 | 【推荐给朋友】 | 【添加到收藏夹】