BootLoader基础详解

PC机的启动流程:上电——>BIOS——>引导操作系统——>识别盘符(C、D盘)——>运行应用程序。同样在嵌入式系统中,启动流程也是类似的:上电——>bootloader——>运行内核——>挂载根文件系统——>运行运用程序。PC机上引导的操作系统是Windows,而嵌入式中基本上是Linux内核、VxWorks、ucOS、wince等。

BootLoader最终的目的:引导内核。嵌入式中内核基本上是存放在flash中的,所以要运行内核就需要从flash中读取内核,然后放到SDRAM中运行。这样BootLoader最基本的能力需要能读取flash、写SDRAM以及执行内核。BootLoader流程:

  • 硬件相关的初始化:

  1. 设置模式(进入SVC)
  2. 初始化时钟(芯片刚上电的时候时钟都很低)
  3. 关闭中断(记住终极目的是引导内核、引导内核、引导内核。用不上的东西都需要关闭,别影响内核的引导)。
  4. 关闭看门狗(有些芯片看门狗默认是开启的,不喂狗隔一段时间芯片就会重启。而此时的任务是引导内核,重启要不得。)
  5. 初始化SDRAM,初始化flash(让flash可以读取数据)。
  6. 初始化串口(方便观察打印信息)、初始化网卡、USB等方便调试(可以通过USB来烧写内核、文件系统之类的)
  7. 设置栈(C运行必备的)
  • 加载内核到SDRAM
  • 运行内核

多阶段BootLoader的原因?

BootLoader终极目的是引导内核,加载内核到内存中然后再执行。具备更复杂的功能以及更好的可移植性的多阶段BootLoader。存在两个阶段:第一个阶段使用汇编来实现,它完成一些依赖于CPU体系结构的初始化,并调用第二阶段的代码硬件设备串口、时钟、看门狗、中断、RAM等的初始化、进入管理模式、复制BootLoader的第二段代码到RAM空间中、设置栈、清除BSS段{主要初始化值为0的全局变量和静态变量,不放入程序中占用额外的内存}、运行第二阶段的代码start_armboot);第二阶段则是使用C语言来实现,目的是可以实现更为复杂的功能以及提高可读性、移植性(本阶段使用到的硬件设备如网卡、nand、USB等硬件设备的初始化、检测系统内存映射以确定内存大小以及地址空间、将内核映像和根文件系统映像从flash中读到RAM中间中、为内核设置启动参数、执行内核)。

对于第二阶段中的读取flash上的镜像放到内存空间上,从什么地址读取,放到内核的什么地方,以及如何调用内核、内核执行需要满足什么条件?

由于内核镜像是烧写在flash中的固定位置,所以读取的时候也是从烧写进去的位置开始读取。通常的做法是给flash划分分区,存在在分区中,然后再从分区中读取出来。而要放入的地址,如果镜像是uImage原则上是除了不破坏u-boot的内存分布是可以随意放置的。因为uImage包含64字节的头部和Image,头部信息中会存有内核的加载地址以及入口地址(加载地址是:把Image放到内存中的地址,而入口地址是内核的执行起始地址。这两个地址都是在编译内核的时候确定的),如果u-boot发现了内核放在内存中的地址和uImage头部中的加载地址不一致的话,就会把Image移动到加载地址的地方。最后再调用Image的执行地址执行内核。要是镜像是Image,也可以加载到不破坏u-boot内存分布的内存中,然后在执行加载地址处的程序。

内核执行要满足的条件:由于BootLoader加载内核后,就进入了内核程序,BootLoader里面的程序再也看不到了所有需要把内核需要的参数(比如根文件系统存放在什么地方,是什么格式的以及内核起来后执行的第一个程序)传递给内核。通常的方法是在内存中指定一块空间,然后按照一定的格式把参数保存在里面,最后把参数的地址告诉内核。内核运行时需要访问些硬件资源,需要进入特权模式(SVC)。内核支持众多的CPU以及ARCH,所以还需要把机器ID告诉内核。最后还需要把第一个传给内核的参数设置为0。

       这里分析下第二个阶段的流程。第二阶段是从start_armboot()开始,里面主要是初始化flash,和各种初始化(CPU、board、中断、环境变量等)———>然后调用main_loop(此函数里面主要执行getenv()获取环境变量,在调用run_command()执行相应的函数。在获取bootcmd环境变量后,里面有内核程序的执行地址)。———>最后执行getenv(bootcmd),然后调用run_conmand()执行获取到的bootcmd环境变量值,执行do_bootm()函数里面的在这个函数里面有把从uImage头部中解析出来的内核入口地址转化为函数指针,调用一些列的宏把bootargs里面的参数保存在内存中,最后调用函数指针执行内核(由于BootLoader是单机,执行这个之后再也不会回来了,所以需要把内核启动时的一些参数传递给内核。参数R0:0,R1:设置为机器码,R2:设置为BootLoader给内核传递参数的内存地址)。

