内容来自 韦东山《嵌入式Linux应用开发完全手册》

一、Bootloader的引出

当系统上电时,并不是直接进入Linux系统的,而是需要先执行一段程序来把单片机的硬件外围初始化好,比如:看门狗、单片机时钟、存储控制器等。如果这段程序能将操作系统内核复制到内存中运行,无论是从本地(比如Flash)还是从远端(比如网络)就称这段程序位Bootloader。

简单地说,Bootloader就是上电时就开始执行,初始化硬件设备为操作系统准备好执行环境,最后调用操作系统内核。

Bootloader还可以具有通过串口、网络、USB等通信路径烧写操作系统内核的功能。

二、Bootloader的结构和启动过程

一般嵌入式系统的存储结构如下:

最前面的是Bootloader,上电后首先执行它。

第二段是参数,是内核执行需要的参数或Bootloader传递给内核的数据。Bootloader把数据保存到这个区域,然后进入内核后,内核去这个区域读,这样就完成了数据从Bootloader到内核的传递。

第三段是内核映像,单片机由Bootloader进入这里,进入后就开始调度各种任务,执行应用程序,开始进入正常运行状态。

第四段是文件系统,其中保存着内核运行需要的应用程序、库等数据。

三、Bootloader的两个阶段

Bootloader的执行过程又分为两个阶段,第一阶段用汇编来实现,完成一些简单的功能,为第二阶段做准备。第二阶段用C语言来实现,这样可以实现一些复杂一点的功能,而且代码具有更好的可读性和移植性。而具体第一阶段和第二阶段各自执行那些功能,可以由开发者自己分配。

四、U-Boot的源码结构

U-Boot是Bootloader的一种,且是开源的,其源码可以在这里下载到:ftp://ftp.denx.de/pub/u-boot/。现在已经更新到很新的版本了。但是本文依然用U-B00t-1.1.6来介绍,因为此版本资料最多,更新版本的U-Boot只是增加了更多的开发板和CPU支持而已,因此选择的时候不用太纠结于U-Boot的版本,能选择到支持自己开发板或CPU的U-Boot最好,没有就只能自己移植了。

下载后,打开文件夹,目录结构如下:

一般移植U-BOOT会修改绿色部分的代码,这些文件夹也有4个层次:

最上层是Lib和common,其中是一些库函数、各种命令。调用关系是从上往下的。比如common里的cmd_nand.c文件中提供了操作NAND Flash的各种命令,当执行这些命令,命令会调用drivers文件夹里的nand/nand_base.c中的擦除、读写函数来实现。nand/nand_base.c中的函数只是针对NAND Flash的共性做了封装,与硬件平台相关的代码都用宏或外部函数来代替,这样可以保证所有的CPU,nand/nand_base.c中的代码都是相同的。nand/nand_base.c中调用宏或外部函数又在cpu或board文件夹中实现。

五、U-Boot的使用方法

从网上下载了一个版本的U-Boot后,我们去board文件夹中找我们的开发板名字,board文件夹中的的每一个子文件夹都是一种开发板,如果在其中找到了,那就不用移植了,直接使用即可。

当我们在board文件夹中找到自己的开发板后,把名字记下来,比如“smdk2410”。然后在根目录下执行

make <board_name>_config

命令,其中的“board_name”即为刚才记下的名字,因此这里是执行:

make smdk2410_config

然后执行

make all

即可完成U-Boot的编译,编译后会生成3个文件:

U-Boot.bin:二进制可执行文件,它就是可以直接烧入ROM、NORFlash的文件。
U-Boot:ELF格式的可执行文件。
U-Boot.srec:Motorola S-Record格式的可执行文件。

我们把上面生成的U-Boot.bin烧录到CPU的Flash中,让CPU上电执行它即可,这样关于U-Boot的工作就完成了。

如果没有现成的,那就只能自己移植了,那我们就来开始分析U-Boot吧。

六、make smdk2410_config命令的解析

上面我们执行了两条命令:

make smdk2410_config
make all

