南京邮电大学通信学院余雪勇yuxy@njupt.edu.cn(1)bootloader程序一般来说,bootloader都分为主机端(host)和目标端(target)两个部分。目标端嵌入目标系统中,在启动之后就一直等待和主机端的bootloader程序之间的通信连接。目标端程序需要使用交叉编辑器编译,主机端的使用本地编译器编译。在主机端和目标端之间的通信方式没有规定,一般由bootloader程序自己规定。但有些bootloader并不需要提供服务端程序,而是使用标准的终端程序作为主机端的连接程序,可以使用Linux下的minicom、kermit或者Windows下的超级终端作为主机端程序。一般bootloader提供给用户一个交互shell,通过交互式完成自由主机控制目标板的过程,bootloader可以存放在flash中,也可以下载到RAM中,以bootmem方式启动。CPU总是从一个位置开始启动,bootloader被CPU运行,并为操作系统的运行做准备,一般来说,bootloader的作用有如下几个。①初始化处理器。bootloader会初始化处理器中的一些配置寄存器,如ARM720T体系结构的CPU如果需要使用MMU,就应当在bootloader中进行初始化。②初始化必备的硬件。使用bootloader初始化板上的必备硬件,如内存、终端控制器等初始化就是通过它完成的;用于从主机下载系统映像到硬件板上的设备也是由它完成初始化的。例如,有些硬件板使用以太网传输嵌入式系统映像文件,在bootloader中会使用以太网驱动程序初始化硬件,随后与主机端的程序通信,并完成下载工作。③下载系统映像。系统映像下载只能由bootloader提供。因为CPU提供的代码无法完成大系统映像的下载工作,而bootloader下载可以很多的自由度,可以指定内核映像和文件系统映像的下载位置。在目标端的bootloader程序中提供了接收映像的服务端程序,而在主机端的程序提供了发送数据包动作——可以通过串口,也可以通过以太网等其他方式发送。发送系统映像结束之后,如果硬件允许,bootloader还可以提供命令将下载成功的映像写入到FlashROM中。一般bootloader都提供了擦写Flash的命令,为操作带来很大的便利。④初始化操作系统并准备运行。使用bootloader可以启动已经下载好的操作系统。可以指定bootloader在RAM中或者Flash中启动操作系统,也可以指定具体的启动地址。(2)嵌入式系统内核对于使用操作系统的嵌入式系统而言,操作系统一般是以内核映像的形式下载到目标系统中。以Linux为例子,在系统开发完成之后,将整个操作系统部分做成压缩或者没有压缩过的内核映像文件,与文件系统一起传送到目标系统中。通过bootloader指定地址运行Linux内核,启动嵌入式Linux系统;然后再通过操作系统解开文件系统,运行应用程序。在内核中通常必须的部件是进程管理、进程间通信、内存管理部分,其他部件,如文件系统、驱动程序、网络协议等,都可以配置,并以相关的方式实现。(3)根文件系统在嵌入式系统中的“硬盘”概念一般都以ramdisk的方式实现。因为Falsh在断电后还能继续保存数据的设备,但其价格相对昂贵;然而系统中又无法使用像硬盘这样的大型设备,因此,需要长久使用的文件系统数据,尤其是应用程序的可执行文件、运行库等,运行时都放在RAM中。常用的方式就是从RAM中划分出一块内存虚拟成“硬盘”,对它的操作与对永久存储器操作一样。在Linux中就存在这样的设备,称为ramdisk,一般使用的设备文件是/dev/ram0。ramdisk的启动需要操作系统的支持。bootloader负责将ramdisk下载到与内核映像不冲突的位置,操作系统启动之后会自动寻找ramdisk所在的位置,将ramdisk作为一种设备安装(mount)为根文件系统。当然,根文件系统不一定使用ramdisk实现,还可以用NFS方式通过网络安装根文件系统。这也是在系统内核中实现的。操作系统启动之后直接通过内核中NFS相关代码对处于网络上的NFS文件系统进行安装。文件系统启动的方式可以在内核代码中编写或者启动时通过参数指定。(4)重定位和下载生成了目标平台需要的image文件之后,就可以通过相应的工具与目标板上的bootloader程序进行通信。可以使用bootloader提供的,或者通用的终端工具与目标板相连接。一般在目标板上使用串口,通过主机终端工具与目标板通信。bootloader中提供下载等控制命令,完成嵌入式系统正式在目标板上运行之前对目标板的控制任务。bootloader指定image文件下载的位置。在下载结束之后,使用bootloader提供的运行命令,从指定地址开始运行嵌入式系统软件。这样,一个完成的嵌入式系统软件开始运行了。(5)Linux内核源代码中的汇编语言代码任何一个用高级语言编写的操作系统,其内核代码中总有少部分代码时用汇编语言编写的。其中大部分是关于终端与异常处理的底层程序,还有一些与初始化有关的程序以及一些核心代码中调用的公用子程序。用汇编语言编写核心代码中的部分代码出于以下几个方面的考虑。操作系统内核中的底层程序直接与硬件打交道,需要用到一些专用的指令,而这些指令在C语言中并无相对应的语言成分。因此,这些底层的操作需要用汇编语言来编写。CPU中的一些对寄存器的操作也是一样,如要设置一个段寄存器时,也只好用汇编语言来编写。CPU中的一些特殊指定也没有相对应的C语言成分,如关中断、开中断等。此外,在同一体系系统的不同CPU芯片中,特别是新开发出来的芯片中,往往会增加一些新的指令,对这些指令的使用也得用汇编语言。内核中实现某些操作的过程、程序段或函数,在运行时会非常频繁地被调用,因此,其时间效率就显得很重要。而用汇编语言编写的程序,在算法和数据结构相同的条件下,常比使用高级语言编写的效率要高。在此类程序或程序段中,往往每一条汇编指令的使用都需要经过推敲。系统调用的进入和返回就是一个典型例子,系统调用的进出时非常频繁用到的过程,每秒钟可能会用到成千上万次,其时间效率可谓举足轻重。再者,系统调用的进出过程还牵涉到用户空间和系统空间之间的来回切换,而用于这个目的的一些指令在C语言中本来就没有对应的语言成分,所以系统调用的进入和返回显然必须用汇编语言来编写。在某些特殊的场合,一段程序的空间效率也会显得非常重要。操作系统的引导程序就是一个例子。系统的引导程序通常一定要能容纳在特定的区域中。此时,这段程序的大小多出一个字节也不允许,所以一般使用汇编语言编写。Linux的特点使得它成为适合嵌入式开发和应用的操作系统。对于使用操作系统的嵌入式系统而言,操作系统一般是以内核映像的形式与文件系统一起下载到目标系统中。一个最基本的嵌入式Linux系统从软件的角度可以分为4个层次。①导加载程序bootloader。②Linux内核。③文件系统。④用户应用程序。(1)嵌入式Linux引导过程嵌入式Linux系统的引导过程如下。①理器重新启动后,首先执行启动代码以初始化内存控制器以及片上设备,然后配置存储映射。②Bootloader把内核从Flash等固态存储设备加载到RAM,然后跳转到内核的第1条指令处执行。③内核首先配置微处理器的寄存器,然后调用start-kernel,它是与微处理器体系结构无关的开始点。④内核初始化高速缓存和各种硬件设备。⑤内核挂载根文件系统。⑥内核执行init进程。⑦init运行时共享库。⑧init读取其配置文件。⑨init最后进入用户会话阶段。在此过程中bootloader的作用是非常重要的。(2)嵌入式系统中bootloader的作用嵌入式系统中首先要考虑的是启动问题,当一个微处理器最初启动时,它首先执行一个预定地址处的指令,这个地址处存放系统初始化或引导程序,正如PC中的BIOS。在嵌入式系统中由于没有BIOS,因此将由引导加载程序bootloader实现类似的功能。bootloader代码量虽少,但是其作用却非常重要,而且许多代码与处理器体系结构相关而不具备移植性,因此研究相关技术从而写出针对特定处理器的启动代码对嵌入式系统设计尤为重要。嵌入式Linux的bootloader主要作用如下。●初始化处理器;●初始化的必备的硬件;●下载系统映像;●初始化操作系统;●启动已下载的操作系统。总之bootloader负责完成系统的初始化,把操作系统内核映像加载到RAM中,然后跳转到内核的入口点去运行。(3)bootloader的操作模式bootloader一般要实现两种操作模式:自举和内核启动模式。自举模式的主要作用是目标机通过串口与主机通信,可以接收主机发送过来的映像文件,并将其固化在目标机的Flash等固态存储设备中,也可以将Flash中的映像文件上传到主机。内核启动模式允许嵌入式系统加电启动后由bootloader从目标机上的Flash等固态存储设备上将操作系统加载到RAM中运行。一般嵌入式系统中bootloader应能实现这两种模式的切换。(4)bootloader的基本框架结构大部分的bootloader依赖于CPU的体系结构和嵌入式板级设备的配置,因此,bootloader可分为两部分:用汇编语言实现的依赖于CPU体系结构的代码stage1和用C语言实现的代码stage2。①stage1的操作。a.硬件的初始化,包括以下步骤。●屏蔽所有中断;●设置CPU的速度和时钟频率。●初始化RAM。●初始化LED或UART。●为加载stage2准备RAM空间。b.加载stage2到RAM中。c.设置堆栈指针SP,为执行C语言代码做准备。d.加载到stage2的C语言入口点。②stage2的基本操作。●初始化本阶段要用到的硬件设备。●检测系统的内存映射。将存储在Flash上的内核映像读到预留的RAM空间之前,先确定这些预留的RAM空间哪些被真正映射到了RAM地址单元。●加载内核映像和根文件系统映像。●设置内核启动参数。Linux2.4.x以后的内核都期望以标记列表的形式传递参数,在嵌入式Linux系统中,通常由bootloader设置的启动参数有ATAG-CORE,ATAG-MEM,ATAG-RAMDISK,ATAG-1NITRD等。●调用内核,即直接跳转到内核的第1条指令地址处执行。基本的启动流程主要分成4个部分。(1)在系统加电检测结束以后,由BIOS中的代码负责把引导器加载进入机器的内存中,控制权交给引导器。这里又要分成两个部分,因为BIOS只负责装入512个字节的程序,然后把控制权交给这512字节。一般的引导器大小超过512字节,其余部分再由这512字节的代码负责装入。图5-22Linux系统启动流程图(2)引导器负责确定Linux内核的位置,把Linux内核加载进入内存中;同时,确定根文件系统的位置,将根文件系统的镜像加载进入内存中。然后在加载内核的时候给内核传入一些启动参数,用于控制内核执行过程中的一些行为,接下来将控制权交给内核。(3)内核接管控制权以后,首先解压缩自己,检测设备,加载内部模块。然后根据启动参数挂载根文件系统。挂载完根文件系统后内核启动的第1个进程是init,默认的位置为“/sbin/init”。如果找不到这个可执行文件,就转而启动“/bin/sh”,提供给用户一个人机交互的界面。(4)init进程启动后查找的第1个配置文件是“/etc/inittab”,这个文件控制init的行动。一般init会首先指定启动等级,然后执行“/etc/rc.d/rc.sysinit”,同时rc.sysinit−启动脚本启动系统服务进程(如update、syslogd等)、网络和必要的环境变量设置。最后inittab会指定init进程去调用getty打开多个终端控制台,