u-boot中执行指令时,是根据name来匹配找到相应的函数,所有的指令都是通过下面的封装来实现的。

这个 U_BOOT_CMD的宏定义了一个cmd_tbl_t带有属性的变量,里面存放有指令的名字,指令的最大参数个数以及是否可以重复执行、还有处理函数以及帮助信息等。

在开发时,通常需要使用各种命令操作BootLoader,一般是通过串口连接PC和开发板。可以在串口上输入各种命令以及观察运行结果等。这个对开发人员有帮助。而平常客户使用时压根不需要操作BootLoader。所以从这个观点来看,bootloader分为以下两种模式:启动加载模式(客户使用)和下载模式(开发人员使用)。下载模式中主要有串口协议xmodem/ymodem/zmodem、网络协议TFTP以及nsf。

BootLoader中常用的有u-boot、vivi、redboot等。u-boot源码获取:http://sourceforge.net/projects/U-Boot。

u-boot(universal boot loader)中代码的结构主要有:

平台相关:cpu/  lib_arm/ 里面存放这各种架构的CPU代码以及lib库。

开发板相关:board/ 里面存放着各种板级相关的代码。

通用函数:include、lib_generic、common。include中存放头文件以及开发板的配置文件。开发板的配置文件都存放在include/configs中。需要手动修改配置文件中的宏定义。

其它通用部分:disk(硬盘接口程序)、drivers(各类具体设备的驱动程序)、dtt(数字温度测量器驱动)、fs(文件系统)、net(各种网络协议)、post(上电自检程序)、RTC(实时时钟驱动)、nand_spl(nand启动)

工具、示例、文档:tools(mkimage制作镜像的工具)、doc(文档)、example(示例程序)。

u-boot操作流程: 

在u-boot1.1.6版本中,需要先配置u-boot,使用make xxx_config进行配置,配置好了再make进行编译。配置的时候执行Makefile文件中的:,$0 -$6 分别是配置目标名、arm arm920t smdk2400 null s3c24x0。这个文件的主要目的是根据传进来的参数在include文件夹中建立asm、asm-$2/arch、asm-$2/proc连接,以及生成config.mk(里面存在着ARCH(arm) CPU(arm920t) BOARD(smdk2400) VENDOR(null) SOC(s3c24x0)等)和config.h文件。

编译时会使用配置生成的文件来选择CROSS_COMPILE,然后把相关的库文件都加入到LIBS中,链接标志为,链接脚本为(顶级config.mk中的指定)u-boot.lds。链接地址为:是在相关的板子里面的config.mk中定义的。

u-boot1.1.6中没有类似内核的图形化界面配置,所以配置u-boot的时候是通过make <board_name>_config命令来配置的,<board_name>是在目录board/中定义的,里面存放有编译链接脚本以及链接时正文段的地址(保存在config.mk中)。然后在执行make 或者是make all来编译生成u-boot.bin、u-boot等文件,在编译成功后会在tools目录中生成一些工具如(mkimage,编译内核是可以使用上,用mkimage来生成u-boot格式的内核镜像文件uImage)。

配置u-boot的时候首先调会用顶层目录下的mkconfig文件,mkconfig的作用是:①:确定开发板名称BOARD_NAME②:创建到平台/开发板相关的头文件的链接。③:创建include/config.mk文件,这个文件里面主要包含开发板所使用的ARCH CPU BOARD SOCmake编译的时候会根据这个来确定编译链以及库文件等。④创建开发板的头文件include/config.h,里面的内容是#include<configs/$1.h>,这里选择编译开发板的配置文件。里面存放开发板的一些配置如CONFG_前缀是选择项,即是否使用,还有CFG_的前缀是参数的设置,编译的时候几乎所有的文件都会被编译和链接,但是文件是否包含有效的代码则有这些宏开关来设置。

配置完成后,执行make all进行编译。此过程如下:首先把配置生成的include/config.mk文件包含进来(即使用的ARCH CPU BOARD SOC)——>再包含顶层目录中的config.mk文件,此文件会根据配置生成的config.mk文件的值确定编译器、编译选项以及BOARDDIR等。

发布了35 篇原创文章 · 获赞 1 · 访问量 1870

猜你喜欢

转载自blog.csdn.net/lzj_linux188/article/details/102556489