执行这两条命令后,make工具会去根目录下的Makefile中查询到底要做哪些事情。因此我们打开Makefile,然后搜索“smdk2410_config”,看一下这条命令的内容是什么(可以先大概看下Makefile的语法:链接):

smdk2410_config  :   unconfig@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0

命令的名字(目标)是:smdk2410_config,依赖是:unconfig,命令是:@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0。根据Makefile的语法,我们先来看看依赖是不是文件,搜索unconfig,发现:

unconfig:@rm -f $(obj)include/config.h $(obj)include/config.mk \$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp

说明unconfig是命令,它的作用是删除以前编译过程中产生的文件。再来看命令内容,“@$(MKCONFIG)”最前面的”@“的作用是不输出命令内容到命令行中,”$(MKCONFIG)”是引用参数值,搜索“MKCONFIG”:

MKCONFIG := $(SRCTREE)/mkconfig

搜索“SRCTREE”

SRCTREE      := $(CURDIR)

CURDIR是make的内嵌变量,其值为当前目录,也就是U-Boot的根目录。所以“@$(MKCONFIG)”就等于“mkconfig”。 ”$(@:_config=)“中“$(@)”的作用与“$@”相同,所以可以变为:“$@:_config=”。“$@”等于目标文件的完整名称。

所以“$@:_config=”即为“smdk2410_config:_config=”,意思是让“smdk2410_config”中的“_config”等于“”,那就变为了“smdk2410”。

因此

 @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0

就变为了:

 mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0

因此我们执行“make smdk2410_config”,就相当于在命令行中执行“mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令。

mkconfig是shell脚本文件,后面的smdk2410 arm arm920t smdk2410 NULL s3c24x0都是传入其中的参数。打开mkconfig,看到第六行:

# Parameters:  Target  Architecture  CPU  Board [VENDOR] [SOC]

说明了mkconfig文件的传入参数的意义。即:

smdk2410 (目标) arm(架构) arm920t(cpu) smdk2410 (开发板选型) NULL(供应商) s3c24x0(片上系统/芯片)

下面分成几个部分来说明mkconfig文件中代码的作用。

1、确定开发板的名称,即确定BOARD_NAME变量的值

11、12行是定义两个变量。

14到21行是while循环,循环条件是“$# -gt 0”,“$#”是传入的参数个数,“-gt”是大于的意思,因此只要传入参数的个数大于0,while循环就会执行。

15到20行是case语句,与c语言switch类似。“$1”是指传入的第一个参数。shift是作用是把传入的参数左移1次,比如传入了4个参数,执行shift后,传入参数就剩下了3个,第一个参数被丢弃了,同时原来的第二个参数变成了第一个参数。

16行的意思就是判断第一个参数是否等于“--”,如果等于就执行shift。

17行的意思是判断第一个参数是否等于“-a”,如果等于就执行shift,并且把“APPEND”的值改为“yes”。

23行,“||”是逻辑“或”,表示在它前面的语句执行失败了,才执行它后面的语句。现在BOARD_NAME仍然为空,因此会执行“||”后面的语句,即BOARD_NAME=smdk2410。

2、创建到平台/开发板相关的头文件的链接。

33行判断U-Boot的源代码目录和我们编译的目标文件目录是否相同。编译的目录可以放在其他地方,这样可以使源代码目录保持干净,他们的值在Makefile中修改。一般默认他们是相同的,所以执行else分支。

46到48行,先进入根目录下的include文件夹,然后删除其中名字为asm的文件,然后再次建立asm文件,并令它链接向asm-$2目录,即asm-arm。

51行删除asm-$2/arch目录,即asm-arm/arch。

53行,“-z”判断字符串是否为空。“-o”是逻辑或。因为$6为s3c24x0,既不为空,也不为NULL,于是执行else分支。

56行中,LNPREFIX为空,所以命令实际为“ln -s arch-s3c24x0 asm-arm/arch”,即令asm-arm/arch链接向arch-s3c24x0

第60、61行重新建立asm-arm/proc文件,并让它链接向proc-armv目录。

3、创建顶层Makefile包含的文件include/config.mk

对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,上面几行代码创建的config.mk文件内容如下:

