这东西已经写,我们没有时间发布,如今,终于有时间稍微长送记录汇总uboot学习过程。具体了。以后忘了也能够再温习回来嘛
有些特殊字符显示得乱掉了
Makefile追踪技巧:
技巧1:能够先从编译目标開始顺藤摸瓜地分析,先不要关注详细细节,着重关注基本的代码结构和编译过程
技巧2:追踪分析时要通过文本或者其它途径临时记录重要的线索
为什么要去研究uboot的Makefile?由于要研究一个project代码,最好的方法就是阅读其Makefile,Makefile会告诉我们project的模块结构以及目标的详细编译和连接过程,读懂了Makefile无疑就是对project结构和代码结构了如指掌。相当于一张地图,是移植以及学习其技术的基础。
在这里,是以smart210的uboot_smart210作为剖析对象。其原生uboot版本号是u-boot-2011.06。uboot的全部Makefile有2000行左右的代码,当中的关系也比較复杂,要想读懂其Makefile,必需要对Make工具的的语法规则和shell脚本比較熟悉,这是阅读的基础,里面非常多非常关键的地方用到了grep、sed和awk等文本处理工具,当然能够边看边学。
此外。还要善于使用搜索,最好能够自己写一些简单的自己主动搜索脚本,由于Makefile里面有非常多变量并非在同一个文件中面定义或赋值,善用搜索。必然能够节约非常多的精力。
怎样编译smart10的uboot?
编译smart210的uboot命令是:
make samrt210
这是相对于曾经的老版本号进行的改进,老版本号一般是现用make xx_config进行配置,然后再用make进行编译,当然后来的版本号也依旧能够使用这种方式进行编译。后来版本号的Makefile结构有了较大的变动。
Make主要过程
在uboot的整个编译过程中,源代码根文件夹下的主Makefile被make读取运行了两次。第一次是完毕编译环境的配置以及相关编译參数的配置;第二次是開始真正的编译过程。
u-bootMakefile主要流程
第一次读入运行Makefile:
1.产生编译目标和对应规则。
从编译命令入手,可是我们在Makefile中根本就无法搜索到smart210的字眼,更不要说smart210这个目标。在代码根文件夹搜索会发现,smart210的字眼出如今boards.cfg。
原来是uboot为了易于拓展对平台和各种开发板,将各种开发板的相关信息集中放在这个文件里,例如以下图:
smart210 arm armv7 smart210 samsung s5pc1xx
那么这个文件是怎么被引入到Makefile中的呢?
在这里:
sinclude $(obj).boards.depend
$(obj).boards.depend: boards.cfg
awk'(NF && $$1 !~ /^#/) { print $$1 ": " $$1 "_config;$$(MAKE)" }' $< > $@
要理解3句语句,必需要知道make的工作过程。
运行make smart210指令时,make会把全部引用的文件展开到Makefile中,假设发现有文件无法找到时便会查找使用对应规则来尝试创建对应的文件。
sinclude$(obj).boards.depend这句语句运行时,代码文件夹下是没有这个文件的,那么make便运行
$(obj).boards.depend: boards.cfg
awk'(NF && $$1 !~ /^#/) { print $$1 ": " $$1 "_config;$$(MAKE)" }' $< > $@
规则来生成.boards.depend文件,.boards.depend是依赖boards.cfg来生成的,当中的内容类似为:
……….
smart210: smart210_config; $(MAKE)
smdkc100: smdkc100_config; $(MAKE)
………..
可见。各种开发板的编译目标和规则现都已产生,而且被引入到Makefile中。因此此时Makefile中便已存在各种开发板的编译目标和规则。
2.配置编译环境。
在(1)中生成了编译目标和对应的规则:
smart210: smart210_config; $(MAKE)
这句规则的依赖的规则是smart210_config,故先运行规则生成依赖。
smart210_config的生成规则:
%_config:: unconfig
@$(MKCONFIG)-A $(@:_config=)
其运行的命令就是:./mkconfig –A smart210,uboot的编译环境绝大部分就是在这个脚本中进行配置的。接下来看看这个脚本到底主要做了些什么事情。
if [ ( $# -eq 2 ) -a ( "$1" ="-A" ) ] ; then
#Automatic mode
##从boards.cfg抽取出smart210那一行信息
line=`egrep-i "^[[:space:]]*${2}[[:space:]]" boards.cfg` || {
echo"make: *** No rule to make target \`$2_config'. Stop." >&2
exit1
}
set${line} ##将line中的字符串分别赋值给位置參数$1,$2,$3.....
#add default board name if needed
[$# = 3 ] && set ${line} ${1}
fi
语句line=`egrep -i"^[[:space:]]*${2}[[:space:]]" boards.cfg`从boards.cfg抽取出smart210那一行信息。并保存到变量line中。set ${line}将line中的字符串分别赋值给位置參数$1,$2,$3.....,由boards.cfg中说保存的信息类型可知。这里相当于是将Target、ARCH、CPU、Board name 、Vendor、SoC等平台相关或者板级相关的将用于配置编译环境的必要信息提取了出来。
echo "ARCH = ${arch}" > config.mk
echo "CPU = ${cpu}" >> config.mk
echo "BOARD = ${board}" >> config.mk
[ "${vendor}" ] && echo"VENDOR = ${vendor}" >> config.mk
[ "${soc}" ] && echo "SOC = ${soc}" >> config.mk
上面的语句将提取的ARCH、CPU、BOARD、VENDOR、SOC保存到config.mk文件里。这个文件将被Makefile引用以获取平台相关或者板级相关的信息来进行编译。
……
else
cd./include
rm-f asm
ln-s ../arch/${arch}/include/asm asm
echo"ln -s ../arch/${arch}/include/asm asm"
fi
上面先删除曾经建立的连接,然后ln -s../arch/${arch}/include/asm asm在include文件夹下创建一个平台相关的头文件夹连接。
rm -f asm/arch
……
else
ln-s ${LNPREFIX}arch-${soc} asm/arch
echo"ln -s ${LNPREFIX}arch-${soc} asm/arch"
fi
上面先include/asm/文件夹下的旧的连接, 然后ln -s${LNPREFIX}arch-${soc} asm/arch在include/asm/中创建一个片上系统(也就是同一平台下的一类芯片)相关的连接。
…….
cat << EOF>> config.h
#defineCONFIG_BOARDDIR board/$BOARDDIR
#include<config_cmd_defaults.h>
#include<config_defaults.h>
#include<configs/${CONFIG_NAME}.h> #即#include<configs/smart210.h>
#include<asm/config.h>
EOF
最后,生成include/config.h,将配置相关的头文件集中在config.h中引用。
编译环境配置小结:
(1) 提取ARCH、CPU、BOARD、VENDOR、SOC等平台相关或者板级相关的信息保存到config.mk文件里备用。
(2) 在include文件夹下创建一个平台相关的头文件夹连接。
(3) 在include/asm/中创建一个片上系统相关的头文件连接。
(4) 将配置相关的头文件集中在config.h中引用。
3.生成Makefile编译时所依赖的配置文件及其依赖关系
Makefile编译时依赖的配置文件是include/autoconf.mk。其头文件依赖关系存放在include/autoconf.mk.dep文件里。那么这两个文件是怎么被引入到Makefile中的呢?
sinclude$(obj)include/autoconf.mk.dep
sinclude$(obj)include/autoconf.mk
make展开Makefile中上面这两句语句时,没有发现对应文件,便使用规则生成它们。
project中的文件的数量的是很庞大的数字。而每一个文件依赖的文件也是许多。如要要将每一个文件的依赖手工一一列出,那工作量是相当繁重的,也不利于代码的移植,因此uboot利用make工具和gcc编译器的特性自己主动生成以及维护文件依赖关系。Make支持多规则目标。可是仅仅能在一个规则中定义命令。换句话来说就是说同一目标的依赖能够不止出如今一个规则中,而gcc的选项-M可生成源文件和头文件的make可用的依赖关系,自己主动生成依赖关系就是利用了这个make特性和gcc的特性。
autoconf.mk.dep的生成规则例如以下:
$(obj)include/autoconf.mk.dep:$(obj)include/config.h include/common.h
@$(XECHO)Generating $@ ;
set-e ;
:Generate the dependancies ;
$(CC)-x c -DDO_DEPS_ONLY -M $(HOSTCFLAGS) $(CPPFLAGS)
-MQ$(obj)include/autoconf.mk include/common.h > $@
gcc的选项-M可用于生成文件的make可用的依赖关系,-MQ用于指定目标名。-x指定语言类型。
生成的autoconf.mk.dep的内容例如以下:
include/autoconf.mk:include/common.h /home/zqb/uboot/uboot_smart210/include/config.h
……..
显然,这就是autoconf.mk的依赖关系,autoconf.mk.dep中的规则仅仅有依赖关系而没有命令。
Uboot的配置主要是通过改动include/configs/中的头文件里宏来实现,有非常多的编译选项也是在当中改动,那么问题就来了,make工具可不认识头文件里的宏语法。所以Makefile中通过一些规则调用sed文本处理工具来生成我们想要的配置格式。
$(obj)include/autoconf.mk:$(obj)include/config.h
@$(XECHO) Generating $@ ;
set -e ;
: Extract the config macros ;
$(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dMinclude/common.h |
sed -n -ftools/scripts/define2mk.sed > $@.tmp &&
mv $@.tmp $@
上面的规则是利用sed工具来进行的,sed的工作方式由tools/scripts/define2mk.sed来指定,头文件里的配置方式是宏。如#define CONFIG_xx xxx。而成的make可用的配置的autoconf.mk文件格式例如以下:
……..
CONFIG_CMD_ITEST=y
CONFIG_CMD_BDI=y
CONFIG_SYS_MAX_NAND_DEVICE=y
……….
事实上就是将头文件里的宏定义转化为了make的变量作为控制开关。比如:
Ifeq($(CONFIG_CMD_ITEST),y)
#do_something
Endif
用上述的方式来生成Makefile编译时所依赖的配置文件及其依赖关系,便可实现自己主动生成相关依赖关系以及因相关的配置文件的修改而又一次编译project,不再须要人工去生成及维护其依赖关系。
第二次读入运行Makefile:
规则smart210: smart210_config;$(MAKE)完毕了依赖smart210_config更新后再次调用make,開始了第二次读入运行Makefile。
1.展开全部的配置文件的配置
主要展开的是:
sinclude $(obj)include/autoconf.mk.dep
sinclude $(obj)include/autoconf.mk #基本的编译配置都在这个文件里。故其很重要
…….
include $(obj)include/config.mk #平台相关或板级相关的信息
……
include $(TOPDIR)/config.mk
源代码根文件夹下的config.mk中语句主要完毕了依据配置项进行的编译选项的配置。如使用什么编译器、连接器,这些编译器、连接器的编译选项是什么:
AS = $(CROSS_COMPILE)as #汇编器
LD = $(CROSS_COMPILE)ld #连接器
CC = $(CROSS_COMPILE)gcc #编译器
CPP = $(CC) -E #预处理
AR = $(CROSS_COMPILE)ar #归档器
………
此外。config.mk还包括了一些通用的源文件编译规则,例如以下:
$(obj)%.s: %.S
$(CPP) $(ALL_AFLAGS) -o $@ $<
$(obj)%.o: %.S
$(CC) $(ALL_AFLAGS) -o $@ $< -c
$(obj)%.o: %.c
$(CC) $(ALL_CFLAGS) -o $@ $< -c
$(obj)%.i: %.c
$(CPP) $(ALL_CFLAGS) -o $@ $< -c
$(obj)%.s: %.c
$(CC) $(ALL_CFLAGS) -o $@ $< -c –S
2.生成终于目标
第二次读入并运行Makefile并没有指定终极目标,故其终极目标默觉得第一个目标,也即是:
all:
sinclude $(obj)include/autoconf.mk.dep
……..
all: $(ALL-y)
你会发现,第一个目标all在Makefile中出现了两次。这是为什么呢?第一个目标规则仅仅有一个目标名,而没有依赖和命令,其出现是为了防止autoconf.mk.dep中的目标成为了终极目标,而后面出现的all: $(ALL-y)才是真正的终极目标。这里也利用了make支持多规则目标的特性。all: $(ALL-y)展开后:
all: u-boot.srec u-boot.binSystem.map spl/u-boot-spl.bin
那么便可知道,终于生成的目标有u-boot.srec u-boot.bin System.map spl/u-boot-spl.bin。
先看看当中两个目标的规则:
…….
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) -O srec $< $@
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -Obinary $< $@
$(BOARD_SIZE_CHECK)
……
你会发现终于的那几个目标都依赖u-boot目标,u-boot的规则例如以下:
$(obj)u-boot: depend
$(SUBDIRS) $(OBJS)$(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
$(GEN_UBOOT)
在这之后里,逐个调用依赖的生成规则继续进行编译,如depend、$(OBJS) $(LIBBOARD)等的生成。
源文件的编译看后面的“3.各个子文件夹下须要编译生成的目标文件被分门别类地编译”。
各源文件编译完毕后,连接生成目标u-boot:
GEN_UBOOT =
UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD)$(LIBS) |
sed -n -e 's/.*($(SYM_PREFIX)__u_boot_cmd_.*)/-u1/p'|sort|uniq`;
cd $(LNDIR) && $(LD) $(LDFLAGS)$(LDFLAGS_$(@F)) $$UNDEF_SYM $(__OBJS)
--start-group $(__LIBS) --end-group$(PLATFORM_LIBS)
-Map u-boot.map -o u-boot
elf格式的u-boot被连接生成后,u-boot.srec u-boot.bin System.map spl/u-boot-spl.bin就接着被生成了。其过程并不复杂。
3.各个子文件夹下须要编译生成的目标文件被分门别类地编译
uboot的開始运行的代码目标:
OBJS =$(CPUDIR)/start.o
uboot板级密切相关的代码编译打包为:
LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).o
其它的同一处理器通用的源文件打包为:
LIBS = lib/libgeneric.o
LIBS +=lib/lzma/liblzma.o
LIBS += lib/lzo/liblzo.o
…….
接下来看看怎样进行编译:
$(OBJS): depend
$(MAKE) -C $(CPUDIR) $(if$(REMOTE_BUILD),$@,$(notdir $@))
$(LIBS): depend $(SUBDIRS)
$(MAKE) -C $(dir $(subst$(obj),,$@))
$(LIBBOARD): depend $(LIBS)
$(MAKE)-C $(dir $(subst $(obj),,$@))
make的dir函数可将文件的路径提取出来,$(MAKE) -C $(dir $(subst $(obj),,$@))的意思是到目标所在文件夹下去读取该文件夹下的Makefile进行编译,这样全部的目标和库都能够生成了。
4.子文件夹下的Makefile
make会像前面所说的那样调用子文件夹中的Makefile进行编译。
在这里drivers/bios_emulator/文件夹下的Makefile进行剖析。其它文件夹的Makefile都是以类似的方式工作。
include$(TOPDIR)/config.mk
makefile通过引用源代码顶层文件夹下的config.mk来配置编译选项,并引入了一些通用的源文件编译规则,如:
AS =$(CROSS_COMPILE)as #汇编器
LD =$(CROSS_COMPILE)ld #连接器
CC =$(CROSS_COMPILE)gcc #编译器
………
$(obj)%.o: %.S
$(CC) $(ALL_AFLAGS) -o $@ $< -c
$(obj)%.o: %.c
Makefile中通过include$(SRCTREE)/rules.mk引入了源代码顶层文件夹下的rules.mk文件。该文件里的规则完毕了头文件依赖关系的自己主动生成,这也是如前面提到的一样。利用了make和gcc的特性。此外,rules.mk文件里还包括了编译宿主机下的目标的规则。那些在宿主机中使用的工具会用到这些规则来编译。
Makefile的all: $(LIB)便是终于目标,依据依赖关系。便能完毕子文件夹的编译。
5.生成S5PV210可启动的smart210-uboot.bin程序
前面生成的u-boot.bin还不能直接在S5PV210了上执行,由于其没有加上S5PV210所要求的特定的校验格式。
S5PV210在启动的时候会自己主动从所选择的启动方式相应的设备中读取其前16kb的内容到内存中,S5PV210要求前面的16字节存放校验和之类的信息,详细内容请參阅S5PV210的datasheet。
在顶层主Makefile的终于目标中有一个目标spl/u-boot-spl.bin,spl也就是第二阶段程序载入程序(Secondary Program Loader),S5PV210内置的ROM中固化的代码是第一阶段载入程序。载入前24Kb(也就是第二阶段程序载入城程序u-boot-spl.bin)到内存中,并跳转到当中执行;而第二阶段载入程序则将完整的u-boot.bin载入到内存中,然后跳转到u-boot.bin中执行。这个时候。uboot就算是启动起来了。
在顶层主Makefile中,目标spl/u-boot-spl.bin的规则是:
$(obj)spl/u-boot-spl.bin: depend
$(MAKE)-C spl all
可见,make会载入spl文件夹下的Makefile进行编译。接下来便看看这个子Makefile的工作方式。
因为硬件的限制。u-boot-spl.bin必须不超过(24*1024-16)字节。所以编译时仅仅须要一些载入完整uboot所须要的模块,其它统统不编译进来。
其它的方面,与Makefile的做法差点儿一样。这里不再赘述,而值得一的是smart210-uboot.bin的终于生成。
在u-boot-spl.bin生成后。例如以下的规则便执行了:
ifdef CONFIG_SAMSUNG
$(obj)$(BOARD)-spl.bin:$(obj)u-boot-spl.bin
$(TOPDIR)/board/$(BOARDDIR)/tools/mk$(BOARD)spl.exe
$(obj)u-boot-spl.bin$(obj)$(BOARD)-spl.bin
cat$(obj)$(BOARD)-spl.bin $(TOPDIR)/u-boot.bin > $(TOPDIR)/$(BOARD)-uboot.bin
endif
mk$(BOARD)spl.exe是在board/Samsung/smart210/tools/中已经被编译好了的工具。就是用来给第二阶段程序载入程序加入S5PV210所要求的头部信息的,加入了头部信息的u-boot-spl.bin就能够被载入并运行了,加了头部信息的u-boot-spl.bin命名为smart210-spl.bin。cat $(obj)$(BOARD)-spl.bin $(TOPDIR)/u-boot.bin >$(TOPDIR)/$(BOARD)-uboot.bin语句将第二阶段程序载入程序smart210-spl.bin和完整的u-boot.bin打包成为一个总体,生成的可运行文件为smart210-uboot.bin,那么smart210-uboot.bin完成后,将能烧成完整的uboot开端。
版权声明:本文博主原创文章。博客,未经同意不得转载。