bootloader从镜像启动linux,Bootloader的启动过程之stage2

(接前一博文)

Stage2的代码通常用C语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性效果。但是与普通C语言应用程序不同的是,在编译和链接bootloader这样的程序时,不能使用glib库中的任何支持函数。那么从哪里跳转进main()函数呢?

最直接的想法就是,直接把main()函数的起始地址作为整个stage2执行映像的入口点,但是这样处理有两个问题,一是无法通过main()函数传递函数参数,二是无法处理main()函数返回的情况。有一种更为巧妙的方法就是利用trampoline(弹簧床)的概念:用汇编语言写一段trampoline小程序,并将它来作为stage2可执行映像的执行入口点。也即在trampoline中用CPU跳转指令跳入main()函数中去执行,当main()函数返回时,CPU执行路径显然再次回到trampoline程序。简言之,就是用这段trampoline小程序作为main()函数的外部包裹(external wrapper)。

Trampoline程序的简单示例(来自Blob Bootloader)

.text

.globl _trampoline

_trampoline:

Bl main

b _trampoline

可以看出,当main()函数返回后,我们又用一条跳转指令重新执行trampoline程序——当然也就要重新执行main()函数,这也是trampoline(弹簧床)一词的意思所在。

Bootloader 的stage2通常包括以下步骤:

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

2.检测系统内存映射(memory map)。所谓内存映射就是指整个4GB物理地址空间(32位机)中有哪些地址范围已经被分配用来寻址系统的RAM单元。比如,在SA-1100 CPU中,从0xC000 0000开始的512M被用作系统的RAM地址空间;Samsung S3C44B0X CPU中,从0x0C00 0000到0x1000 0000间的64M被用作系统的RAM地址空间。

注意,虽然CPU通常预留出一大段足够的地址空间给系统RAM,但是在搭建具体的嵌入式系统时却不一定会实现CPU预留的全部RAM地址空间,也即具体的嵌入式系统往往只把CPU预留的全部RAM地址空间中的一部分映射到RAM单元上,而让剩下的那部分预留RAM地址空间处于未使用状态。因此,Bootloader的stage2必须在它想做点什么(比如,将存储在FLASH上帝内核映像读到RAM空间中)之前检测整个昔日的内存映射情况。也即它必须知道CPU预留的全部RAM地址空间中的哪些被真正映射到RAM地址单元,哪些处于unused状态。

一般用如下数据结构描述RAM地址空间的一段连续的地址范围:

Type struct memory_area_struct

{

u32

start;

//内存区域的起始地址

u32

size;

//内存区域的大小(字节数)

int

used;

//内存区域的状态

}memory_area_t;

其中,used = 0/1,1表示这段地址范围已经被实现,也即真正的被映射到RAM单元上,0表示这段地址范围并未被系统实现,处于未使用状态。

整个CPU预留的RAM地址空间可以用一个memory_area_t 类型数组来表示,如

Memory_area_t memory_map[NUM_MEM_AREAS]=

{

[0...(NUM_MEM_AREAS)]=

{

.start = 0,

.size = 0,

.used = 0

//表示检测内存映射之前的初始状态

},

};

内存检测用到如下算法:

1)数组初始化,每个区域的used标志设为0;

2)将整个空间中所有页面的钱32位(4字节)写为0;

3)依次检测每个页面是否有效(使用test_mempage算法):

3.1)若当前页面无效

若当前区域已映射,则当前区域检测结束;

3.2)若当前页面有效

判断该页面是否由其他页面映射而来,若是同3.1;

否则若当前区域已映射,则增加有效页面到当前区域中;

若当前区域为一个新的区域,则初始化该区域并增加当前页面到当前区域中。

在用上述算法检测完系统的内存映射情况后,Bootloader也可以将内存映射到详细信息打印到串口。

3.将kernel映像和跟文件系统映像从flash上读到RAM空间中。

在这个过程中需要规划内存占用的布局,包括内存映像所占用的内存范围和根文件系统所占用的内存范围。实际上只要考虑基地址和映像大小就行了,例如,对内核映像,一般考虑从(MEN_START + 0x8000)开始约1MB的内存范围内。嵌入式Linux的内核一般都不会超过1MB,为什么要把MEN_START 到MEN_START + 0x8000 这一段32KB大小的内存出来呢?这是因为 Linux 内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。对根文件系统映像,一般从 MEM_START+0x0010,0000 开始。如果用 Ramdisk 作为根文件系统映像,则其解压后的大小一般是1MB。

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

While (count)

{

* dest ++ = * src ++;

Count - = 4;

}

4.为内核设置启动参数

在嵌入式Linux系统中,需要有boot_loader设置的参数有:

内核参数,如页面大小、根设备;

内存映射情况;

命令行参数;

Initrd映像参数,包括起始地址、大小;

Ramdisk 参数,包括解压后的大小。

5.调用内核

内核调用的一般方法是直接跳转到内核的第一条指令处,也即RAM中内核被加载的地址处。对于ARM Linux系统,在跳转前必须满足一下几个条件:

1)CPU寄存器的设置:

R0 = 0;

R1 = 机器类型ID;

R2 = 传递给内核的启动参数其实地址。

2)CPU模式:

必须禁止中断(IRQs 和FIQs);

CPU必须处于SVC模式。

3)Cache和MMU的设置:

MMU必须关闭;

指令Cache可以打开也可以关闭;

数据Cache必须关闭。

从此stage2的工作结束,系统完全交给操作系统。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值