ARCH = arm
CPU = arm920t
BOARD = smdk2410
SOC = s3c24x0

4、创建开发板相关的头文件include/config.h

APPEND维持原值“no”,所以config.h被重新建立,它的内容如下:

/* Automatically generated - do not edit */
#include <configs/smdk2410.h>"

现在总结一下,配置命令“make smdk2410_config”,实际的作用就是执行“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令。假设执行“./mkconfig $1 $2 $3 $4 $5 $6”命令,则将产生如下结果:
(1)开发板名称BOARD_NAME等于$1;
(2)创建到平台/开发板相关的头文件的链接:

ln -s asm-$2 asm
ln -s arch-$6 asm-$2/arch
ln -s proc-armv asm-$2/proc# 如果$2不是arm的话,此行没有

(3) 创建顶层Makefile包含的文件include/config.mk。

ARCH = $2
CPU = $3
BOARD = $4
VENDOR = $5# $5为空,或者是NULL的话,此行没有
SOC = $6# $6为空,或者是NULL的话,此行没有

(4)创建开发板相关的头文件include/config.h。

/* Automatically generated - do not edit */
#include <configs/$1.h>

从上面知道了,如果我们要编译smdk2410开发板的U-Boot,先要执行“make smdk2410_config”,执行这个命令相当于进行了4个步骤,前面3个步骤都是建立软链接和新建新的文件,不牵扯到源代码中已有的文件。只有第4个步骤,#include <configs/$1.h>,包含了include/configs中已有开发板的头文件。

在这个文件中对对应开发板的U-Boot进行裁剪和配置,比如这个smdk240开发板不需要某个功能,就在include/configs/smdk2410.h文件中把该功能的宏开关关闭。我们打开smdk2410.h发现其中有两类宏:

(1)一类是选项(Options),前缀为"CONFIG_",它们用于选择CPU、SOC、开发板类型,设置系统时钟、选择设备驱动等。比如:

#define CONFIG_ARM920T        1/* This is an ARM920T Core*/
#define CONFIG_S3C2410        1/* in a SAMSUNG S3C2410 SoC */
#define CONFIG_SMDK2410       1/* on a SAMSUNG SMDK2410 Board */
#define CONFIG_SYS_CLK_FREQ   12000000/* the SMDK2410 has 12MHz input clock */
#define CONFIG_DRIVER_CS8900  1/* we have a CS8900 on-board */

(2)另一类是参数(Setting),前缀为“CFG_”,它们用于设置malloc缓冲池的大小、U-Boot的提示符、U-Boot下载文件时的默认加载地址、Flash的起始地址等。比如:

#define CFG_MALLOC_LEN (CFG_ENV_SIZE + 128*1024)
#define CFG_PROMPT     "100ASK> "/* Monitor Command Prompt*/
#define CFG_LOAD_ADDR  0x33000000/* default load address*/
#define PHYS_FLASH_1   0x00000000 /* Flash Bank #1 */

从下面的编译、连接过程可知,U-Boot中几乎每个文件都被编译和连接,但是这些文件是否包含有效的代码,则由宏开关来设置。比如对于网卡驱动drivers/cs8900.c,它的格式为:

#include <common.h>/* 将包含配置文件include/config/<board_name>.h */
……
#ifdef CONFIG_DRIVER_CS8900
/* 实际的代码 */
……
#endif/* CONFIG_DRIVER_CS8900 */

如果定义了宏CONFIG_DRIVER_CS8900,则文件中包含有效的代码;否则,文件被注释为空。

可以这样粗糙地认为,“CONFIG_”除了设置一些参数外,主要用来设置U-Boot的功能、选择使用文件中的哪一部分;而“CFG_”用来设置更细节的参数。

七、make all命令的解析

执行make smdk2410_config只是完成对开发板的配置,相当于做了一些编译前的准备工作,接着执行make all才是真正的开始编译。

分析make all之前,先看下根目录Makefile中包含进了哪些文件:

117 include $(OBJTREE)/include/config.mk
118 export ARCH CPU BOARD VENDOR SOC
119
……
127 ifeq ($(ARCH),arm)
128 CROSS_COMPILE = arm-linux-
129 endif
……
163 # load other configuration
164 include $(TOPDIR)/config.mk
165

