基于S3C2410开发板的Bootloader运行原理与实现

描述

在专用的嵌入式开发板上运行操作系统(如Linux)已经变得越来越流行,而Bootloader就是为引导操作系统内核运行的一段代码。通过它可以初始化硬件设备、建立或检测内存空间的映射,其功能有点类似于PC机的BIOS(基本输入输出系统)程序。它的主要作用是为运行操作系统提供基本的运行环境,并操作系统的内核装载到存储器(RAM)中的合适位置上去运行。本文将以Samsung公司的S3C2410开发板为开发平台,具体阐述了Bootloader的运行原理与实现分析。

Bootloader程序与CPU芯片的内核结构、具体芯片和使用的操作系统等因素有着密切关系,因此要为所有类型的嵌入式开发板建立一个通用的Bootloader几乎是不可能的。尽管如此,本文将尽量对Bootloader归纳出一些通用的概念,以帮助特定用户设计实现自己的Bootloader。

1.系统组成

典型的ARM嵌入式系统硬件平台一般包括一个以ARM为内核的处理器、存储器和必要的外部接口与设备。在本系统中,采用内嵌ARM920T的Samsung公司S3C2410处理器,工作频率200MHz,存储器使用2MB的NorFlash和64MB的SDRAM,外部接口除了用于下载和通信的串口,还配备有以太网接口、USB接口。

软件平台由以下部分组成:Bootloader、嵌入式操作系统内核(Kernel)、文件系统(Filesystem)。其中,嵌入式操作系统内核是嵌入式系统加电运行后的管理平台,负责实时性任务和多任务的管理。文件系统是嵌入式系统软件平台占用存储量最大的一部分,也是与用户开发最相关的一部分。它存储了系统配置文件、系统程序、用户应用程序和必需的驱动程序。

2、Bootloader运行流程

系统加电或复位后,所有的CPU通常都从某个由CPU制造商预先安排的地址上取指令。比如SansungS3C2410CPU,在系统加电或复位时就会从地址0x00000000处读取它的第一条指令。基于CPU构建的嵌入式系统则通常都会有某种类型的固态存储设备(如本例中的FLASH)被映射到这个预先安排的地址上,而Bootloader程序一般正是被烧录或者下载到固态存储设备的0x00000000地址处,因此在系统在加电或复位后,CPU将会首先执行Bootloader程序。

由于Bootloader的实现依赖于CPU的体系结构,因此Bootloader功能的实现基本可分为Stage1和Stage2两大部分,分别运行于系统的ROM和RAM中。依赖于CPU体系结构的代码,比如设备初始化代码等,通常都放在Stage1中,基本都用汇编语言来实现,以达到简短精练的目的。而Stage2则通常用C语言来实现,这样可以实现更复杂的功能,而且代码会具有更好的可读性和可移植性。

3.1其中,Bootloader的Stage1通常依次执行以下主要步骤:

3.1.1硬件设备初始化。

其目的是为执行Stage2及随后的操作系统Kernel准备好一些基本的硬件环境。

(a)建立中断向量表,当程序出现异常后可跳转到相应子程序执行。如

存储器

(b)屏蔽所有的中断。为中断提供服务通常是操作系统设备驱动程序的责任,因此在Bootloader的执行全过程中可以不必响应任何中断。可以通过写S3C2410的寄存器INTMSK、INTSUBMSK来完成;

(c)设置CPU的速度和时钟频率。可以通过写寄存器LOCKTIME、MPLLCON、UPLLCON来实现;

(d)RAM初始化。包括正确地设置系统的内存控制器的功能寄存器BWSCON以及各内存控制寄存器等。

(e)初始化LED。典型地,通过GPIO来驱动LED,其目的是表明系统的状态是正常还是出现错误。

3.1.2为加载Stage2准备RAM空间,拷贝Stage2到RAM中。

为了获得更快的执行速度,通常把Stage2加载到RAM空间中来执行,因此必须为加载Bootloader的Stage2准备好一段可用的RAM空间范围。具体的地址范围可以任意安排,比如我们习惯将Stage2可执行映像安排到RAM地址最顶部1MB开始的空间内执行。拷贝时要确定两点:Stage2的可执行映像存放在Flash中的起始地址和终止地址;以及RAM空间的起始地址。

