bootloader详解程序及其功能和特点.docx
《bootloader详解程序及其功能和特点.docx》由会员分享,可在线阅读,更多相关《bootloader详解程序及其功能和特点.docx(13页珍藏版)》请在冰点文库上搜索。
![bootloader详解程序及其功能和特点.docx](https://file1.bingdoc.com/fileroot1/2023-7/21/3279959e-6325-417e-a377-f32bc4028bd8/3279959e-6325-417e-a377-f32bc4028bd81.gif)
bootloader详解程序及其功能和特点
bootloader详解-----程序及其功能和特点
在进行嵌入式开发时,会碰到一个名词bootloader,那个东西不太好懂,不要说自己写bootloader,确实是能看懂他人的bootoader都比较困难。
本文详细的介绍了bootloader的原理,回答了什么是bootloader,什么缘故要用bootloader。
看到后,希望您能明白什么是bootloader。
一、引言
在专用的嵌入式板子运行GNU/Linux系统已经变得愈来愈流行。
一个嵌入式Linux系统从软件的角度看通常能够分为四个层次:
1.引导加载程序。
包括固化在固件(firmware)中的boot代码(可选),和BootLoader两大部份。
2.Linux内核。
特定于嵌入式板子的定制内核和内核的启动参数。
3.文件系统。
包括根文件系统和成立于Flash内存设备之上文件系统。
通经常使用ramdisk来作为rootfs。
4.用户应用程序。
特定于用户的应用程序。
有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。
经常使用的嵌入式GUI有:
MicroWindows和MiniGUI等。
引导加载程序是系统加电后运行的第一段软件代码。
回忆一下PC的体系结构咱们能够明白,PC机中的引导加载程序由BIOS(其本质确实是一段固件程序)和位于硬盘MBR中的OSBootLoader(比如,LILO和GRUB等)一路组成。
BIOS在完成硬件检测和资源分派后,将硬盘MBR中的BootLoader读到系统的RAM中,然后将操纵权交给OSBootLoader。
BootLoader的要紧运行任务确实是将内核映象从硬盘上读到RAM中,然后跳转到内核的入口点去运行,也即开始启动操作系统。
而在嵌入式系统中,通常并无像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。
比如在一个基于ARM7TDMIcore的嵌入式系统中,系统在上电或复位时通常都从地址0x00000000处开始执行,而在那个地址处安排的通常确实是系统的BootLoader程序。
本文将从BootLoader的概念、BootLoader的要紧任务、BootLoader的框架结构和BootLoader的安装等四个方面来讨论嵌入式系统的BootLoader。
二、BootLoader的概念
简单地说,BootLoader确实是在操作系统内核运行之前运行的一段小程序。
通过这段小程序,咱们能够初始化硬件设备、成立内存空间的映射图,从而将系统的软硬件环境带到一个适合的状态,以便为最终挪用操作系统内核预备好正确的环境。
通常,BootLoader是严峻地依托于硬件而实现的,专门是在嵌入式世界。
因此,在嵌入式世界里成立一个通用的BootLoader几乎是不可能的。
尽管如此,咱们仍然能够对BootLoader归纳出一些通用的概念来,以指导用户特定的BootLoader设计与实现。
1.BootLoader所支持的CPU和嵌入式板
每种不同的CPU体系结构都有不同的BootLoader。
有些BootLoader也支持多种体系结构的CPU,比如U-Boot就同时支持ARM体系结构和MIPS体系结构。
除依托于CPU的体系结构外,BootLoader事实上也依托于具体的嵌入式板级设备的配置。
这也确实是说,关于两块不同的嵌入式板而言,即便它们是基于同一种CPU而构建的,要想让运行在一块板子上的BootLoader程序也能运行在另一块板子上,通常也都需要修改BootLoader的源程序。
2.BootLoader的安装媒介(InstallationMedium)
系统加电或复位后,所有的CPU通常都从某个由CPU制造商预先安排的地址上取指令。
比如,基于ARM7TDMIcore的CPU在复位时通常都从地址0x00000000取它的第一条指令。
而基于CPU构建的嵌入式系统通常都有某种类型的固态存储设备(比如:
ROM、EEPROM或FLASH等)被映射到那个预先安排的地址上。
因此在系统加电后,CPU将第一执行BootLoader程序。
3.用来操纵BootLoader的设备或机制
主机和目标机之间一样通过串口成立连接,BootLoader软件在执行时通常会通过串口来进行I/O,比如:
输出打印信息到串口,从串口读取用户操纵字符等。
4.BootLoader的启动进程是单时期(SingleStage)仍是多时期(Multi-Stage)
通常多时期的BootLoader能提供更为复杂的功能,和更好的可移植性。
从固态存储设备上启动的BootLoader大多都是2时期的启动进程,也即启动进程能够分为stage1和stage2两部份。
而至于在stage1和stage2具体完成哪些任务将在下面几篇讨论。
5.BootLoader的操作模式(OperationMode)
大多数BootLoader都包括两种不同的操作模式:
"启动加载"模式和"下载"模式,这种区别仅关于开发人员才成心义。
但从最终用户的角度看,BootLoader的作用确实是用来加载操作系统,而并非存在所谓的启动加载模式与下载工作模式的区别。
启动加载(Bootloading)模式:
这种模式也称为"自主"(Autonomous)模式。
也即BootLoader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个进程并无效户的介入。
这种模式是BootLoader的正常工作模式,因此在嵌入式产品发布的时侯,BootLoader显然必需工作在这种模式下。
下载(Downloading)模式:
在这种模式下,目标机上的BootLoader将通过串口连接或网络连接等通信手腕从主机(Host)下载文件,比如:
下载内核映像和根文件系统映像等。
从主机下载的文件通常第一被BootLoader保留到目标机的RAM中,然后再被BootLoader写到目标机上的FLASH类固态存储设备中。
BootLoader的这种模式通常在第一次安装内核与根文件系统时被利用;另外,以后的系统更新也会利用BootLoader的这种工作模式。
工作于这种模式下的BootLoader通常都会向它的终端用户提供一个简单的命令行接口。
像Blob或U-Boot等如此功能壮大的BootLoader通常同时支持这两种工作模式,而且许诺用户在这两种工作模式之间进行切换。
比如,Blob在启动时处于正常的启动加载模式,可是它会延时10秒等待终端用户按下任意键而将blob切换到下载模式。
若是在10秒内没有效户按键,那么blob继续启动Linux内核。
6.BootLoader与主机之间进行文件传输所用的通信设备及协议
最多见的情形确实是,目标机上的BootLoader通过串口与主机之间进行文件传输,传输协议一般是xmodem/ymodem/zmodem协议中的一种。
可是,串口传输的速度是有限的,因此通过以太网连接并借助TFTP协议来下载文件是个更好的选择。
另外,在论及那个话题时,主机方所用的软件也要考虑。
比如,在通过以太网连接和TFTP协议来下载文件时,主机方必需有一个软件用来的提供TFTP效劳。
在讨论了BootLoader的上述概念后,下面咱们来具体看看BootLoader的应该完成哪些任务。
三、BootLoader的要紧任务与典型结构框架
在继续本节的讨论之前,第一咱们做一个假定,那确实是:
假定内核映像与根文件系统映像都被加载到RAM中运行。
之因此提出如此一个假设前提是因为,在嵌入式系统中内核映像与根文件系统映像也能够直接在ROM或Flash如此的固态存储设备中直接运行。
但这种做法无疑是以运行速度的捐躯为代价的。
从操作系统的角度看,BootLoader的总目标确实是正确地挪用内核来执行。
另外,由于BootLoader的实现依托于CPU的体系结构,因此大多数BootLoader都分为stage1和stage2两大部份。
依托于CPU体系结构的代码,比如设备初始化代码等,通常都放在stage1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。
而stage2那么通经常使用C语言来实现,如此能够实现给复杂的功能,而且代码会具有更好的可读性和可移植性。
BootLoader的stage1通常包括以下步骤(以执行的前后顺序):
·硬件设备初始化。
·为加载BootLoader的stage2预备RAM空间。
·拷贝BootLoader的stage2到RAM空间中。
·设置好堆栈。
·跳转到stage2的C入口点。
BootLoader的stage2通常包括以下步骤(以执行的前后顺序):
·初始化本时期要利用到的硬件设备。
·检测系统内存映射(memorymap)。
·将kernel映像和根文件系统映像从flash上读到RAM空间中。
·为内核设置启动参数。
·挪用内核。
3.1BootLoader的stage1
3.1.1大体的硬件初始化
这是BootLoader一开始就执行的操作,其目的是为stage2的执行和随后的kernel的执行预备好一些大体的硬件环境。
它通常包括以下步骤(以执行的前后顺序):
1.屏蔽所有的中断。
为中断提供效劳一般是OS设备驱动程序的责任,因此在BootLoader的执行全进程中能够没必要响应任何中断。
中断屏蔽能够通过写CPU的中断屏蔽寄放器或状态寄放器(比如ARM的CPSR寄放器)来完成。
2.设置CPU的速度和时钟频率。
3.RAM初始化。
包括正确地设置系统的内存操纵器的功能寄放器和各内存库操纵寄放器等。
4.初始化LED。
典型地,通过GPIO来驱动LED,其目的是说明系统的状态是OK仍是Error。
若是板子上没有LED,那么也能够通过初始化UART向串口打印BootLoader的Logo字符信息来完成这一点。
5.关闭CPU内部指令/数据cache。
3.1.2为加载stage2预备RAM空间
为了取得更快的执行速度,通常把stage2加载到RAM空间中来执行,因此必需为加载BootLoader的stage2预备好一段可用的RAM空间范围。
由于stage2一般是C语言执行代码,因此在考虑空间大小时,除stage2可执行映象的大小外,还必需把堆栈空间也考虑进来。
另外,空间大小最好是memorypage大小(一般是4KB)的倍数。
一样而言,1M的RAM空间已经足够了。
具体的地址范围能够任意安排,比如blob就将它的stage2可执行映像安排到从系统RAM起始地址0xc0200000开始的1M空间内执行。
可是,将stage2安排到整个RAM空间的最顶1MB(也即(RamEnd-1MB)-RamEnd)是一种值得推荐的方式。
为了后面的表达方便,那个地址把所安排的RAM空间范围的大小记为:
stage2_size(字节),把起始地址和终止地址别离记为:
stage2_start和stage2_end(这两个地址均以4字节边界对齐)。
因此:
stage2_end=stage2_start+stage2_size
另外,还必需确保所安排的地址范围的的确确是可读写的RAM空间,因此,必需对你所安排的地址范围进行测试。
具体的测试方式能够采纳类似于blob的方式,也即:
以memorypage为被测试单位,测试每一个memorypage开始的两个字是不是是可读写的。
为了后面表达的方便,咱们记那个检测算法为:
test_mempage,其具体步骤如下:
1.先保留memorypage一开始两个字的内容。
2.向这两个字中写入任意的数字。
比如:
向第一个字写入0x55,第2个字写入0xaa。
3.然后,当即将这两个字的内容读回。
显然,咱们读到的内容应该别离是0x55和0xaa。
若是不是,那么说明那个memorypage所占据的地址范围不是一段有效的RAM空间。
4.再向这两个字中写入任意的数字。
比如:
向第一个字写入0xaa,第2个字中写入0x55。
5.然后,当即将这两个字的内容当即读回。
显然,咱们读到的内容应该别离是0xaa和0x55。
若是不是,那么说明那个memorypage所占据的地址范围不是一段有效的RAM空间。
6.恢复这两个字的原始内容。
测试完毕。
为了取得一段干净的RAM空间范围,咱们也能够将所安排的RAM空间范围进行清零操作。
3.1.3拷贝stage2到RAM中
拷贝时要确信两点:
(1)stage2的可执行映象在固态存储设备的寄存起始地址和终止地址;
(2)RAM空间的起始地址。
3.1.4设置堆栈指针sp
堆栈指针的设置是为了执行C语言代码作好预备。
通常咱们能够把sp的值设置为(stage2_end-4),也即在3.1.2节所安排的那个1MB的RAM空间的最顶端(堆栈向下生长)。
另外,在设置堆栈指针sp之前,也能够关闭led灯,以提示用户咱们预备跳转到stage2。
通过上述这些执行步骤后,系统的物理内存布局应该如以下图2所示。
3.1.5跳转到stage2的C入口点
在上述一切都就绪后,就能够够跳转到BootLoader的stage2去执行了。
比如,在ARM系统中,这能够通过修改PC寄放器为适合的地址来实现。
图2bootloader的stage2可执行映象刚被拷贝到RAM空间时的系统内存布局
3.2BootLoader的stage2
正如前面所说,stage2的代码通经常使用C语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。
可是与一般C语言应用程序不同的是,在编译和链接bootloader如此的程序时,咱们不能利用glibc库中的任何支持函数。
其缘故是显而易见的。
这就给咱们带来一个问题,那确实是从那里跳转进main()函数呢?
直接把main()函数的起始地址作为整个stage2执行映像的入口点或许是最直接的方式。
可是如此做有两个缺点:
1)无法通过main()函数传递函数参数;2)无法处置main()函数返回的情形。
一种更为巧妙的方式是利用trampoline(弹簧床)的概念。
也即,用汇编语言写一段trampoline小程序,并将这段trampoline小程序来作为stage2可执行映象的执行入口点。
然后咱们能够在trampoline汇编小程序顶用CPU跳转指令跳入main()函数中去执行;而当main()函数返回时,CPU执行途径显然再次回到咱们的trampoline程序。
简而言之,这种方式的思想确实是:
用这段trampoline小程序来作为main()函数的外部包裹(externalwrapper)。
下面给出一个简单的trampoline程序例如(来自blob):
.text
.globl_trampoline
_trampoline:
blmain
/*ifmaineverreturnswejustcallitagain*/
b_trampoline
能够看出,当main()函数返回后,咱们又用一条跳转指令从头执行trampoline程序――固然也就从头执行main()函数,这也确实是trampoline(弹簧床)一词的意思所在。
3.2.1初始化本时期要利用到的硬件设备
这通常包括:
(1)初始化至少一个串口,以便和终端用户进行I/O输出信息;
(2)初始化计时器等。
在初始化这些设备之前,也能够从头把LED灯点亮,以说明咱们已经进入main()函数执行。
设备初始化完成后,能够输出一些打印信息,程序名字字符串、版本号等。
3.2.2检测系统的内存映射(memorymap)
所谓内存映射确实是指在整个4GB物理地址空间中有哪些地址范围被分派用来寻址系统的RAM单元。
比如,在SA-1100CPU中,从0xC000,0000开始的512M地址空间被用作系统的RAM地址空间,而在SamsungS3C44B0XCPU中,从0x0c00,0000到0x1000,0000之间的64M地址空间被用作系统的RAM地址空间。
尽管CPU通常预留出一大段足够的地址空间给系统RAM,可是在搭建具体的嵌入式系统时却不必然会实现CPU预留的全数RAM地址空间。
也确实是说,具体的嵌入式系统往往只把CPU预留的全数RAM地址空间中的一部份映射到RAM单元上,而让剩下的那部份预留RAM地址空间处于未利用状态。
由于上述那个事实,因此BootLoader的stage2必需在它想干点什么(比如,将存储在flash上的内核映像读到RAM空间中)之前检测整个系统的内存映射情形,也即它必需明白CPU预留的全数RAM地址空间中的哪些被真正映射到RAM地址单元,哪些是处于"unused"状态的。
(1)内存映射的描述
能够用如下数据结构来描述RAM地址空间中的一段持续(continuous)的地址范围:
typedefstructmemory_area_struct{
u32start;/*thebaseaddressofthememoryregion*/
u32size;/*thebytenumberofthememoryregion*/
intused;
}memory_area_t;
这段RAM地址空间中的持续地址范围能够处于两种状态之一:
(1)used=1,那么说明这段持续的地址范围已被实现,也即真正地被映射到RAM单元上。
(2)used=0,那么说明这段持续的地址范围并未被系统所实现,而是处于未利用状态。
基于上述memory_area_t数据结构,整个CPU预留的RAM地址空间能够用一个memory_area_t类型的数组来表示,如下所示:
memory_area_tmemory_map[NUM_MEM_AREAS]={
[0...(NUM_MEM_AREAS-1)]={
.start=0,
.size=0,
.used=0
},
};
(2)内存映射的检测
下面咱们给出一个可用来检测整个RAM地址空间内存映射情形的简单而有效的算法:
/*数组初始化*/
for(i=0;imemory_map.used=0;
/*firstwritea0toallmemorylocations*/
for(addr=MEM_START;addr*(u32*)addr=0;
for(i=0,addr=MEM_START;addr/*
*检测从基地址MEM_START+i*PAGE_SIZE开始,大小为
*PAGE_SIZE的地址空间是不是是有效的RAM地址空间。
*/
挪用3.1.2节中的算法test_mempage();
if(currentmemorypageisnotavalidrampage){
/*noRAMhere*/
if(memory_map.used)
i++;
continue;
}
/*
*当前页已是一个被映射到RAM的有效地址范围
*可是还要看看当前页是不是只是4GB地址空间中某个地址页的别名?
*/
if(*(u32*)addr!
=0){/*alias?
*/
/*那个内存页是4GB地址空间中某个地址页的别名*/
if(memory_map.used)
i++;
continue;
}
/*
*当前页已是一个被映射到RAM的有效地址范围
*而且它也不是4GB地址空间中某个地址页的别名。
*/
if(memory_map.used==0){
memory_map.start=addr;
memory_map.size=PAGE_SIZE;
memory_map.used=1;
}else{
memory_map.size+=PAGE_SIZE;
}
}/*endoffor(…)*/
在用上述算法检测完系统的内存映射情形后,BootLoader也能够将内存映射的详细信息打印到串口。