第117、164行用于包含其他的config.mk文件,第117行所要包含文件的就是在上面的配置过程中制作出来的 include/config.mk文件,其中定义了ARCH、CPU、BOARD、SOC等4个变量的值为arm、arm920t、smdk2410、 s3c24x0。

第164行包含顶层目录的config.mk文件,它根据上面4个变量的值确定了编译器、编译选项等。打开顶层目录的config.mk文件,其中对我们理解编译过程有帮助的是BOARDDIR、LDFLAGS的值,config.mk中:

88 BOARDDIR = $(BOARD)
……
91 sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk# include board specific rules
……
143 LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
……
189 LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)

所以BOARDDIR值为smdk2410。

在board/smdk2410/config.mk中,定义了“TEXT_BASE = 0x33F80000”。所以LDFLAGS的值包含有:

“-T board/smdk2410/u-boot.lds -Ttext 0x33F80000”。

继续往下看Makefile:

166
##################################################################### ####
167 # U-Boot objects....order is important (i.e. start must be first)
168
169 OBJS = cpu/$(CPU)/start.o
……
193 LIBS = lib_generic/libgeneric.a
194 LIBS += board/$(BOARDDIR)/lib$(BOARD).a
195 LIBS += cpu/$(CPU)/lib$(CPU).a
……
199 LIBS += lib_$(ARCH)/lib$(ARCH).a
200 LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a \
201 fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
202 LIBS += net/libnet.a
……
212 LIBS += $(BOARDLIBS)
213

从第169行得知,OBJS的值为“cpu/$(CPU)/start.o”,即“cpu/arm920t/start.o”。

第193~213行指定了LIBS变量就是平台/开发板相关的各个目录、通用目录下相应的库,比如:lib_generic /libgeneric.a、board/smdk2410/libsmdk2410.a、cpu/arm920t/libarm920t.a、 lib_arm/libarm.a、fs/cramfs/libcramfs.a fs/fat/libfat.a等。

OBJS、LIBS所代表的.o、.a文件就是U-Boot的构成文件,它们通过如下命令由相应的源文件(或相应子目录下的文件)编译得到。

268 $(OBJS):
269 $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
270
271 $(LIBS):
272 $(MAKE) -C $(dir $(subst $(obj),,$@))
273
274 $(SUBDIRS):
275 $(MAKE) -C $@ all
276

第268、269两行的规则表示,对于OBJS中的每个成员,都将进入cpu/$(CPU)目录(即cpu/arm920t)编译它们。现在OBJS为cpu/arm920t/start.o,它将由cpu/arm920t/start.S编译得到。

第271、272两行的规则表示,对于LIBS中的每个成员,都将进入相应的子目录执行“make”命令。这些子目录中Makefile结构相似,它们将Makefle中指定的文件编译、连接成一个库文件。

当所有的OBJS、LIBS所表示的.o和.a文件都生成后,就剩最后的连接了,这对应Makefile中如下几行:

246 $(obj)u-boot.srec:$(obj)u-boot
247 $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
249 $(obj)u-boot.bin:$(obj)u-boot
250 $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
251
……
262 $(obj)u-boot:depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
263 UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
264 cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
265 --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
266 -Map u-boot.map -o u-boot
267

先使用第262~266的规则连接得到ELF格式的u-boot,最后转换为二进制格式的u-boot.bin、S-Record格式的u- boot.srec。          LDFLAGS确定了连接方式,其中的“-T board/smdk2410/u-boot.lds -Ttext 0x33F80000”字样指定了程序的布局、地址。          board/smdk2410/u-boot.lds文件如下:

28 SECTIONS
29 {
30     . = 0x00000000;
31
32     . = ALIGN(4);
33     .text :
34     {
35         cpu/arm920t/start.o(.text)
36         *(.text)
37     }
38
39     . = ALIGN(4);
40     .rodata : { *(.rodata) }
41
42     . = ALIGN(4);
43     .data : { *(.data) }
44
45     . = ALIGN(4);
46     .got : { *(.got) }
47
48     . = .;
49     __u_boot_cmd_start = .;
50     .u_boot_cmd : { *(.u_boot_cmd) }
51     __u_boot_cmd_end = .;
52
53     . = ALIGN(4);
54     __bss_start = .;
55     .bss : { *(.bss) }
56     _end = .;
57 }

