本文转载:http://blog.ednchina.com/hhuwxf/1915416/message.aspx
一、Bootloader的引入
从之前的硬件实验可以看出,系统上电后,需要一个程序来初始化:关闭WATCHDOG、改变系统时钟、初始存储控制器、将更多代码复制到中等内存等。如果能将操作系统内核(无论是本地的,比如Flash;还是从远端,例如,通过网络复制到内存中,称为此程序Bootloader。
简单地说,Bootloader这么短的程序,它在系统上电时就开始执行,初始化硬件设备,准备好软件环境,最后调用操作系统内核。
可以增强Bootloader比如增加网络功能,从PC通过串口或网络下载文件,烧写文件,将Flash上压缩的文件解压后再运行等──这是一个更强大的功能Bootloader,也称为Monitor。事实上,用户在最终产品中不需要这些功能,只是为了方便开发。
Bootloader在嵌入式系统中,硬件配置非常不同,即使是相同的CPU,它的外设(例如Flash)也可能不同,所以不可能有一个Bootloader支持所有的CPU、所有电路板。甚至支持CPU架构比较多的U-Boot,也不是一拿来就可以使用的(除非里面的配置刚好与你的板子相同),需要进行一些移植。
二、 Bootloader的启动方式
CPU上电后,将从某个地址执行。MIPS结构的CPU会从0xBFC00000取第一条指令,而ARM结构的CPU则从地址0x从一万开始。嵌入式单板需要存储器件ROM或Flash等待映射到这个地方址,Bootloader将其存储在此地址的开头,以便一上电就能执行。
在开发过程中,通常需要使用各种命令Bootloader,一般通过串口连接PC和开发板,可以在串口上输入各种命令,观察操作结果等。这对开发人员来说只是有意义的,用户在使用产品时不需要接口来控制Bootloader是的。从这个角度来看看,Bootloader可分为两种操作模式(Operation Mode):
(1)启动加载(Boot loading)模式。
上电后,Bootloader操作系统从板上的固态存储设备加载到RAM在运行过程中,用户没有干预整个过程。产品发布时,Bootloader在这种模式下工作。
(2)下载(Downloading)模式。
在这种模式下,开发人员可以通过串口连接或网络连接从主机使用各种命令(Host)下载文件(如内核图像、文件系统图像),直接放入内存操作或烧入Flash类固态存储设备。
板与主机之间传输文件时,可使用串口xmodem/ymodem/zmodem协议使用简单,但速度慢;也可以通过网络使用网络tftp、nfs协议传输时,主机应打开tftp、nfs服务;还有其他方法,比如USB等。
像Blob或U-Boot等等功能强大Bootloader这两种工作模式通常同时支持允许用户在这两种工作模式之间切换。U-Boot启动时正常启动加载模式,但会延迟几秒钟(可以设置)等待终端用户按下任意键而将U-Boot切换到下载模式。如果在指定时间内没有用户按钮,则U-Boot继续启动Linux内核。编辑] 15.1.2 Bootloader结构及启动过程
- 1. 概述
移植前先了解Bootloader一些通用概念有助于理解它的代码。
嵌入式Linux从软件的角度来看,系统通常可以分为四个层次:
(1)引导加载程序,包括固化固件(firmware)中的 boot 代码(可选)和Bootloader两大部分。
有些CPU在运行Bootloader固化程序(固件,firmware),比如x86结构的CPU就是先运行BIOS硬盘的第一个分区在运行之前是固件(MBR)中的Bootloader。
大多数嵌入式系统中没有固件,Bootloader是上电后执行的第一个程序。
(2)Linux内核。
嵌入式板的定制内核和内核的启动参数。内核的启动参数可以是默认的,也可以是默认的Bootloader传递给它的。
(3)文件系统。
包括根文件系统和根文件系统Flash内存设备上的文件系统。Linux系统可以操作所需的应用程序、库等,如为用户提供操作Linux控制界面shell动态连接程序运行所需的程序glibc或uClibc库,等等。
(4)用户应用程序。
特定于用户的应用程序也存储在文件系统中。有时,嵌入式图形用户界面可能包含在用户应用程序和核心层之间。常用的嵌入式 GUI 有:Qtopia 和 MiniGUI 等。
显然,嵌入系统的固态存储设备有相应的分区存储,图15.一是典型的分区结构。[[Image:]]
图15.1 嵌入式Linux典型的分区结构在系统中
“Bootparameters一些可设置的参数存储在分区中,例如IP要传递给核心的地址、串口波特率、命令行参数等。正常启动时,Bootloader首先运行,然后将内核复制到内存中(一些内核可以直接在固态存储设备上运行),并在内存的固定地址设置传输到内核的参数,最后运行内核。内核启动移动后,它会挂接(mount)根文件系统(Root filesystem)启动文件系统中的应用程序。
- 2. Bootloader的两个阶段
Bootloader启动过程的启动过程可分为单阶段(SingleStage)、多阶段(Multi-Stage)两种。通常是多阶段的Bootloader它可以提供更复杂的功能和更好的可移植性。启动固态存储设备动的Bootloader大部分是 2 阶段的启动过程。这可以从之前的硬件实验中很好地理解:第一阶段是通过汇编来实现的,这取决于 CPU系统结构的初始化,并在第二阶段调用代码。第二阶段通常使用C语言可以实现更复杂的功能,代码具有更好的可读性和可移植性。
一般来说,这两个阶段的功能可以分类如下,但这不是绝对的:
(1)Bootloader功能的第一阶段。
-
- 硬件设备的初始化。
- 为加载Bootloader代码准备的第二阶段RAM空间。
- 拷贝Bootloader代码的第二阶段是 RAM 空间中。
- 设置好栈。
- 跳转到代码的第二阶段C入口点。
硬件初始化的第一阶段一般包括:关闭WATCHDOG、关中断,设置CPU速度和时钟频率,RAM初始化等。这些都不是必须的,比如S3C2410/S3C2440开发板使用U-Boot中,就将CPU在第二阶段设置速度和时钟频率。
甚至在第二阶段复制代码RAM空间不是必要的,对NOR Flash等待存储设备,可以直接在上面执行代码,但与此相比RAM执行效率大大降低。
(2)Bootloader功能的第二阶段。
-
- 本阶段应初始化使用的硬件设备。
- 内存映射检测系统(memory map)。
- 内核图像和根文件系统图像Flash上读到RAM空间中。
- 为内核设置启动参数。
- 调用内核。
为了方便开发,程序员和Bootloader进行交互。
所谓检测内存映射,就是确定板上使用了多少内存,它们的地址空间是什么。由于嵌入式开发,Bootloader它主要是为某种板编写的,因此可以根据板的情况直接设置,不需要考虑适用于各种情况的复杂算法。
Flash上面的内核图像可能被压缩并读取RAM之后需要解压。当然,对于具有自解压功能的核心,不需要Bootloader来解压。
复制根文件系统的图像RAM这是不必要的。这取决于根文件系统的类型,以及内核访问它的方法。
下一节将介绍内核设置的启动参数。
将内核存放在适当的位置后,直接跳到其入口点调用内核。调用内核前,应满足以下条件:
(1)CPU 设置寄存器。
-
- R0=0
- R1=机器类型ID;对于ARM结构的CPU,其机器类型ID可以参见 linux/arch/arm/tools/mach-types。
- R2=启动参数标记列表在 RAM 中起始基地址
(2)CPU工作模式。
-
- 禁止中断(IRQs和FIQs)
- CPU 必须 SVC 模式
(3)Cache 和 MMU 的设置。
-
- MMU 必须关闭
- 指令 Cache 可以打开也可以关闭
- 数据 Cache 必须关闭
如果用C语言,可以像下列示例代码一样来调用内核:
void (*theKernel)(int zero, int arch, u32 params_addr) = (void (*)(int, int, u32))KERNEL_RAM_BASE; …… theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);
- 3. Bootloader与内核的交互
Bootloader与内核的交互是单向的,Bootloader将各类参数传给内核。由于它们不能同时运行,传递办法只有一个:Bootloader将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获得参数。
除了约定好参数存放的地址外,还要规定参数的结构。Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以标记ATAG_CORE 开始,以标记ATAG_NONE 结束。标记的数据结构为tag,它由一个tag_header结构和一个联合(union)组成。tag_header结构表示标记的类型及长度,比如是 表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合(union),比如表示内存时使用tag_mem32,表示命令行时使用 tag_cmdline。数据结构tag和tag_header定义在Linux内核源码的include/asm/setup.h头文件中:
1
2
3
4
5
6
7
8
|
struct
tag_header { u32 size; u32 tag; };
<br>
struct
tag {
struct
tag_header hdr;
union
{
struct
tag_corecore;
struct
tag_mem32mem;
struct
tag_videotextvideotext;
struct
tag_ramdiskramdisk;
struct
tag_initrdinitrd;
struct
tag_serialnrserialnr;
struct
tag_revisionrevision;
struct
tag_videolfbvideolfb;
struct
tag_cmdlinecmdline; <br>
/* * Acorn
specific */
struct
tag_acornacorn; <br>
/* * DC21285 specific
*/
struct
tag_memclkmemclk; } u; };
|
下面以设置内存标记、命令行标记为例说明参数的传递:
(1)设置标记 ATAG_CORE。
标记列表以标记 ATAG_CORE开始,假设Bootloader与内核约定的参数存放地址为0x30000100,则可以以如下代码设置标记 ATAG_CORE:
params = (struct tag *) 0x30000100; <br>params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size (tag_core); <br>params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; <br>params = tag_next (params);
其中,tag_next定义如下,它指向当前标记的末尾:
#define tag_next(t)((struct tag *)((u32 *)(t) + (t)->hdr.size))
(2)设置内存标记。
假设开发板使用的内存起始地址为0x30000000,大小为0x4000000,则内存标记可以如下设置:
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = 0x30000000;
params->u.mem.size = 0x4000000;
params = tag_next (params);
(3)设置命令行标记。
命令行就是一个字符串,它被用来控制内核的一些行为。比如"root=/dev /mtdblock2 init=/linuxrc console=ttySAC0"表示根文件系统在MTD2分区上,系统启动后执行的第一个程序为/linuxrc,控制台为ttySAC0(即第一个串 口)。
命令行可以在Bootloader中通过命令设置好,然后如下构造标记传给内核:
char *p = "root=/dev/mtdblock2 init=/linuxrc console=ttySAC0";
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
(4)设置标记ATAG_NONE。
标记列表以标记ATAG_NONE结束,如下设置:
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
常用Bootloader介绍
现在Bootloader种类繁多,比如x86上有LILO、GRUB等。对于ARM架构的CPU,有U-Boot、Vivi等。它们各有特点,下面列出Linux的开放源代码的Bootloader及其支持的体系架构,如表15.1所示。
开放源码的Linux引导程序
<table fck__showtableborders?="">
Bootloader Monitor 描述 X86 ARM PowerPC
LILO 否 Linux磁盘引导程序 是 否 否
GRUB 否 GNU的LILO替代程序 是 否 否
Loadlin 否 从DOS引导Linux 是 否 否
ROLO 否 从ROM引导Linux而不需要BIOS 是 否 否
Etherboot 否 通过以太网卡启动Linux系统的固件 是 否 