分类: LINUX
2013-04-24 11:15:29
原文地址:bootloader(xboot)起动流程分析 作者:piaoyizu
本文先引用一段《Boot Loader 技术内幕》中的一段话来简介bootloader基本概念,再
通过xboot的实例分析来分析xboot接口。
作者: 欢迎大家来讨论相关内容。
的概念 简单地说,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 的源程序。
的安装媒介(Installation Medium)
系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址
上取指令。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x00000000
取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设
备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在
系统加电后,CPU 将首先执行 Boot Loader 程序。
下图1就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像
的固态存储设备的典型空间分配结构图。
1 固态存储设备的典型空间分配结构
用来控制 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 READMEBoot目录下是主要的程序代码及开发板代码。
Include 目录下是程序的头文件。
Spl 是相应的环境脚本及制作mbr工具代码。
Xboot的目录结构相对简单, x-boot.lds是链接脚本。
4. x-boot汇编启动分析 下边我们就开始分析x-boot的启动流程, 首先看x-boot.lds文件,整个脚本内容
如下,看了脚本就会发现,x-boot还存在u-boot的影子,属抄袭作品:-) .
在24行我们发现,ENTRY(_start)指定入口为_start, 接入寻找导出_start符号的文件,
发现是在boot/init/init.S中导出。
在init.S文件中的16行发现, 这段代码最终跳转到boot_main符号处执行c代码, 最
终跳到./boot/common/boot_main.c中的boot_main 函数开始执行, 这就进入了我们的C程序。
5. xboot的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的原因吧,欢迎大家来讨论,
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.