从第35行可知,cpu/arm920t/start.o被放在程序的最前面,所以U-Boot的入口点在cpu/arm920t/start.S中。

现在来总结一下U-Boot的编译流程:

(1)首先编译cpu/$(CPU)/start.S,对于不同的CPU,还可能编译cpu/$(CPU)下的其他文件。

(2)然后,对于平台/开发板相关的每个目录、每个通用目录,都使用它们各自的Makefile生成相应的库。

(3)将1、2步骤生成的.o、.a文件按照board/$(BOARDDIR)/config.mk文件中指定的代码段起始地址、board/$(BOARDDIR)/u-boot.lds连接脚本进行连接。

(4)第3步得到的是ELF格式的U-Boot,后面Makefile还会将它转换为二进制格式、S-Record格式。

八、U-Boot的启动过程

首先强调,本书使用的U-Boot从NOR Flash启动,下面以开发板smdk2410的U-Boot为例。

U-Boot属于两阶段的Bootloader,第一阶段的文件为cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平台相关,后者是开发板相关。

1、U-Boot第一阶段代码分析

(1)硬件设备初始化。

依次完成如下设置:将CPU的工作模式设为管理模式(svc),关闭WATCHDOG,设置FCLK、HCLK、PCLK的比例(即设置CLKDIVN寄存器),关闭MMU、CACHE。

代码都在cpu/arm920t/start.S中,注释也比较完善。

(2)为加载Bootloader的第二阶段代码准备RAM空间。

所谓准备RAM空间,就是初始化内存芯片,使它可用。对于S3C2410/S3C2440,通过在start.S中调用lowlevel_init函数来设置存储控制器,使得外接的SDRAM可用。代码在board/smdk2410/lowlevel_init.S中。

注意:lowlevel_init.S文件是开发板相关的,这表示如果外接的设备不一样,可以修改lowlevel_init.S文件中的相关宏。

lowlevel_init函数并不复杂,只是要注意这时的代码、数据都只保存在NOR Flash上,内存中还没有,所以读取数据时要变换地址。代码如下:

129 _TEXT_BASE:
130 .wordTEXT_BASE
131
132 .globl lowlevel_init
133 lowlevel_init:
134     /* memory control configuration */
135     /* make r0 relative the current location so that it */
136     /* reads SMRDATA out of FLASH rather than memory ! */
137     ldr r0, =SMRDATA
138     ldr r1, _TEXT_BASE
139     sub r0, r0, r1
140     ldr r1, =BWSCON/* Bus Width Status Controller */
141     add r2, r0, #13*4
142 0:
143     ldr r3, [r0], #4
144     str r3, [r1], #4
145     cmp r2, r0
146     bne 0b
147
148     /* everything is fine now */
149     mov pc, lr
150
151     .ltorg
152 /* the literal pools origin */
153
154 SMRDATA:/* 13个寄存器的值 */
155     .word ……
156     .word ……

第137~139行进行地址变换,因为这时候内存中还没有数据,不能使用连接程序时确定的地址来读取数据。

第137行中SMRDATA 表示这13个寄存器的值存放的开始地址(连接地址),值为0x33F8xxxx,处于内存中。

第138行获得代码段的起始地址,它就是第130行中的“TEXT_BASE”,其值在board/smdk2410/config.mk中定义:“TEXT_BASE = 0x33F80000”。

第139行将0x33F8xxxx与0x33F80000相减,这就是13个寄存器值在NOR Flash上存放的开始地址。

(3)拷贝Bootloader的第二阶段代码到 RAM 空间中。

这里将整个U-Boot的代码(包括第一、第二阶段)都复制到SDRAM中,这在cpu/arm920t/start.S中实现:

