在嵌入式系统当中你可能经常听到 boot loader(引导加载器)这一词,boot loader是指什么呢?在我们日常经常接触的东西中是不是有与boot loader的作用或是概念相似的呢?有一点我一定猜得到,你现在正在用计算机看这篇文章。如果你稍微熟悉计算机的组成,你一定知道BIOS(Basic Input/Output System)。BIOS在计算机中就是用来启动计算系统的,在完成一定的硬件初始化工作以及人机交互后,它加载位于硬盘中的操作系统,并最终运行操作系统。嵌入式系统中的boot loader与BIOS的作用就是相类似的,也是完成对于处理器相关的硬件资源进行初始化后,最后加载通常是存放在FLASH中的应用程序,当然在嵌入式系统当中操作系统与应用程序一般是在同一个可执行文件中的,这与我们的电脑有很大的不同。
BIOS与boot loader有相同之处,比如:
1)完成处理器正常最小系统的初始化。最小系统的概念通过举个例比较容易让人明白,比如对于我们的计算机,通常在初始化时不需要用到以太网,因此,在最小化系统中就不包括以太网卡的初始化,对其的初始化完全可以放到操作系统中去做,而不是在BIOS中。相类似的是,在嵌入式系统中,我们通常不需要在初始化的过程中用到USB设备,那么对于USB设备的初始化就不需要包含在最小系统中,而可以在被boot loader加载的应用程序中去初始化它。
2)两者在最后都得加载其它的程序,并将运行权交给被加载的程序。对于BIOS,其所加载的程序通常是操作系统,当然,如果你正在安装系统那么BIOS所加载的可能是位于光盘中安装程序。对于loader,其加载的是一个可执行程序,这一可执行程序包括实时操作系统(有的也不是实时操作系统)和我们的应用程序。
3)如果BIOS或是boot loader程序太大,通常都会采用压缩技术对其进行压缩。对于BIOS,其肯定会采用压缩技术进行压缩,因为BIOS随着计算机行业的飞速发展而越来越复杂,比如,以前的BIOS不需要支持USB的鼠标和键盘,但现在就得支持了,这就意味着BIOS要有USB鼠标和键盘的驱动程序。另一方面计算机的出货量大,所以BIOS芯片(其实就是一块FLASH芯片)的容量也就会尽可能的控制得小以提高利润,比如可能控制其容量在512K字节。同BIOS相类似的是,如果我们的嵌入式系统中的FLASH紧张时,为了节省FLASH空间,我们也得对boot loader采用压缩技术。可能有人要问,BIOS也好,boot loader也好,如果采用了压缩技术对其压缩,那是谁负责在运行它们时对其解压缩呢?答案是它们自己,很有趣吧!其实,我们说对它们采用压缩技术并不是对于全部的程序(或数据)都采用压缩技术。在它们当中,一定存在一部分不压缩的部分,这保证处理器启动时,能直接运行这部分的代码。技巧在于,没有压缩的代码当中包括了解压程序(从C程序角度来看是几个函数),其将被压缩了的部分解压到内存中。当然,其中的程序处理很有技巧性,要保证程序在运行的过程中,对于压缩部分代码的运行是透明的,这在具体的代码分析中我们再来看。
BIOS与boot loader除了相同之处外,还有不同之处,它们是:
1)BIOS往往比boot loader复杂很多,因为我们的计算机硬件环境比嵌入式系统复杂得多。在计算机行业,为了保证操作系统在设计时尽可能运行在不同的主板上,这需要BIOS的帮助以屏蔽一些硬件信息。操作系统对主板上的一些硬件资源的访问,需要通过BIOS来完成,即调用BIOS的功能(或说是函数)来完成。这样做的好处是非常的明显的,操作系统的开发商不用关心主板上的具体硬件是如何设计的,而主板的设计厂商则负责实现BIOS来对主板上的硬件资源进行存取访问。总的来说,就是将主板上对于复杂硬件资源的处理任务交给了主板厂商来完成,而不用操作系统厂商去关心,因当说这样做非常的合理和有效。
2)BIOS在加载完了操作系统以后,其还将驻留在内存中,以便操作系统在运行的过程中调用其功能,但boot loader就不需要这样了。通常,boot loader加载完了程序后,跳转到被加载的程序后就不再存在了,这里所说的跳转有时也称之为将运行权交给了被加载程序。
3)boot loader通常采用汇编和C程序相结合进行编写,但BIOS为了节省程序空间往往全部采用汇编进行编写。
下面我们来看一看boot loader在启动过程中会做哪些事情。在讨论这些之前,我们需要明白嵌入式系统的处理器与我们计算机处理器的区别是什么。对于嵌入式系统的处理器,准确的说应当叫微控制器,即英文的microcontroller,而我们的计算机处理器则应称之为微处理器,即microprocessor。微控制器除了具有微处理器的功能外(即通过运算来处理一定的事务),往往其中集成了很多的其它硬件功能模块,比如SDRAM内存控制器、I2C控制器、SD卡控制器等等。正如微处理器的名字那样,它只有处理功能,其它的控制芯片都是通过芯片组(什么是芯片组,我打算后面用另一篇文章来进行解释)来完成的。虽然,我们对于微处理器和微控制器采用了处理器这么一个简化词来描述,但我们需要明白其中的不同之处,这一点非常的重要。下面我们来看一看boot loader做些什么事情,它们有:
1)对PLL时钟进行初始化。往往处理器一启动时,为了更好的设备兼容性,其工作频率都很低,在boot loader程序的特定位置,需要提高处理器的时钟频率,以加快运行速度。速度一旦调好往往是不会改变的了,之所以说是往往,因为如果处理器支持节电模式功能,那也会造成PLL时钟的变化(时钟越快越耗电)。
2)初始化SDRAM内存控制器。通常loader自身也需要用到内存,比如,大多boot loader都会将自己加载到内存中。内存的配置一般是包括行地址和列地址的配置以及自动刷新频率的配置。一旦配置好了,后面就不用再更改了。
3)初始化中断控制器和中断服务程序。
4)初始化各地址空间的片选地址寄存器和读写时序。
5)初始化堆栈(stack)寄存器。比如,在x86中需要初始化ESP寄存器,在PowerPC中需要初始化r1寄存器。
6)对于boot loader中需要访问的其它硬件设备进行初始化。比如,我们通常会有一个串口作为控制台(console),这就需要在boot loader中初始化相应的串口,并接受用户的命令,以响应用户的请求。可以想像,boot loader中存在一定的命令处理程序。
7)将boot loader自身加载到内存中,如果需要解压,那么还得做解压操作。前面提到了,将boot loader加载到内存是为了更快的运行程序。
8)加载需要运行的应用程序并最终运行被加载的应用程序。
需要指出的是,在嵌入式系统中我们的应用程序当中还得做一部分与boot loader相同的工作。比如,对于中断服务程序的重新初始化,因为在boot loader中设置的是boot loader中的程序作为中断服务程序,当应用程序加载了以后,boot loader中的程序就不复存在了,因此,我们需要重新初始化中断服务程序以指向被加载程序中的函数。对于SDRAM内存的初始化、PLL的初始化,在应用程序中通常不需要重新做。
虽然,在此我们没有对具体的处理器和操作系统进行讲解,但是无论对于什么处理器和操作系统,其总体过程几乎相同,只是处理器的指令集不同。最后,我要给读者您留一个问题,这个问题如下。
什么是Boot Loader(转载)
我们说boot loader通常采用汇编和C语言相结合来编写的,那能不能全部用C语言来进行编写呢?为什么?
对于这一问题的答案是:不能。C程序中所有的代码都是以函数的形式出现的。可能有人要说在C中也可以嵌入汇编代码,能不能用这种方式来实现整个boot loader呢?再看看题目,这里只考虑所有的代码只能是以函数的形式出现。那C程序中函数调用要一个什么样的环境呢?堆栈!我们写C程序时在函数名的后面,就是用花括号将代码括起来的,开始的花括号其实可以理解为有一段汇编代码(具体细节以后会有文章进行解释)对堆栈进行操作。那堆栈从哪来呢?显然,堆栈是一块内存区,也就是说我们用C程序写代码之前,必须保证内存已经初始化好了、可以用了。回忆一下,我们在上文中提到,SDRAM内存芯片的初始化是boot loader中很重要的一步。也就是说,我们必须保证初始化好了SDRAM内存芯片后,才能进行C函数调用。因此,完全用C语言来实现boot loader是不可行的。