3.1.3设置堆栈指针sp。

堆栈指针的设置是为了执行C语言代码作好准备,通常我们可以把sp(Userstack)设置在上面所安排的那个1MBRAM空间的最顶端(堆栈向下生长)。此外,在设置堆栈指针sp之前,也可以关闭LED灯,以提示我们准备跳转到Stage2。

3.1.4跳转到Stage2的C入口点。

在上述一切都就绪后,就可以跳转到Bootloader的Stage2去执行了。比如通过修改PC寄存器为合适的地址来实现。

3.2Bootloader的Stage2通常依次执行以下主要步骤:

3.2.1进入Stage2的入口程序

Stage2的代码通常用C语言来实现,但是与普通C语言应用程序不同的是,在编译和链接bootloader这样的程序时,我们无法使用glibc库中的任何支持函数。这就需要我们利用汇编语言写一段trampoline(弹簧床)小程序,并将这段trampoline小程序来作为Stage2可执行映象的执行入口点。然后我们可以在trampoline小程序中用CPU跳转指令跳入main()函数中去执行;而当main()函数返回时,CPU执行路径显然再次回到了trampoline程序。具体程序如下:

.text

.globl_trampoline

_trampoline:

blmain

b_trampoline

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

这通常包括:初始化至少一个串口,以便和终端用户进行I/O输出信息;初始化计时器等。在初始化这些设备之前,也可以重新把LED灯点亮,以表明我们已经进入main()函数执行。设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。

3.2.3检测系统的内存映射(Memorymap)

所谓内存映射就是指在整个4GB物理地址空间中有哪些地址范围被分配用来寻址系统的RAM单元。CPU通常预留出一段足够大的地址空间给系统RAM,但是在搭建具体的嵌入式系统时不一定会实现CPU预留的全部RAM地址空间。而是往往只把CPU预留的全部RAM地址空间中的一部分映射到RAM单元上,而让剩下的那部分预留RAM地址空间处于未使用状态。如S3C2410使用的RAM空间仅为0x30000000-0x33ffffff。

3.2.4加载内核映像和根文件系统映像并从Flash上拷贝

规划内存占用的布局。这里包括两个方面:(1)内核映像所占用的内存范围;(2)根文件系统所占用的内存范围。对于内核映像,一般将其拷贝到从(MEM_START+0x8000)这个基地址开始的大约1MB大小的内存范围内(嵌入式Linux的内核一般都不超过1MB)。从MEM_START到MEM_START+0x8000这段32KB大小的内存之所以被空出,是因为Linux内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。而对于根文件系统映像,则一般将其拷贝到MEM_START+0x0010,0000开始的地方。若采用Ramdisk作为根文件系统映像,则其解压后的大小一般为1MB。

由于像ARM这样的嵌入式CPU通常都是在统一的内存地址空间中寻址Flash等固态存储设备,因此从Flash上读取数据与从RAM单元中读取数据并没有什么不同。用一个简单的循环就可以完成从Flash设备上拷贝映像的工作。如:

count=kernelsize

while(count》0){

*dest++=*src++;/*theyareallalignedwithwordboundary*/

count-=4;/*bytenumber*/

};

3.2.5设置内核的启动参数

将内核映像和根文件系统映像拷贝到RAM空间后,设置Linux内核的启动参数,如ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等,然后就可以准备启动Linux内核了。

3.2.6调用内核。

Bootloader调用Linux内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到MEM_START+0x8000地址处。此时,还需要设置CPU寄存器、CPU模式、Cache和MMU。

3.结束语

Bootloader是依赖于硬件而实现的,每个目标板的硬件配置都不完全一样,因此Bootloader程序也都不会完全一样。本文以Samsung公司的的S3C2410开发板为平台,阐述了Bootloader运行的主要步骤和关键技术,为实现引导Linux操作系统内核运行提供合适的环境。文章后面关于Linux内核启动参数的具体设置及如何调用内核是和具体操作系统密切关系的,未做详细论述。此外,设计与实现一个优秀的Bootloader程序是一个庞大复杂的过程,在程序中如能多利用LED和串口输出信息会是帮助我们调试的好方法。

责任编辑:gt

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分