164 relocate:                /* 将U-Boot复制到RAM中 */
165     adrr0, _start        /* r0 = 当前代码的开始地址 */
166     ldrr1, _TEXT_BASE    /* r1 = 代码段的连接地址 */
167     cmp r0, r1           /* 测试现在是在Flash中还是在RAM中 */
168     beq stack_setup      /* 如果已经在RAM中(这通常是调试时,直接下载到RAM中),* 则不需要复制*/
169
170     ldrr2, _armboot_start /* _armboot_start在前面定义,是第一条指令的运行地址 */
171     ldrr3, _bss_start     /* 在连接脚本u-boot.lds中定义,是代码段的结束地址 */
172     subr2, r3, r2         /* r2 = 代码段长度 */
173     addr2, r0, r2         /* r2 = NOR Flash上代码段的结束地址 */
174
175 copy_loop:
176     ldmiar0!, {r3-r10}    /* 从地址[r0]处获得数据 */
177     stmiar1!, {r3-r10}    /* 复制到地址[r1]处 */
178     cmpr0, r2             /* 判断是否复制完毕 */
179     blecopy_loop          /* 没复制完,则继续 */

(4)设置好栈。

栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存即可。

182 /* Set up the stack */
183 stack_setup:
184     ldr r0, _TEXT_BASE             /* _TEXT_BASE为代码段的开始地址,值为0x33F80000 */
185     sub r0, r0, #CFG_MALLOC_LEN    /* 代码段下面,留出一段内存以实现malloc */
186     sub r0, r0, #CFG_GBL_DATA_SIZE /* 再留出一段内存,存一些全局参数 */
187 #ifdef CONFIG_USE_IRQ
188     sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) /* IRQ、FIQ模式的栈 */
189 #endif
190     sub sp, r0, #12 /* 最后,留出12字节的内存给abort异常,* 往下的内存就都是栈了*/
191

到了这一步,读者可以知道内存的使用情况了,如下图所示(图中与上面的划分稍有不同,这是因为在cpu/arm920t/cpu.c中的cpu_init函数中才真正为IRQ、FIQ模式划分了栈):


图15.3 U-Boot内存使用情况

(5)跳转到第二阶段代码的C入口点。

在跳转之前,还要清除BSS段(初始值为0、无初始值的全局变量、静态变量放在BSS段),代码如下:

192 clear_bss:
193     ldr r0, _bss_start  /* BSS段的开始地址,它的值在连接脚本u-boot.lds中确定 */
194     ldr r1, _bss_end    /* BSS段的结束地址,它的值在连接脚本u-boot.lds中确定 */
195     mov r2, #0x00000000
196
197 clbss_l:str r2, [r0]    /* 往BSS段中写入0值 */
198     add r0, r0, #4
199     cmp r0, r1
200     ble clbss_l
201

现在,C函数的运行环境已经完全准备好,通过如下命令直接跳转(这之后,程序才在内存中执行),它将调用lib_arm/board.c中的start_armboot函数,这是第二阶段的入口点:

223      ldr pc, _start_armboot
224
225 _start_armboot:.word start_armboot
226

2、U-Boot第二阶段代码分析

它与15.1.2节中描述的Bootloader第二阶段所完成的功能基本上一致,不过顺序有点小差别。另外,U-Boot在启动内核之前可以让用户决定是否进入下载模式,即进入U-Boot的控制界面。

第二阶段从lib_arm/board.c中的start_armboot函数开始,先看从这个函数开始的程序流程图,图15.3所示。

移植U-Boot的主要工作在于对硬件的初始化、驱动,所以下面讲解时将重点放在硬件的操作上。

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

最主要的是设置系统时钟、初始化串口,只要这两个设置好了,就可以从串口看到打印信息。

board_init函数设置MPLL、改变系统时钟,它是开发板相关的函数,在board/smdk2410/smdk2410.c中实现。值得注意的是,board_init函数中还保存了机器类型ID,这将在调用内核时传给内核,代码如下:

/* arch number of SMDK2410-Board */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; /* 值为193 */

串口的初始化函数主要是serial_init,它设置UART控制器,是CPU相关的函数,在cpu/arm920t/s3c24x0/serial.c中实现。

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

