•UBoot源码解析(一)主要内容•分析UBoot是如何引导Linux内核•UBoot源码的一阶段解析BootLoader概念•Boot Loader 就是在操作系统内核运行之前运行的一段小程序。
通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境•通常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。
因此,在嵌入式世界里建立一个通用的Boot Loader 几乎是不可能的。
尽管如此,我们仍然可以对Boot Loader 归纳出一些通用的概念来,以指导用户特定的Boot Loader 设计与实现。
UBoot来源•U-Boot 是 Das U-Boot 的简称,其含义是 Universal Boot Loader,是遵循 GPL 条款的开放源码项目。
最早德国DENX 软件工程中心的 Wolfgang Denk 基于 8xxROM 和FADSROM 的源码创建了 PPCBoot 工程项目,此后不断添加处理器的支持。
而后,Sysgo Gmbh 把 PPCBoot 移植到 ARM 平台上,创建了 ARMBoot 工程项目。
最终,以 PPCBoot 工程和 ARMBoot 工程为基础,创建了 U-Boot 工程。
•而今,U-Boot 作为一个主流、通用的 BootLoader,成功地被移植到包括 PowerPC、ARM、X86 、MIPS、NIOS、XScale 等主流体系结构上的百种开发板,成为功能最多、灵活性最强,并且开发最积极的开源 BootLoader。
目前。
U-Boot 仍然由 DENX 的 Wolfgang Denk 维护UBoot存储空间分布•UBoot是用来引导OS系统启动,那么它是如何引导OS启动的呢?启动参数内核根文件系统bootloaderUBoot和内核的交互•UBoot如何调用Linux内核?–UBoot通过命令把Linux内核镜像文件从Flash中读取到内存的某一位置,然后设置PC寄存器执向该位置UBoot调用Linux内核的前提条件是?–R0 =0–R1=适当的机器码机器码的位置存放在linux/arch/arm/mach-type 文件中–R2 =启动参数标记列表在内存中的位置–CPU必须设置为SVC模式并关闭中断–MMU必须关闭•UBoot如何给内核传递参数?–UBoot和内核交互是单向的,两个程序不能同时运行,那么要实现参数传递只能通过把参数存放到一个固定内存位置然后调用theKernel函数给对R0,R1,R2寄存器赋值。
Linux启动调用start_kernel函数读取内容即可。
do_bootm_linux函数•do_bootm_linux是UBoot真正启动Linux的函数(位于UBoot所在目录/lib_arm/bootm.c)int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images){bd_t*bd = gd->bd;char*s;int machid = bd->bi_arch_number;void(*theKernel)(int zero, int arch, uint params);..................theKernel = (void (*)(int, int, uint))images->ep; //传递Linux内核首地址theKernel (0, machid, bd->bi_boot_params); //启动Linux内核theKernel函数•参数 zero:设置为0 R0寄存器为0•参数:arch 表示机器存储于R1寄存器中•参数:params用于传递给Linux的参数地址(包括命令行参数信息)(*theKernel)(int zero, int arch, uint params);启动参数•Linux内核启动的时候并不会自己去扫描硬件的基本信息,因为它启动时都已经假设硬件都已经准备好了,但是使用过程中不可避免的要用到硬件信息,这些信息包括内存大小,串口配置信息等等。
那么这信息内核是如何获的呢?–编译Linux内核时可以通过make menuconfig命令进行命令行参数的配置–Uboot引导的时候传递启动参数参数地址给内核。
命令行参数参数信息•u-boot要传递给内核的参包含哪些内容?–MACH_TYPE(即我们所说的机器码)、–命令行参数CommandLine–系统根设备信息(标志,页面大小)、–内存信息(起始地址,大小)、–RAMDISK信息(起始地址,大小)、压缩的RAMDISK根文件系统信息(起始地址,大小)。
由此可见要传递的参数很多tag_list参数列表•UBoot如何组织这么多的参数信息呢?–UBoot使用struct tage 和struct tag_head来描述这些参数信息。
存放位置(include/asm-arm/setup.h)–相对应的在Linux内核源代码中也有完全相同的结构体定义存放位置(linux/arch/arm/include/asm/setup.h)–#define ATAG_CORE0x54410001–#define ATAG_MEM0x54410002–#define ATAG_VIDEOTEXT0x54410003–#define ATAG_RAMDISK0x54410004–#define ATAG_INITRD0x54410005–#define ATAG_SERIAL0x54410006–#define ATAG_REVISION0x54410007–#define ATAG_CMDLINE0x54410009tag结构体struct tag {struct tag_header hdr;union {struct tag_core core;//列表的开始struct tag_mem32mem;//描述内存信息//videotext; //struct tag_videotext videotext;struct tag_ramdisk ramdisk;struct tag_initrd initrd;//串口信息struct tag_serialnr serialnr;serialnr; ////版本信息revision; //struct tag_revision revision;struct tag_videolfb videolfb;//命令行cmdline; //struct tag_cmdline cmdline;。
//内存时钟memclk; //struct tag_memclk memclk;} u;};struct tag_header {u32 size;//每个参数的大小u32 tag;//每个参数的类型};do_bootm_linux流程•在do_bootm_linux函数(Uboot/lib_arm/bootm.c)中定义函数指针theKernel函数指针,并通过读取Linux的内核镜像文件uImage前面的40字节内容获取Linux内核的入口地址–theKernel = (void (*)(int, int, uint))images->ep;•通过getenv函数分别获取机器码和命令行参数信息信息•通过setup_start_tag函数开始参数列表信息•分别通过setup_xxx_tag函数把命令行信息,内存信息,串口信息分别写入参数列表中。
•最后调用cleanup_before_linux函数实现调用Linux前的准备工作•通过调用theKernel函数实现启动Linux内核。
bootm代码argv[], bootm_headers_t **images)int do_bootm_linux(int flag, int argc, char **argv[], bootm_headers_tint do_bootm_linux(int flag, int argc, char{bd_t*bd = gd->bd;char*s;int machid = bd->bi_arch_number;void(*theKernel)(int zero, int arch, uint params);char **commandline = getenv ("bootargs");char。
theKernel = (void (theKernel = (void (**)(int, int, uint))images->ep;s = getenv ("machid");。
setup_start_tag (bd);setup_serial_tag (¶ms);setup_revision_tag (¶ms);setup_memory_tags (bd);setup_commandline_tag (bd, commandline);if (images->rd_start && images->rd_end)setup_initrd_tag (bd, images->rd_start, images->rd_end);setup_videolfb_tag ((gd_tsetup_videolfb_tag ((gd_t **) gd);setup_end_tag (bd);cleanup_before_linux ();theKernel (0, machid, bd->bi_boot_params);return 1;}Linux内核的入口地址•搞清楚UBoot传递给Linux参数机制后,我们又有个新疑问,UBoot是如何知道Linux内核的入口地址呢?毕竟这个时候Linux内核还没启动•启动参数的位于内存具体哪个位置或者说谁决定了启动参数存放的位置?–UBoot工具mkimage的具体作用是什么?–UBoot是不能够直接引导Linux内核镜像文件,必须给内核镜像文件添加一个0x40字节的头部信息这样UBoot才能够引导Linux内核–那么这0x40字节的信息包含哪些内容?mkimage 结构体信息struct mkimage_params {int dflag;int eflag;int fflag;int lflag;int vflag;int xflag;int os;int arch;int type;int comp;char char **dtc;unsigned int addr;unsigned int ep;unsigned int ep; //Linux//Linux 内核镜像的入口地址char char **imagename;char char **datafile;char char **imagefile;char char **cmdname;};•mkimage 程序通过-e 参数写入Linux 内核的镜像入口地址,而这入口地址被以结构体的形式写入镜像文件的头部0x40字节的位置中,UBoot 利用image_get_ep 函数从内核文件中读取入口地址UBoot 源码结构开发使用文档文档Doc存放制作S-Record 或者 U-Boot 格式的映像等工具,例如mkimage 工具tools 一些独立运行的应用程序的例子,例如helloworld应用例程examples 数字温度测量器或者传感器的驱动通用Dtt RTC 的驱动程序通用Rtc 硬盘接口程序通用Disk 通用的设备驱动程序,主要有以太网接口的驱动通用drivers 存放上电自检程序通用Post 存放文件系统的程序通用Fs 存放网络的程序通用Net 通用库函数的实现通用lib_generic 通用的多功能函数实现通用common 头文件和开发板配置文件,所有开发板的配置文件都在configs 目录下通用include 存放对X86体系结构通用的文件,主要用于实现X86平台通用的函数平台依赖lib_i386存放对ARM 体系结构通用的文件,主要用于实现ARM 平台通用的函数平台依赖lib_arm Uboot 头文件平台依赖include 存放CPU 相关的目录文件,例如:mpc8xx 、ppc4xx 、arm720t 、arm920t 、 xscale 、i386等目录平台依赖cpu 存放电路板相关的目录文件,例如:RPXlite(mpc8xx)、smdk2410(arm920t)、sc520_cdp(x86) 等目录平台依赖board 解 释 说 明特 性目 录UBoot入口位置•UBoot是如何对编译好的二进制文件进行组合链接成一个可执行文件的?–lds链接文件用于描述如何组织和编译这些.o文件。