tiny210——uboot移植之Makefile剖析篇

来源:互联网 发布:程序员分类 知乎 编辑:程序博客网 时间:2024/05/18 22:09

 这篇东东早就写好了,一直没时间发出来,现在终于有时间发一下了大笑记录总结uboot的学习历程,好像够详细了,以后忘了也可以再温习回来嘛大笑有些特殊字符显示得乱掉了尴尬


Makefile追踪技巧:

技巧1:可以先从编译目标开始顺藤摸瓜地分析,先不要关注具体细节,着重关注主要的代码结构和编译过程

技巧2:追踪分析时要通过文本或者其他途径暂时记录重要的线索

技巧3:将主要的Makefile文件中export出来的变量以及include的文件提取出来,看看include的文件大致是些什么文件,当看到一些来历不明的变量或者操作时可以从这些记录中查找是否有相关记录(将Make涉及的配置文件找出来,相当于形成了一个闭合的环境)


为什么要去研究uboot的Makefile?因为要研究一个工程代码,最好的方法就是阅读其Makefile,Makefile会告诉我们工程的模块结构以及目标的具体编译和连接过程,读懂了Makefile无疑就是对工程结构和代码结构了如指掌,相当于一张地图,是移植以及学习其技术的基础。

在这里,是以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中上面这两句语句时,没有发现相应文件,便使用规则生成它们。

           工程中的文件的数量的是非常庞大的数字,而每个文件依赖的文件也是非常多,如要要将每个文件的依赖手工一一列出,那工作量是相当繁重的,也不利于代码的移植,因此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编译时所依赖的配置文件及其依赖关系,便可实现自动生成相关依赖关系以及因相关的配置文件的改动而重新编译工程,不再需要人工去生成及维护其依赖关系。

 

第二次读入执行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_.*\)/-u\1/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的启动。

1 0
原创粉丝点击