对于特定的开发板,其内存的分布是明确的,所以可以直接设置。board/smdk2410/smdk2410.c中的dram_init函数指定了本开发板的内存起始地址为0x30000000,大小为0x4000000。代码如下:

int dram_init (void)
{gd->bd->bi_dram[0].start = PHYS_SDRAM_1;    /* 即0x300000000 */gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;/* 即0x4000000 */return 0;
}

这些设置的参数,将在后面向内核传递参数时用到。

(3)U-Boot命令的格式。

从图15.3可以知道,即使是内核的启动,也是通过U-Boot命令来实现的。U-Boot中每个命令都通过U_BOOT_CMD宏来定义,格式如下:

U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")

各项参数的意义为:

① name:命令的名字,注意,它不是一个字符串(不要用双引号括起来)。

② maxargs:最大的参数个数

③ repeatable:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次运行。

④ command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s *, int, int, char *[])。

⑤ usage:简短的使用说明,这是个字符串。

⑥ help:较详细的使用说明,这是个字符串。

宏U_BOOT_CMD在include/command.h中定义:

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) /
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

Struct_Section也是在include/command.h中定义:

#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))

比如对于bootm命令,它如此定义:

U_BOOT_CMD(bootm,CFG_MAXARGS,1,do_bootm,“string1”,“string2”
);

宏U_BOOT_CMD扩展开后就是:

cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) = {“bootm”, CFG_MAXARGS, 1, do_bootm, “string1”, “string2”};

对于每个使用U_BOOT_CMD宏来定义的命令,其实都是在".u_boot_cmd"段中定义一个cmd_tbl_t结构。连接脚本u-boot.lds中有如下代码:

__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;

程序中就是根据命令的名字在内存段__u_boot_cmd_start~__u_boot_cmd_end找到它的cmd_tbl_t结构,然后调用它的函数(请参考common/command.c中的find_cmd函数)。
        内核的复制和启动,可以通过如下命令来完成:bootm从内存、ROM、NOR Flash中启动内核,bootp则通过网络来启动,而nboot从NAND Flash启动内核。它们都是先将内核映像从各种媒介中读出,存放在指定的位置;然后设置标记列表以给内核传递参数;最后跳到内核的入口点去执行。具体实现的细节不再描述,有兴趣的读者可以阅读common/cmd_boot.c、common/cmd_net.c、common/cmd_nand.c来了解它们的实现。
(4)为内核设置启动参数。

与15.1.2小节中《Bootloader与内核的交互》所描述的一样,U-Boot也是通过标记列表向内核传递参数。并且,15.1.2小节中内存标记、命令行标记的示例代码就是取自U-Boot中的setup_memory_tags、setup_commandline_tag函数,它们都是在lib_arm/armlinux.c中定义。一般而言,设置这两个标记就可以了,在配置文件include/configs/smdk2410.h中增加如下两个配置项即可:

#define CONFIG_SETUP_MEMORY_TAGS 1
#define CONFIG_CMDLINE_TAG       1

对于ARM架构的CPU,都是通过lib_arm/armlinux.c中的do_bootm_linux函数来启动内核。这个函数中,设置标记列表,最后通过“theKernel (0, bd->bi_arch_number, bd->bi_boot_params)”调用内核。其中,theKernel指向内核存放的地址(对于ARM架构的CPU,通常是0x30008000),bd->bi_arch_number就是前面board_init函数设置的机器类型ID,而bd->bi_boot_params就是标记列表的开始地址。

