解析嵌入式系统的Boot Loader
扫描二维码
随时随地手机看文章
由Boot Loader和固化在固件(firmware)中的Boot代码(可选)共同组成一个嵌入式系统的引导加载程序。嵌入式系统是“控制、监视或者辅助装置、机器和设备运行的装置”(devices used to control, monitor, or assist the operation of equipment, machinery or plants)。从中可以看出嵌入式系统是软件和硬件的综合体,还可以涵盖机械等附属装置。目前国内一个普遍被认同的定义是:以应用为中心、以计算机技术为基础、软件硬件可裁剪、适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。嵌入式系统是以应用为中心,以计算机技术为基础,软硬件可裁剪,适用于应用系统,对功能、对可靠性、成本、体积、功耗有严格要求的专用计算机系统。
1 Boot Loader分析
系统加电,然后复位后,基本上所有的CPU都是从复位地址上取得指令的。以微处理器为核心的嵌入式系统中,通常都有某种类型的固态存储设备(FLASH,E2PROM等),这个固态存储设备被映射到一个预先设置好的地址上。在系统加电复位后,一开始处理器就会去执行存放在复位地址处的程序,而且通过开发环境可以将Boot Loader定位在复位地址一开始的存储空间上,因此Boot Loader是系统加电后,在操作系统内核或者一些应用程序被运行之前,首先会运行的程序。嵌入式系统必须根据应用需求对软硬件进行裁剪,满足应用系统的功能、可靠性、成本、体积等要求。所以,如果能建立相对通用的软硬件基础,然后在其上开发出适应各种需要的系统,是一个比较好的发展模式。目前的嵌入式系统的核心往往是一个只有几K到几十K微内核,需要根据实际的使用进行功能扩展或者裁减,但是由于微内核的存在,使得这种扩展能够非常顺利的进行。
微处理器用一片或少数几片大规模集成电路组成的中央处理器。这些电路执行控制部件和算术逻辑部件的功能。微处理器与传统的中央处理器相比,具有体积小,重量轻和容易模块化等优点。微处理器的基本组成部分有:寄存器堆、运算器、时序控制电路,以及数据和地址总线。微处理器能完成取指令、执行指令,以及与外界存储器和逻辑部件交换信息等操作,是微型计算机的运算控制部分。它可与存储器和外围电路芯片组成微型计算机。
以微处理器为核心的嵌入式系统中,一般都有某种类型的固态存储设备(FLASH,E2PROM等),这个固态存储设备被映射到一个预先设置好的地址上。在系统加电复位后,一开始处理器就会去执行存放在复位地址处的程序。而且通过开发环境可以将Boot Loader定位在复位地址一开始的存储空间上,Linux是一类Unix计算机操作系统的统称。Linux操作系统的内核的名字也是“Linux”。Linux操作系统也是自由软件和开放源代码发展中最着名的例子。严格来讲,Linux这个词本身只表示Linux内核,但在实际上人们已经习惯了用Linux来形容整个基于Linux内核,并且使用GNU 工程各种工具和数据库的操作系统。Linux得名于计算机业余爱好者Linus Torvalds。它的主要任务有以下7个方面。
(1)初始化处理器及外设的硬件资源配置。一般嵌入式系统的处理器在上电复位后,外部的I/O引脚都处于输入状态,处理器的片内和片外设备资源都需要配置。
(2)建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的环境,这样就能为最终启动操作系统的内核提供最好条件。
(3)把操作装载到映射的内存中,这也是所有任务当中最重要的一个,只有完成这个任务,操作系统才能被装载到内存当中去,Boot Loader一般会提供串口和网络装载两种方式。
(4)为了把操作系统的映像保存在FLASH中,以便以后启动,可以直接装载FLASH的数据,而不用重新下载程序,但需要对FLASH进行编程。
(5)运行操作系统。设置相关的寄存器和资源,跳转到操作系统的所在空间,进行相关的引导,这就是Boot Loader。
(6)在Linux系统启动时,传递系统的启动参数,可以给内核传递命令行等参数,通过命令行可以选择控制系统的启动模式。
(7)命令行的解析和输入/输出控制。为了开发的方便,多数的Boot Loader都采用串口作为终端的控制方式。
Boot Loader的启动过程可分为两个重要阶段。第一阶段:由于Boot Loader的实现依赖于CPU的体系结构,所以设备代码的初始化等功能都在该阶段完成。而且,为了达到缩短代码的目的,通常用汇编语言来编写。
①硬件设备的初始化。在该阶段的执行过程中,首先需要对硬件设备进行初始化,其目的主要是为第二阶段的执行以及随后Kernel的调用准备基本的硬件环境。
②为加载Boot Loader的第二阶段准备RAM空间。为了获得更快的执行速度,通常把第二阶段加载到RAM空间中来执行。因此,必须为加载Boot Loader准备好一段可用的RAM空间范围。
③设置堆栈指针。设置堆栈是为了执行C语言代码作好准备。
④跳转到第二阶段的C入口点。当程序执行到这个位置时,可以通过修改PC寄存器的值,使其跳转到第二阶段。
第二阶段阶段的启动流程分析:为了便于实现复杂的功能和获得更好的代码可读性和可移植性,通常第二阶段的代码用C语言来实现。但是,与普通C语言的不同之处是,这里使用了“弹簧床”的概念,即先用汇编语言写一段小程序,并将这段小程序作为第二阶段可执行映像的执行入口点,然后在汇编程序中用CPU跳转指令跳入main()函数中去执行,当main()函数返回时,CPU执行路径再次返回到汇编程序中第二阶段,包括初始化本阶段要使用的硬件设备,检测系统内存映射,会将Kernel映像和根文件系统映像从FLASH中读到RAM空间中。
2 Boot Loader的设计
2.1 中断向量表(二级)的设计与建立
如果有中断或者异常发生时,处理器便会强制性地把PC指针指向向量表中它所对应的中断类型地址值。为了提高中断响应速度,FLASH的0x0地址存放能跳转到0x33FFFF00地址处中断向量的跳转指令,也就是会在在RAM中建立一个二级中断向量表,起始地址为0x33FFFF00。代码如下:
2.2 第二阶段拷贝到RAM
把第二阶段Stage2拷贝到RAM地址的最顶大小为1 MB的开始空间,RAM的起始地址为0x30000000。代码如下所示:
2.3 堆栈指针的设置
用户使用哪些中断决定了系统堆栈的初始化,以及系统需要处理的哪些错误类型。一般情况下,堆栈设置是必须,而且是由管理者自己设置的。如果需要使用IRQ中断,那么IRQ堆栈的设置也是必须的,下面是IRQ堆栈的设置:
3 Stage2的设计
3.1 可执行映像Stage2的入口
由于Glibc库支持的函数不能用于编译和链接Boot Loader这样用C语言编写的程序,因此把main()函数的起始地址作为第二阶段的入口点是最直接的想法。可以用汇编编写一段Trampoline小程序,用CPU跳转指令跳到main()函数去执行,当函数返回时会再次回到Trampoline程序,代码如下:
程序顺利时就不会再回到开始的Trampoline程序,不然就会回到最后的语句,系统就会重新启动。
3.2 内存影射
一般S3C2410上配置的SDRSAM大小为64 MB,该SDRAM的物理地址范围是Ox30000000~Ox33FFFFFF(属于Bank 6)。由Section的大小可知,该物理空间可被分成64个物理段。因为ARM体系结构中数据缓冲必须通过MMU开启,因此Boot Loader效率不是很高,但是MMU可以通过平板映射方式被开启,MMU是Memory Management Unit的缩写,中文名是内存管理单元,它是中央处理器(CPU)中用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权。映射关系代码如下:
3.3 装载内核映像和根文件系统映像
像ARM这样的嵌入式CPU通常都是在统一的内存地址空间中寻址FLASH等固态存储设备的,因此从Flash上读取数据与从RAM单元中读取数据一样,用一个简单的循环就可以完成从FLASH设备上拷贝映像的工作:其中count为根文件系统映像的大小或内核映像的大小。
3.4 内核的启动参数的设置
内核启动可以从NAND FLASH(NOR FLASH)中启动运行Linux,需要修改启动命令如下:
LCD启动参数一般都包括root,init和console。noinitrd不使用ramdisk。root根文件系统在MTD分区。Init内核运行入口命令文件。co-nsol内核信息控制台,ttys0表示串行口0;tty0表示虚拟终端。
4 结语
通过对Boot Loader的分析可以看出,设计一个性能优良的Boot Loader可以提高系统的稳定性及实时性,它是嵌入式开发中不可或缺的一部分。只有设计出一个稳定的Boot Loader,才能进行下一步的系统开发工作,直至完成整个嵌入式系统的开发。设计Boot Loader是一项很复杂的工作,需要对硬件资源和所用的操作系统有很深的理解。
QICK