链接:http://blog.chinaunix.net/uid-23711997-id-131364.html
BootLoader实验
(3)、设置堆栈指针sp
【实验目的】
1、了解
BootLoader的基本概念和框架结构
2、了解
BootLoader引导操作系统的过程
4、掌握
BootLoader程序的使用方法
【实验原理】
1、bootLoader的作用
PC 机中的引导加载程序由 BIOS和位于硬盘 MBR 中的 OS Boot Loader一起组成。BIOS 在完成硬件检测和资源分配后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给 OS Boot Loader。Boot Loader 的主要运行任务就是将内核映象从硬盘上读到 RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。
嵌入式系统中,通常并没有像 BIOS 那样的固件程序,因此整个系统的加载启动任务完全由
bootLoader 来完成。
bootloader的主要作用:
(1)、初始化硬件设备
(2)、建立内存空间的映射图
(3)、完成内核的加载,为内核设置启动参数
2、bootLoader程序结构框架
嵌入式系统中的boot Loader 的实现完全依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为第一阶段和第二阶段两大部分,依赖于CPU体系结构的代码,比如设备初始化代码等,通常都放在阶段1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而阶段2 则通常用C语言来实现,这样可以实现一些复杂的功能,而且代码会具有更好的可读性和可移植性。
(1)、Boot Loader 的阶段1通常主要包括以下步骤:
l 硬件设备初始化;
l 拷贝Boot Loader的程序到RAM空间中;
l 设置好堆栈;
l 跳转到阶段2的C入口点。
(2)、Boot Loader的阶段2通常主要包括以下步骤:
l 初始化本阶段要使用到的硬件设备;
l 系统内存映射(memory map);
l 将kernel映像和根文件系统映像从Flash读到RAM空间中;
l 为内核设置启动参数;
l 调用内核。
3、bootLoader程序架构分析
3.1 Boot Loader 阶段1分析
(1) 基本的硬件初始化
Boot Loader一开始就执行基本硬件初始化操作,其目的是为阶段2的执行以及随后的内核的执行准备好一些基本的硬件环境。它通常包括以下步骤:
l 初始化GPIO功能,通过GPIO来驱动LED,其目的是表明系统的状态是OK还是Error。如果板子上没有LED,那么也可以通过初始化UART向串口打印Boot Loader的Logo字符信息来完成这一点。(
参考fixgpio.S)
@ fixgpio.S
define_gpio:
……
ldr r1, =GPIO_BASE /* 0x40E00000*/
ldr r0, =_GPSR0 /*0x00003000*/
str r0, [r1, #GPSR0] /*GPIO<13,12> is set as output */
ldr r0, =_GPCR0 /* 0x00000800*/
str r0, [r1, #GPCR0] /*GPIO<11> is clear to level zero*/
ldr r0, =_GAFR0L /* 0x80000000*/
str r0, [r1, #GAFR0L] /* GPIO(15) is set as alternate functions 2->nCS<1>*/
ldr r0, =_GAFR0U /* 0x00000010 | (2<<0 )*/
str r0, [r1, #GAFR0U] /*GPIO<18,16> set as alternate functions 2
……
|
l 设置CPU的速度和时钟频率。(参考
setup_memory.S部分代码)
clock_enable :/* start.S*/
#if defined(CONFIG_PXA25x)
ldr r0, =0x0001FFFF
#elif defined(CONFIG_PXA27x)
ldr r0, =0x01FFFFFF
#endif
ldr r1, =CKEN
str r0, [r1]
mov pc, lr
|
l 存储控制单元初始化。包括正确地设置系统动静态存储控制器的各个寄存器等。
setup_memory : /* memsetup.S*/
@ change cpu speed function
ldr r1, =CCCR
ldr r0, =_CCCR
str r0, [r1] @ set CCCR
……
|
(2)、将bootLoader程序加载到RAM空间
/* copy
bootloader to dynamic memory area*/
ldr r0, =0x00
ldr r1, =__boot_start /* @boot.ld.in file */
ldr r2, =__boot_end /* @boot.ld.in file */
1: ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r1, r2
blt 1b
|
堆栈指针的设置是为了执行C语言代码作好准备。通常我们可以把sp的值安排在RAM
空间的最顶端(堆栈向下生长)。此外,在设置堆栈指针sp之前,也可以关闭led灯,以提示准备跳转到阶段2。经过上述这些执行步骤后,系统的物理内存布局应该如图3-1所示。
@ set stack point
ldr sp, =stack_point-4 /*stack_point = __boot_start + 0x00100000;*/
|
图3-1 系统的物理内存布局
(4)、跳转到阶段2的C入口点
在上述一切都就绪后,就可以跳转到Boot Loader的阶段2去执行了。在ARM系统中,
可以通过修改PC寄存器为合适的地址来实现。
ldr pc, =main /* @ start.S file*/
|
3.2 Boot Loader 阶段2分析
阶段2的代码通常用C语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通C语言应用程序不同的是,在
编译和链接Boot Loader程序时,不能使用glibc库中的任何支持函数。可以直接把main()函数的起始地址作为整个阶段2执行映像的入口点。
@ jump to c code
ldr pc, =main
|
(1)、初始化本阶段要使用到的硬件设备;
本阶段初始化的硬件设备通常包括:
l 初始化至少一个串口,以便和终端用户进行I/O输出信息;
l 初始化计时器、
l 初始化网络传输等。
在初始化这些设备之前,也可以重新把LED灯点亮,以表明程序
bootLoader程序已经进入main()函数执行。设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。(
参考main.c文件)
……
(*(volatile unsigned short *)(0x0a000000)) |= (1<<9)|(1<<8)|(1<<6)|(1<<15);
uart_init(); //初始化串口
time_init(); //初始化定时器
config_init(); //内存映射配置初始化
//信息提示
printf("\033[H\033[J\n"); // clear screen.
printf(" %s :
bootloader for Xscale 270 board\n", PACKAGE);
printf(" Copyright (C) 2002-2004 Emdoor Co,. ltd.\n");
printf(" support : http://www.emdoor.com\n");
iflash_init(); //flash存储器初始化
PWMPCR0 = 0xff;
PWMDCR0 = (0x4ff>>2);
//let eth CS is ok
(*(volatile unsigned long *)(0x4800000c)) = _MSC1_ED;
eth_init(); //网络初始化
……
|
(2)、系统的内存映射
所谓内存映射就是指在整个4GB物理地址空间中有哪些地址范围被分配用来寻址系统的RAM单元。虽然CPU通常预留出一大段足够的地址空间给系统RAM,但是在搭建具体的嵌入式系统时却不一定会实现CPU预留的全部RAM地址空间。也就是说,具体的嵌入式系统往往只把CPU预留的全部RAM地址空间中的一部分映射到RAM单元上。开发板的
bootloader程序利用map __bsetup parts[]结构体对内存的分配进行配置(
参考partition.c),从内存分布可以看出,PXA27X开发板SRAM和SDRAM起始地址的存储分布情况:
bootloader: 0x00000000_0x00040000,共占256K,SDRAM起始地址:0xA1E00000
kernel: 0x00040000_0x00180000,共占1.25M ,SDRAM起始地址:0xA0008000
root: 0x00180000_0x02000000,共占30.5M,SDRAM起始地址0xA0000000
struct map __bsetup parts[] = {
{
.name = "loader",
.sramb = LOADER_SRAM_BASE, //0x00000000
.srams = LOADER_MAX_SIZE, //0x00040000->256K
.dramb = LOADER_DRAM_BASE, //0xA1E00000
.drams = 0,
.maxs = LOADER_MAX_SIZE,
}, {
.name = "kernel",
.sramb = KERNEL_SRAM_BASE, //0x00040000
.srams = KERNEL_MAX_SIZE, //0x00140000->1.25M
.dramb = KERNEL_DRAM_BASE, //0xA0008000
.drams = 0,
.maxs = KERNEL_MAX_SIZE,
}, {
.name = "ramdisk",
.sramb = RAMDISK_SRAM_BASE, //0x00180000
.srams = RAMDISK_MAX_SIZE, //0x00300000->3M
.dramb = RAMDISK_DRAM_BASE, //0xA1000000
.drams = 0,
.maxs = RAMDISK_MAX_SIZE,
}, {
.name = "root",
.sramb = ROOTFS_SRAM_BASE, //0x00180000
.srams = ROOTFS_MAX_SIZE, //0x01e80000->30.5M
.dramb = ROOTFS_DRAM_BASE, //0xA0000000
.drams = 0,
.maxs = ROOTFS_MAX_SIZE,
}
};
|
(3)、加载内核映像和根文件系统映像
l 规划内存占用的布局
这里包括两个方面:① 内核映像所占用的内存范围;② 根文件系统所占用的内存范围。在规划内存占用的布局时,主要考虑基地址和映像的大小两个方面。
对于内核映像,一般将其拷贝到从(MEM_START+0x8000)这个基地址开始的大约1MB大小的内存范围内(嵌入式Linux的内核一般都不操过1MB)。为什么要把从MEM_START到MEM_START+0x8000这段32KB大小的内存空出来呢?这是因为Linux内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。
而对于根文件系统映像,则一般将其拷贝到MEM_START+0x00100000开始的地方。如果用Ramdisk作为根文件系统映像,则其解压后的大小一般是1MB。
(4)、设置内核的启动参数
应该说,在将内核映像和根文件系统映像拷贝到RAM空间中后,就可以准备启动Linux
内核了。但是在调用内核之前,应该作一步准备工作,即:设置Linux内核的启动参数。 Linux 2.4.x 以后的内核都倾向以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记ATAG_CORE开始,以标记ATAG_NONE结束。每个标记由tag_header结构和随后的特定参数值数据结构来组成。(参考文件:linux.c)
……
#define ATAG_NONE 0x00000000
struct tag_header {
uint32 size;
uint32 tag;
};
……
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
|
在嵌入式Linux系统中,通常需要由Boot Loader设置的常见启动参数有:ATAG_CORE、
ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
比如,设置ATAG_CORE的代码如下:
void create_tags(void){
tags = (struct tag *)BOOT_PARAMS;
setup_tag_core(0, 0);
setup_tag_initrd2(0xA1000000, 0x00420000);
setup_end_tag();
return;
}
static void setup_tag_core(uint32 rootdev, uint32 flags){
tags->hdr.tag = ATAG_CORE;
tags->hdr.size = tag_size(tag_core);
tags->u.core.flags = flags; // not use.
tags->u.core.pagesize = 0; // set read/write.
tags->u.core.rootdev = 0;
tags = tag_next(tags);
return;
}
|
其中,BOOT_PARAMS 表示内核启动参数在内存中的起始基地址,指针tags是一个struct tag类型的指针。宏tag_next()将以指向当前标记的指针为参数,计算出当前标记的下一个标记的起始地址。注意,内核的根文件系统所在的设备ID就是在这里设置的。
下面是设置ATAG_INITRD的示例代码,它告诉内核在RAM中的什么地方可以找到initrd映象(压缩格式)以及它的大小:
static void setup_tag_initrd2(uint32 start, uint32 size){
tags->hdr.tag = ATAG_INITRD2;
tags->hdr.size = tag_size(tag_initrd);
tags->u.initrd.start = start;
tags->u.initrd.size = size;
tags = tag_next(tags);
return;
}
|
最后,设置ATAG_NONE标记,结束整个启动参数列表:
static void setup_end_tag(void){
tags->hdr.tag = ATAG_NONE;
tags->hdr.size = 0;
return;
}
|
(5)、调用内核
Boot Loader调用Linux内核的方法是直接跳转到内核的第一条指令处,也即直接跳转
到MEM_START+0x8000地址处。在跳转时,下列条件要满足:
l CPU寄存器的设置:
R0=0;
R1=机器类型ID;关于机器类型号,可以参见linux/arch/arm/tools/mach-types;
R2=启动参数标记列表在RAM中起始基地址;
l CPU 模式:
必须禁止中断(IRQs和FIQs);
CPU必须SVC模式;
l Cache和MMU的设置:
MMU必须关闭;
指令Cache可以打开也可以关闭;
数据Cache必须关闭;
如果用C语言,可以像下列示例代码这样来调用内核:(粗体表示)
static bool do_boot(int argc, char **argv){
void (*kernel)(int zero, int arch);
if (argc == 1) {
struct map *mp;
mp = find_map("kernel");
if (!mp){ printf(" can't found map for kernel.\n"); goto invalid; }
kernel = (void *)mp->dramb;
} else if (argc == 2){
bool res;
ulong tmp;
res = strtoul(argv[1], &tmp, 16);
if (!res) goto invalid;
kernel = (void *)tmp;
} else goto invalid;
if (!get_kernel_size(kernel)){ printf(" error: kernel is not exists.\n"); return false; }
create_tags();
printf("starting kernel ...\n");
kernel(0, 200);
return true;
invalid :
boot_usage();
return false;
}
|
【实验仪器】
1、装有Linux操作系统的PC机一台;
2、XSBase270或XSBase255 ARM实验开发平台一套
【实验内容】
1、bootLoader程序的编译
为了
编译Bootloader程序,需要事先在目标板上安装交叉
编译工具Toolchain。(见linux使用手册Toolchain安装部分)。
(1)、利用 tar zxvf Boot-XSBase270.tar.gz 指令解压。
[root@localhost
BootLoader]$tar zxvf Boot-XSBase270.tar.gz
利用上述命令解压后,
bootloader源代码解压到当前目录中Boot-XSBase270文件夹中。
(2)、
编译。在解压的目录里进行make
编译。
[root@localhost
BootLoader]$ cd Boot-XSBase270
[root@localhost Boot-XSBase270]$make
编译完成后, 在当前目录下会生成
bootloader映象文件boot。
2、bootLoader程序的下载
将
bootloader的映象文件boot拷贝Jflash-XSBase270目录下,连好JTAG下载器,并利用Jflash-XSBase270目录中jflashmm程序将
bootloader映象文件boot烧写到开发板上。
[root@localhost Boot-XSBase270]$cp boot /root/EmdoorARM/Jflash/Jflash-XSBase270/
[root@localhost Boot-XSBase270]$ cd /root/EmdoorARM/Jflash/Jflash-XSBase270
[root@localhost Boot-XSBase270]$ ./jflashmm boot
3、
bootLoader的使用方法
Bootloader命令的使用方法。
help
用法
|
帮助
|
描述
|
简短显示各命令的介绍
|
参数
|
无
|
举例
|
Bboot> Help
|
Load
用法
|
load [kernel/ramdisk]
|
描述
|
把存放在FLASH中的映像文件拷贝到SDRAM中。在Autoboot
过程中会自动运行,把内核映像从FLASH加载到SDRAM中。
|
参数
|
Kernel - 把内核映像从FLASH拷贝到SDRAM中
Ramdisk- 从FLASH中拷贝RAMDISK到SDRAM
|
举例
|
Bboot> load kernel
|
bootp
用法
|
Bootp
|
描述
|
运行bootp命令用来获取HOST主机发送的BOOTP的数据包,解析
BOOTP的数据包,获取本机的IP地址
|
参数
|
无
|
举例
|
Bboot> bootp
|
tftp
用法
|
Tftp 文件名 {address/loader/kernel/root/ramdisk}
|
描述
|
通过以太网下载主机的数据或文件到目标平台的SDRAM
|
参数
|
文件名 - 主机平台需要传输的文件名
loader - 把传输到目标平台的文件放置在SDRAM的loader区域
kernel - 把传输到目标平台的文件放置在SDRAM的kernel区域
root - 把传输到目标平台的文件放置在SDRAM的root区域
ramdisk - 把传输到目标平台的文件放置在SDRAM的ramdisk区域
address - 把传输到目标平台的文件放置在SDRAM的指定地址
|
举例
|
Bboot> tftp zImage kernel
|
flash
用法
|
Flash {loader/kernel/root/ramdisk}
|
描述
|
把SDRAM中的数据烧录到FLASH中特定的地址
|
参数
|
loader - 把SDRAM中的数据烧录到FLASH中的loader区域
kernel – 把SDRAM中的数据烧录到FALSH中的kernel区域
root – 把SDRAM中的数据烧录到FLASH中的root区域
ramdisk – 把SDRAM中的数据烧录到FLASH中的ramdisk的区域
|
举例
|
Bboot> flash kernel
|
erase
用法
|
erase {loader/kernel/ramdisk/root}
|
描述
|
擦除FALSH中的相应区域
|
参数
|
loader – 擦除FALSH中loder区域
kernel – 擦除FLASH中的kernel区域
root – 擦除FLASH中的root区域
ramdisk – 擦除FLASH中的ramdisk区域
|
举例
|
bboot> erase kernel
|
boot
用法
|
Boot
boot [addr]
|
描述
|
在SDRAM中运行kernel
通过参数中指定的地址运行kernel
|
参数
|
addr – kernel image address
|
举例
|
bboot> boot
|
set
用法
|
set [name] [value]
|
描述
|
设置IP地址,MAC地址以及autoboot参数
|
参数
|
Name [myipaddr] [destipaddr] [myhaddr] [autoboot]
Value [ip] [ip] [mac address] [load kernel; boot]
|
举例
|
bboot> set //输出设置信息
bboot> set myipaddr 192.168.100.X //改变目标平台的IP地址
bboot> set destipaddr 192.168.100.XX //改变主机平台的IP地址
bboot> set myhaddr 00:0E:6F:CE:59:21 //设置目标平台的MAC地址
bboot> set autoboot load kernel; boot //装载kernel后自动启动
|
ping
用法
|
Ping
|
描述
|
检查目标平台和主机平台的网络连接
|
参数
|
Ping主机平台的ip地址
|
举例
|
Ping 192.168.100.xx
|
reboot
用法
|
Reboot
|
描述
|
软件复位
|
参数
|
None
|
举例
|
Reboot
|
【思考题】
1、分析
bootloader源程序中的flash.c代码,画出该代码的流程图
2、利用实验二Makefile的知识点,分析
bootloader源程序三个Makefile的结构与关系
3、某同学在
bootloader源程序目录中,利用make
编译bootloader源程序,出现如下错误:
[root@hostlocal Boot-XSBase270]make
compile start.S
make[1]: arm-linux-gcc: Command not found
make[1]:***[start.o] Error 127
make: *** [all] Error 2
请分析产生错误的原因,并给出解决办法
4、
bootloader主要功能有哪些?
5、请在阅读完
bootloader程序后,画出
bootloader程序框架流程.。