本文先引用一段《Boot Loader 技术内幕》中的一段话来简介bootloader基本概念,再
通过xboot的实例分析来分析xboot接口。
作者: http://blog.csdn.net/dyron欢迎大家来讨论相关内容。
简单地说,Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小
程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一
个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
通常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌
入式世界里建立一个通用的 BootLoader 几乎是不可能的。尽管如此,我们仍然可以对 Boot
Loader 归纳出一些通用的概念来,以指导用户特定的 Boot Loader 设计与实现。
每种不同的 CPU 体系结构都有不同的 Boot Loader。有些 Boot Loader 也支持
多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。
除了依赖于 CPU 的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设
备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU
而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,
通常也都需要修改 Boot Loader 的源程序。
2. Boot Loader 的安装媒介(Installation Medium)
系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址
上取指令。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x00000000
取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设
备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在
系统加电后,CPU 将首先执行 Boot Loader 程序。
下图1就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像
的固态存储设备的典型空间分配结构图。
图1 固态存储设备的典型空间分配结构
3. 用来控制 Boot Loader 的设备或机制
主机和目标机之间一般通过串口建立连接,Boot Loader 软件在执行时通常会通过串口
来进行 I/O,比如:输出打印信息到串口,从串口读取用户控制字符等。
由于我们项目使用了mmc卡做为主存储器件,故我们的bootloader前需要存放
mbr信息,故我们的bootloader固件由mbr+bootloader两部分组成。
Xboot的目录如下:
boot mbr.bin rules.mk
Changelog mbr-xboot.bin spl
config.mk mkcompatibility.sh x-boot.bin
mkconfig x-boot.lds include
msc-spl.bin x-boot-msc.bin Makefile
README
Boot目录下是主要的程序代码及开发板代码。
Include 目录下是程序的头文件。
Spl 是相应的环境脚本及制作mbr工具代码。
Xboot的目录结构相对简单, x-boot.lds是链接脚本。
下边我们就开始分析x-boot的启动流程, 首先看x-boot.lds文件,整个脚本内容
如下,看了脚本就会发现,x-boot还存在u-boot的影子,属抄袭作品:-) .
OUTPUT_ARCH(mips)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.sdata : { *(.sdata) }
_gp = ALIGN(16);
__got_start = .;
.got : { *(.got) }
__got_end = .;
.sdata : { *(.sdata) }
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
uboot_end_data = .;
num_got_entries = (__got_end - __got_start) >> 2;
. = ALIGN(4);
.sbss : { *(.sbss) }
.bss : { *(.bss) }
uboot_end = .;
}
在24行我们发现,ENTRY(_start)指定入口为_start, 接入寻找导出_start符号的文件,
发现是在boot/init/init.S中导出。
1 /*
2 * Startup Code for the X-Boot.
3 *
4 * Copyright (c) 2005 - 2009 Ingenic Semiconductor Corp.
5 *
6 */
7#include
8#include
9
10 .set noreorder
11
12 .globl _start
13 .text
14_start:
15 la sp, TEXT_BASE // set sp
16 j boot_main // jump to c routine
17 nop
在init.S文件中的16行发现, 这段代码最终跳转到boot_main符号处执行c代码, 最
终跳到./boot/common/boot_main.c中的boot_main 函数开始执行, 这就进入了我们的C程序。
上章说到我们的pc指针进入了boot_main函数, 首先在738-761行设置开发板必要的
GPIO。 然后设置键盘及必要的gpio引脚。
Xboot的启动相对简单,初始化引脚后,初始化MMC卡,进行检测开机逻辑, 都是各
厂商自己定制的内容,主要就是选择进入什么样的kernel模式,而进入kernel模式的选择
有多种,这里主要介绍一下通过内核参数来选择的方式。
这里以BOOT_NORMAL为例,分析一下xboot的参数传递
下边是BOOTARGS_NORMAL的定义。
#define BOOTARGS_NORMAL CONFIG_BOOTARGS
#define CONFIG_BOOTARGS "mem=128M console=ttyS1,57600n8ip=off
root=/dev/ram0 rw rdinit=/init"
在msc_boot.c的第56行,将启动参数赋值到cmdline中
static u8 cmdline[256] = CFG_CMDLINE;
将kernel从MMC拷贝到启动地址 0x80000000
#define CFG_KERNEL_DST 0x80000000 /* Load Linux kernel to this addr */
do_msc((u8 *)CFG_KERNEL_DST, offset,size);
初始化kernel的启动参数与及randisk
int init_boot_linux(unsigned char*data_buf, unsigned int data_size)
因为我们的kernel并非正常的zImage, 而是由ramdisk+kernel打包成的boot.img, 故
在boot_linux中进行了分解,并设置了详细的参数。
首先从MMC读出的buf中读出2k的bootimginfo, 里边包含有kernel的地址大小与
ramdisk的地址大小, 启动命令行等信息.
memcpy(&bootimginfo, Bulk_Data_Buf,2048);
计算出kernel和ramdisk实际占用的大小。
kernel_actual = (bootimginfo.kernel_size +page_mask) & (~page_mask);
ramdisk_actual = (bootimginfo.ramdisk_size+ page_mask) & (~page_mask);
获得kernel与ramdisk在buf中的实际地址,填充到bootimginfo结构中.
bootimginfo.kernel_addr = (unsignedint)Bulk_Data_Buf;
bootimginfo.ramdisk_addr =bootimginfo.kernel_addr + kernel_actual;
从MMC中读取ramdisk到CFG_RAMDISK_DST处。
do_msc((u8 *)CFG_RAMDISK_DST,ramdisk_offset, ramdisk_actual);
填充启动kernel的参数结构及组成kernel 启动的cmdline
param_addr= (u32 *)PARAM_BASE;
param_addr[0]= 0;/* might be address of ascii-z string: "memsize" */
param_addr[1]= 0;/* might be address of ascii-z string: "0x01000000" */
param_addr[2]= 0;
param_addr[3]= 0;
param_addr[4]= 0;
param_addr[5]= PARAM_BASE + 32;//0x80004020
param_addr[6]= CFG_KERNEL_START;
tmpbuf= (u8 *)(PARAM_BASE + 32);
跳转到kernel的启动地址,启动kernel
#define PARAM_BASE 0x80004000
(*kernel)(2, (char **)(PARAM_BASE + 16), (char*)PARAM_BASE);
这里的第1个参数是指传入参数的个数, 第2个参数是指传入参数的内存地址, 第3
个参数是指环境变量的内存地址。 (在uboot中传递为4个参数,在xboot中这种实现似
乎不太符合标准。 待深入研究)
相对于uboot来讲,xboot的启动流程相对非常简化,命令行工具都去除掉了,但启动
速度确实大大优于uboot, 这也许就是君正选择xboot的原因吧,欢迎大家来讨论,
[email protected]
8. 补充内容, 回答网友yuenshu fong的问题
xboot的编译流程
xboot的编译流程如下:
根据 ALL = $(X_BOOT) $(X_BOOT_NAND) $(X_BOOT_SD) $(X_BOOT_MSC)
首先编译x-boot.bin
$X_BOOT_NAND X_BOOT_SD未定义,所以跳过
然后到X_BOOT_MSC: $(X_BOOT_MSC): $(MSC_SPL) $(X_BOOT) MBR
它依赖于$(MSC_SPL) $(X_BOOT) MBR
先编译 MSC_SPL, 接着会调用make -s -C spl msc-spl.bin
进入spl/Makefile
msc-spl.bin
$(obj)msc-spl.bin: $(obj)msc-spl
$(OBJDUMP) -Dz $< > $(obj)msc-spl.dump
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
cp msc-spl.bin $(TOPDIR)
#define JZ_MSCBOOT_CFG 0x4d53504c
msc-spl.bin首先依赖于msc-spl, 直接编译并链接__LIBS, 注意这里调用了LDFLAGS, 又是x-boot.lds,
这个LDFLAGS内有一个TEXT_BASE, 地址是0x80000200.
$(obj)msc-spl: depend $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
$(LD) $(LDFLAGS) $(__OBJS) \
--start-group $(__LIBS) --end-group \
-o $@
现在生成了msc-spl, 返回msc-spl.bin的编译选项
$(OBJDUMP) -Dz $< > $(obj)msc-spl.dump, 将msc-spl生成msc-spl.dump
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@ 将msc-spl生成msc-spl.bin
cp msc-spl.bin $(TOPDIR) 复制到顶层目录
X_BOOT在之前已编译过,暂时跳过
编译MBR, 使用gcc spl/tools/mbr_creater/mbr_creater.c -o spl/tools/mbr_creater/mbr_creater
spl/tools/mbr_creater/mbr_creater mbr.bin
现在返回
$(X_BOOT_MSC): $(MSC_SPL) $(X_BOOT) MBR
$(MAKE) -C spl pad
cat spl/msc-spl-pad.bin boot/x-boot.bin > x-boot-msc.bin
cat mbr.bin x-boot-msc.bin > mbr-xboot.bin
现在生成pad, 调用spl/tools/msc-spl-pad.sh生成8k的msc-spl-pad.bin
返回X_BOOT_MSC, 调用cat生成x-boot-msc.bin,再将mbr.bin打进去,就生成了mbr-xboot.bin
mbr-xboot.bin组成格式
mbr[512byte] + msc-spl-pad.bin[8k] + x-boot.bin
msc-spl-pad.bin入口地址是0x80000200, 正好跳过前边512字节的mbr.