U-Boot移植教程之一:U-Boot分析与启动过程相关推荐

  1. Spring Boot实践教程(二):SpringApplication分析

    2019独角兽企业重金招聘Python工程师标准>>> 本文会通过分析上一篇中跑起来的示例程序来分析一下Spring Boot程序运行的基本原理. 概要 在上一篇的介绍中,程序是通过 ...

  2. Nimbus三Storm源码分析--Nimbus启动过程

    Nimbus server, 首先从启动命令开始, 同样是使用storm命令"storm nimbus"来启动 看下源码, 此处和上面client不同, jvmtype=" ...

  3. 飞鸽传书源码分析-程序启动过程

    本文章是在飞鸽传书的2.06源码基础上分析 飞鸽传书源码运行流程如下,本篇文章只说明了飞鸽传书的启动过程,对于飞鸽伟书的消息机制及菜单加载等功能都不在本篇文章范围之内. 1. WinMain函数 [c ...

  4. elasticSearch6源码分析(1)启动过程

    1.找到bin目录,下面有elasticSearch的sh文件,查看执行过程 exec \"$JAVA" \$ES_JAVA_OPTS \-Des.path.home=" ...

  5. 【Java】【Flume】Flume-NG源代码分析的启动过程(两)

    本节分析配置文件的解析,即PollingPropertiesFileConfigurationProvider.FileWatcherRunnable.run中的eventBus.post(getCo ...

  6. workerman源码分析之启动过程

    2019独角兽企业重金招聘Python工程师标准>>> http://www.cnblogs.com/CpNice/p/4714182.html 转载于:https://my.osc ...

  7. springboot做网站_Github点赞接近 100k 的Spring Boot学习教程+实战项目推荐!

    " 本文已经收录进:awesome-java (Github 上非常棒的 Java 开源项目集合) 很明显的一个现象,除了一些老项目,现在 Java 后端项目基本都是基于 Spring Bo ...

  8. map文件分析 stm32_浅谈STM32的启动过程

    分享这篇文章,谈一下STM32启动流程.如果读者朋友已经有过汇编相关基础,能够够好理解本文内容.汇编语言是比C语言更接近机器底层的编程语言,能让我们更好的理解和操纵硬件底层. STM32三种启动模式 ...

  9. 【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 二 | AMS 进程相关源码 | 主进程相关源码 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

最新文章

  1. jQuery选择器总结
  2. 【AJAX】DWR使用总结
  3. 对于SAP的月结相关流程介绍
  4. 警惕:高考将至 著名高校网站被挂马
  5. 【小技巧】字符char与整型int的相互转换
  6. 计算机打印机无法扫描,佳能MF4752打印机无法扫描文件怎么办?
  7. QT5开发及实例学习之十二Qt5图像坐标变换
  8. iPhone 13外观四年以来首次改动:真的尽力了
  9. 编程之美- 中国象棋将帅问题
  10. tortoisegit 还原到某个版本
  11. Jmeter使用技巧集锦大全
  12. 项目经理:什么是矩阵型组织结构?
  13. 培养好习惯是很难,但也有方法
  14. 他一定幸福地生活在那里
  15. 解决arcgis地图选中的时候有白色边框的问题
  16. 亚马逊云计算服务将支持甲骨文数据库
  17. 3阶段魔方 2层以后口诀
  18. 中北c语言程序设计,中北大学软件学2013届C语言程序设计实训题目.doc
  19. Suzy找到实习了吗 Day 21 | 二叉树进行中:530. 二叉搜索树的最小绝对差,501. 二叉搜索树中的众数,236. 二叉树的最近公共祖先
  20. jmeter安装及使用基本教程

热门文章

  1. python实现高校教务管理系统_python+mysql实现教务管理系统
  2. 钉钉自定义机器人python_使用钉钉自定义机器人发送舔狗日记[70行][python]
  3. [UE4]网游中角色Pawn的移动位置同步以及RTS多角色同时移动的解决方案
  4. JAVA实现二维数组中的查找(《剑指offer》)
  5. 贝佐斯明抢马斯克太空生意:数十亿美元组卫星互联网,5年内发射3236颗卫星...
  6. Meta AI推出“杂食者”:一个模型搞定图像、视频和3D数据三大分类任务,性能还不输独立模型...
  7. 日本发明的“舔屏尝味”电视火了:伸个舌头可尝酸甜苦辣,网友一时不知如何评价...
  8. 这个机器人一个表情,看过的人不寒而栗
  9. 用边缘计算为智能制造提速,行业的破局者是他们
  10. 花33元租号玩2小时王者荣耀,未成年为绕过防沉迷用上黑科技上号器App