Boot Loader开发--Blob分析

来源:互联网 发布:navicat for mysql 11 编辑:程序博客网 时间:2024/05/13 11:30

本文详细描述Blob的原理、实现方法及开发要点,作为对自己工作的总结。

一、Bootloader简介

Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。

通常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。每种CPU体系结构都有不同的 BootLoader。有些 Boot Loader 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU 的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。

常用的Bootloader有U-Boot,Vivi,Blob,Redboot等。他们通常存储于系统的ROM中。对于嵌入式系统而言,主要是Flash,E2ROM等。一个嵌入式系统的典型存储空间分配如图(1)所示。


图(1)嵌入式系统典型存储空间分配

二、Blob功能

Blob通过串口输出信息,接收从串口的输入,blob的控制台就是通过串口实现的。文件传输也可以通过网口或者USB口进行以提高传输速度。其主要功能如下:

● 使用xModem协议下载文件到内存

● Nor Flash在线烧写

● Nand Flash在线烧写

● Linux内核加载和引导

● 网络和简单协议(TFTP、PING)支持

● USB传输支持

● 在线调试BLOB

         和其他Bootloader一样,blob支持两种工作模式:加载模式和下载模式。分别描述如下:

(1)加载模式

也称为“自主”(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。

(2)下载模式

在这种模式下,目标机上的 Boot Loader将通过串口连接或网络连接等通信手段从主机下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。

         显然,两种模式的差别仅仅对于开发者才有意义的。在启动的stage 2,将kernel复制到RAM后,Blob在一定时间内会循环检查控制台有无输入,如果此时用户没有按任何按键,blob将自动引导操作系统启动,否则将进入下载模式,输出控制台信息。等待时间的长短在Main.c中设定。

三、Blob的文件组织结构

Blob->include ->blob     ->arch:与硬件平台相关的头文件其它头文件,如xmodem.h等

       ->src       ->blob:           大部分源代码在这个目录

->diag:              诊断测试代码,不常用

->lib       :               库文件,如串口驱动、led驱动等

       ->tools:

       ->utils:

四、Blob空间分配

在../include/blob/arch/lubbock.h中,定义了系统的存储空间分配,包括Bootloader、Linux内核,文件系统等。主要内容如下:

/* the baseaddress were BLOB is loaded by the first stage loader */

#define BLOB_ABS_BASE_ADDR     (0xA0000000)

/* where dovarious parts live in RAM */

#define BLOB_RAM_BASE               (0xA0100000)

#define KERNEL_RAM_BASE           (0xA0200000)      

#define PARAM_RAM_BASE            (0xA0180000)

#define RAMDISK_RAM_BASE        (0xA0400000)

/* and wheredo they live in flash */

#define BLOB_FLASH_BASE            (0x00000000)

#define BLOB_FLASH_LEN              (64* 1024)

#define PARAM_FLASH_BASE  (BLOB_FLASH_BASE+ BLOB_FLASH_LEN)

#define PARAM_FLASH_LEN           (64*1024)

#define KERNEL_FLASH_BASE              (0x00020000)

#define KERNEL_FLASH_LEN          (1920*1024)         //1920=2048-128

#define RAMDISK_FLASH_BASE     (0x00200000)

#define RAMDISK_FLASH_LEN              (14* 1024 * 1024) //for rootfs

#define JFFS2_FLASH_BASE            (0x01000000)        //17M--32M

#define JFFS2_FLASH_LEN                     (4* 1024 * 1024)

/* theposition of the kernel boot parameters */

#define BOOT_PARAMS            (0xA0000100)

         因此,整个32M的Flash空间分配如图(2)。


图(2)Blob对系统Flash的规划

五、Blob启动分析

Blob被放在Flash的0x0地址起始处,ARM微处理器复位后自动跳到0x0地址执行,因此系统复位时首先运行blob程序。Blob负责完成对系统硬件的初始化、对SDRAM进行测试,然后加载操作系统,并跳到操作系统处开始执行。Blob的启动流程如图(3)所示。


图(3) blob启动流程

Blob支持自烧写,即通过blob更新blob。对于一个空的目标系统,首先是通过JTAG或者其他手段将blob程序烧写到flash中去,之后,如果需要修改blob,则可以直接使用blob提供的工具来完成更新了。要实现这一功能,blob程序必须在SDRAM中运行。因此,blob键程序分成两个段:stage1和stage2,如图(4)所示。


图(4)Blob的两个阶段

         在Stage 1,基本都是用汇编语言编写。通常要完成以下工作;

(1)   初始化硬件;

(2)   为Blob的Stage 2准备RAM空间;

(3)   复制Stage 2到RAM;

(4)   设置好堆栈;

(5)   跳到Stage 2 的C程序入口点;

当blob由stage1执行到Stage 2时,程序已经在SDRAM中运行了。下面看一下是如何跳转的。

在../include/blob/arch/lubbock.h中定义了很多宏变量:

/* the baseaddress were BLOB is loaded by the first stage loader */

#define BLOB_ABS_BASE_ADDR     (0xA0000000)

/* where dovarious parts live in RAM */

#defineBLOB_RAM_BASE        (0xA0100000)

/* and wheredo they live in flash */

#define BLOB_FLASH_BASE            (0x00000000)

#define BLOB_FLASH_LEN              (64* 1024)

 

在Start.S中,初始化硬件后进行了Stage 2的搬移:

BLOB_START:      .word   BLOB_ABS_BASE_ADDR

……

relocate:

       adr   r0, _start /* blob入口地址到r1,在此相当于基址 */

 

       /* relocate the second stage loader */

       add  r2, r0, #(64 * 1024)              /* blob maximumsize is 64kB */

       add  r0, r0, #0x1000             /* skip first 4k bytes,Only Stage 2 */

       ldr   r1, BLOB_START     /* 目的地址 */

 

       /* r0 =source address

        * r1 = target address

        * r2 = source end address

        */

copy_loop:

       ldmia      r0!, {r3-r10} /* ldmia批量加载,r0指向的地址上的多字数据,保存到r3-r10中,r0的值更新*/

       stmia       r1!, {r3-r10}/*将r3-r10中的数据存储到R1指向的地址上,R1值更新*/

       cmp r0, r2 /*源开始地址是否=源结束地址*/

       ble   copy_loop /* 不等就继续复制数据 */

……

         可以看到,Blob的Stage 1将flash地址0x1000开始的60K空间全部搬移到了BLOB_START定义的RAM空间。在搬移过程中,跳过了前4K的空间,因为前4K存放的blob的Stage 1。Blob如何将代码分割为前4k和后60K的,我们下面在讨论。搬移成功后,紧接着是下面的跳转语句:

       /*blob is copied to ram, so jump to it */

       ldr   r0,BLOB_START

       mov pc,r0             // 跳转

通过将PC指针指向BLOB_START即实现了跳转。

Stage 2 开始并不是Mani.c文件,而是trampoline.S,意为“跳板”。该文件内容如下:

_trampoline:

       /*clear the BSS section */

       ldr   r1,bss_start

       ldr   r0,bss_end

       sub  r0,r0, r1

       /* r1 = startaddress */

       /* r0 =#number of bytes */

       mov r2,#0

clear_bss:

       stmia       r1!,{r2}

       subs r0,r0, #4

       bne  clear_bss

       /*setup the stack pointer */       //设置堆栈

       ldr   r0,stack_end

       sub  sp,r0, #4

 

       /*jump to C code */

       bl    main                     // 这里跳转到C程序,即Main.C的main()函数

       /*if main ever returns we just call it again */

       b     _trampoline

bss_start:        .word      __bss_start

bss_end:         .word      __bss_end

stack_end:      .word      __stack_end

这是,Blob才真正开始执行C程序代码了。Blob的Stage 2一般使用C语言编写,便于实现更复杂的功能和取得更好的代码可读性和可移植性。Stage 2一般完成的功能是:

(1)初始化本阶段要使用到的硬件设备

(2)检测系统内存映射(memory map)

(3)将 kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中

(4)为内核设置启动参数

(5) 调用内核

Stage 2中程序的执行过程如图(5)所示。


图(5)Blob Main函数流程图

启动内核掉用parse_command("boot")完成得,该函数实际执行的函数是在../src/blob/linux.c中的boot_linux(),如下:

static int boot_linux(int argc, char *argv[])

{

         int i,len;

         struct param_struct *params;

         void (*theKernel)(int zero, int arch) = (void (*)(int,int))KERNEL_RAM_BASE;

 

         /* Initial kernel params */

         params = (struct param_struct *)BOOT_PARAMS;

         params->u1.s.page_size = LINUX_PAGE_SIZE;

         params->u1.s.nr_pages = (0x04000000 >>LINUX_PAGE_SHIFT);

         memcpy(params->commandline, boot_paramsm,strlen(boot_paramsm));

         setup_start_tag();           //传递内核参数

         setup_memory_tags();

         setup_commandline_tag(argc, argv);

setup_initrd_tag();

         setup_ramdisk_tag();

         setup_end_tag();

 

         /* we assume that the kernel is in place */

         SerialOutputString("Starting kernel ...\n");

         serial_flush_output();

 

         /* disable subsystems that want to be disabled beforekernel boot */

         exit_subsystems();

 

         /* start kernel */

         theKernel(0, ARCH_NUMBER);  // 启动内核

         SerialOutputString("Hey, the kernel returned! Thisshould not happen.\n");

         return 0;

}

这里面非常重要的就是theKernel()函数指针。这是在C语言中实现程序跳转的很好的方法。theKernel()有两个参数,根据POSIX准则,第一个参数将传递给CPU的R0寄存器,第二个则是R1寄存器。在跳到内核是,必须要满足以下条件:

(1) CPU 寄存器的设置:

·R0=0;(theKernel的第一个参数)
·R1=机器类型 ID;关于 Machine Type Number,可以参见 linux/arch/arm/tools/mach-types。(theKernel的第二个参数)
·R2=启动参数标记列表在 RAM 中起始基地址;

(2) CPU 模式:

·必须禁止中断(IRQs和FIQs);
·CPU 必须 为Supervisor的保护模式;

(3)Cache 和 MMU 的设置:

·MMU 必须关闭;
·指令 Cache 可以打开也可以关闭;
·数据 Cache 必须关闭

至此,Blob交出CPU使用权了,一次正常的引导过程完成了。而进入Blob控制台后,blob在死循环中等待用户输入,并执行用户命令。Blob支持简单的命令集,比如,help命令打印帮助信息,xdownload命令下载文件,flash命令将下载的文件烧写到flash中,boot命令引导内核启动等。

六、移植到PXA255平台需要做的工作

Blob支持的平台比较多,因此移植blob到一个已有的平台是比较简单的事情。只需要修改为数不多的几个文件即可。移植到一个新的平台则相对复杂,具体可以参考../doc/porting.txt。下面以第一种情况为例说明需要修改的几个地方。

(1)在../include/blob/arch/lubbock.h修改存储空间分配方案及IO寄存器、Mem控制器的初始值;

(2)如果系统由LED,在../src/lib/led.c和../src/blob/ledasm.S中修改led的控制函数;

(3)在memsetup-pxa.S中设置CPU工作时钟、内存访问速度等;

(4)在Main.c中修改控制台参数;

(5)在../src/lib/serial-pxa.c中选择控制台串口,在../src/blob/initcalls.c中设置默认的控制台串口速率。

(6)在../src/blob/flash.c中修改FLASH读写函数(如果不是同一种Flash的话,这里用的是IntelE28F128J3A);

(7)对于不同的Linux内核,需要修改../src/blob/linux.c中的参数和boot_linux()函数。

(8)如果需要网卡功能和USB功能支持,则需要修改相应的文件,这里不再详述。

七、其它主要程序分析

1、  Blob链接时如何区分Stage 1 和 Stage 2?

分析Blob的Makefile可以得到答案。../src/blob/Makefile.in文件写得很清楚:

# ---- Blob first stage loader---------------------------------------

# WARNING: start.S *must* be the first file, otherwisethe target will

# be linked in the wrong order!

blob_start_elf32_SOURCES = \

         start.S \

         ledasm.S \

         testmem.S

……

blob_start_elf32_DEPENDENCIES = \

         memsetup-pxa.o \

         start-ld-script

 

# ---- Blob second stage---------------------------------------------

# WARNING: trampoline.S *must* be the first file,otherwise the target

# will be linked in the wrong order!

blob_rest_elf32_SOURCES = \

         trampoline.S \

         flashasm.S \

         stack.S \

         testmem2.S \

         bootldrpart.c \

         commands.c \

         flash.c \

         initcalls.c \

         linux.c \

         main.c \

         memory.c \

         param_block.c \

         partition.c \

         reboot.c \

         uucodec.c \

         xmodem.c

……

在../src/blob/start-ld-script和../src/blob/rest-ld-script两个文件中定义了链接时各个代码段的地址。

2、  Blob使用的文件传输协议

Blob使用Xmodem协议传输文件,源代码可以参考../src/blob/xmodem.c。

八、Blob编译说明

使用arm-linux-gcc可以编译blob,这里使用的arm-linux-gcc版本为3.3.2。编译blob时必须指定Linux源码的安装路径,原因在于编译blob时需要寻找在linux.c程序中语句#include<asm-arm/setup.h>包含的头文件,该头文件包含了Linux内核启动参数结构体,不同版本的linux内核可能不一样。

./configure-pxa255可以自动编译生成blob可执行文件。该脚本实际执行如下指令:

make distclean

export CC=/usr/local/arm/3.3.2/bin/arm-linux-gcc

export OBJCOPY=/usr/local/arm/3.3.2/bin/arm-linux-objcopy

./configure --with-board=lubbock --with-cpu=pxa255--with-linux-prefix=/usr/src/linux-2.6.8.1-sniffer --enable-usb --with-eth=usb--enable-debug

Make

各个选项的说明可以参考configure.in文件。更为详细的说明参考blob的README文件。

九、Blob使用说明

参考blob的README文件。Blob的控制台界面如下:


图(6)Blob控制台

原文主要载于

http://blog.163.com/xd_zxw/blog/static/15473901200702101959175/

 本人稍作编辑修改。

原创粉丝点击