一、uboot主Makefile分析
VERSION = 1 PATCHLEVEL = 3 SUBLEVEL = 4 EXTRAVERSION = U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) VERSION_FILE = $(obj)include/version_autogenerated.h HOSTARCH := $(shell uname -m | \ sed -e s/i.86/i386/ \ -e s/sun4u/sparc64/ \ -e s/arm.*/arm/ \ -e s/sa110/arm/ \ -e s/powerpc/ppc/ \ -e s/ppc64/ppc/ \ -e s/macppc/ppc/) HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \ sed -e 's/\(cygwin\).*/cygwin/') export HOSTARCH HOSTOS
这是uboot的Makefile的24~43行。
1、uboot的版本号分3个级别:
VERSION:主板本号
PATCHLEVEL:次版本号
SUBLEVEL:再次版本号
EXTRAVERSION:另外附加的版本信息
这4个用.分隔开共同构成了最终的版本号
2、Makefile中版本号最终生成了一个变量U_BOOT_VERSION,这个变量记录了Makefile中配置的版本号。
3、 include/version_autogenerated.h文件是编译过程中自动生成的一个文件,所以源目录中没有,但是编
译过后的uboot中就有了。它里面的内容是一个宏定义,宏定义的值内容就是我们在Makefile中配置的
uboot的版本号。
4、HOSTARCH和HOSTOS
(1)、直接在shell中执行uname -m得到i686,得到的值其实你当前执行这个命令的电脑的CPU的版本号。
(2)、shell中的|叫做管道,管道的作用就是把管道前面一个运算式的输出作为后一个表达式的输入。
(3)、HOSTARCH这个名字:HOST是主机,就是当前在做开发用的这台电脑。ARCH是architecture
(架构)的缩写,表示CPU的架构。所以HOSTARCH就是表示主机的CPU的架构。
(4)、这两个环境变量是主机的操作系统和主机的CPU架构,得出后保存备用,后面自然会用到。
*****************************************************************************************************
ifeq (,$(findstring s,$(MAKEFLAGS))) XECHO = echo else XECHO = : endif
这个Makefile中的50~54行(关于静默编译)
1、平时默认编译时命令行会打印出很多编译信息。但是有时候我们不希望看到这些编译信息,所以后台
编译即可。这就叫静默编译。
2、使用方法就是make -s ,-s会单独作为MAKEFLAGS传给Makefile,在50-54行这段代码的作用下XECHO
会变为空(默认等于echo),实现了静默编译。
**********************************************************************************************************
ifdef O ifeq ("$(origin O)", "command line") BUILD_DIR := $(O) endif endif ifneq ($(BUILD_DIR),) saved-output := $(BUILD_DIR) # Attempt to create a output directory. $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}) # Verify if it was successful. BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd) $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist)) endif # ifneq ($(BUILD_DIR),) OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR)) SRCTREE := $(CURDIR) TOPDIR := $(SRCTREE) LNDIR := $(OBJTREE) export TOPDIR SRCTREE OBJTREE MKCONFIG := $(SRCTREE)/mkconfig export MKCONFIG ifneq ($(OBJTREE),$(SRCTREE)) REMOTE_BUILD := 1 export REMOTE_BUILD endif # $(obj) and (src) are defined in config.mk but here in main Makefile # we also need them before config.mk is included which is the case for # some targets like unconfig, clean, clobber, distclean, etc. ifneq ($(OBJTREE),$(SRCTREE)) obj := $(OBJTREE)/ src := $(SRCTREE)/ else obj := src := endif export obj src # Make sure CDPATH settings don't interfere unexport CDPATH这是Makefile的78~123行
1、两种编译方法(原地编译和单独输出文件夹编译)
(1)、编译复杂项目,Makefile提供2中编译管理方法。默认情况下是当前文件夹中的.c文件,编译出来
的.o会放在同一文件夹上。这种方式叫原地编译,原地编译的好处就是处理起来简单。
(2)、原地编译有一些坏处:第一,污染了源文件目录;第二的缺陷就是一套源代码只能按照一种配置和
编译进行处理,无法同时维护2个或2个以上的配置编译方式。
(3)、为了解决以上2中缺陷,uboot支持单独输出文件夹方式的编译(linux kernel也支持,而且uboot
的这种技术就是从linux kernel学习来的)。
(4)、具体用法:默认的就是原地编译。如果需要指定具体的输出目录编译则有2中方式来指定输出目录。
第一种:make O=输出目录
第二种:export BUILD_DIR=输出目录 然后再make
如果两个都指定了(既有BUILD_DIR环境变量存在,又有O=xx),则O=xx具有更高优先级。
2、(1)、OBJTREE:编译出的.o文件存放的目录的根目录。在默认编译下,OBJTREE等于当前目录;在O=xx编译,
OBJTREE就等于我们设置的那个输出目录。
(2)、SRXTREE:源码目录,其实就是源代码的根目录,也就是当前目录。
在默认编译下,OBJTREE和SRCTREE相等;在O=xx这种编译下OBJTREE和SRTCREE不相等。Makefile
中定义这个变量,其实就是为了记录编译后.o文件往哪里放,就是为了实现O=xx这种编译方式。
(3)、MKCONFIG(Makefile的101行)
Makefile中定义的一个变量(在这里定义,在后面使用),它的值就是我们源码根目录下面的mkconfig。
这个mkconfig是一个脚本,这个脚本uboot配置的脚本。
*********************************************************************************************************
ifeq ($(ARCH),powerpc) ARCH = ppc endif ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk)) # load ARCH, BOARD, and CPU configuration include $(obj)include/config.mk export ARCH CPU BOARD VENDOR SOC ifndef CROSS_COMPILE ifeq ($(HOSTARCH),$(ARCH)) CROSS_COMPILE = else ifeq ($(ARCH),ppc) CROSS_COMPILE = ppc_8xx- endif ifeq ($(ARCH),arm) #CROSS_COMPILE = arm-linux- #CROSS_COMPILE = /usr/local/arm/4.4.1-eabi-cortex-a8/usr/bin/arm-linux- #CROSS_COMPILE = /usr/local/arm/4.2.2-eabi/usr/bin/arm-linux- CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi- endif ifeq ($(ARCH),i386) CROSS_COMPILE = i386-linux- endif ifeq ($(ARCH),mips) CROSS_COMPILE = mips_4KC- endif ifeq ($(ARCH),nios) CROSS_COMPILE = nios-elf- endif ifeq ($(ARCH),nios2) CROSS_COMPILE = nios2-elf- endif ifeq ($(ARCH),m68k) CROSS_COMPILE = m68k-elf- endif ifeq ($(ARCH),microblaze) CROSS_COMPILE = mb- endif ifeq ($(ARCH),blackfin) CROSS_COMPILE = bfin-uclinux- endif ifeq ($(ARCH),avr32) CROSS_COMPILE = avr32-linux- endif ifeq ($(ARCH),sh) CROSS_COMPILE = sh4-linux- endif ifeq ($(ARCH),sparc) CROSS_COMPILE = sparc-elf- endif # sparc endif # HOSTARCH,ARCH endif # CROSS_COMPILE export CROSS_COMPILE
以上是Makefile的136~182行
1、include $(obj)include/config.mk(133行)
(1)、include/config.mk不是源码自带的(这个文件是编译过程中生成的)。
(2)、我们x210在iNand情况下配置生成的config.mk内容为:
ARCH = arm
CPU = s5pc11x
BOARD = x210
VENDOR = samsung
SOC = s5pc110
(3)我们在下一行(134行)export导出了这5个变量作为环境变量。所以着两行加起来其实就是为当前makefile
定义了5个环境变量而已。之所以不直接给出这5个环境变量的值,是因为我们希望这5个值是可以被人很
容易的、集中的配置的。
(4)这里的配置值来自于2589行那里的配置项。如果我们要更改这里的某个配置值要到2589行那里调用MKCONFIG
脚本传参时的参数。
2、ARCH CROSS_COMPLILE
(1)、接下来有2个很重要的环境变量。一个是ARCH,上面导出的,值来自于我们的配置过程,
它的值会影响后面的CROSS_COMPILE环境变量的值。ARCH的意义是定义当前编译的目标CPU的架构。
(2)、CROSS_COMPILE是定义交叉编译工具链的前缀的。定义这些前缀是为了在后面用(用前缀加上后缀来
定义编译过程中用到的各种工具链中的工具)。我们把前缀和后缀分开还有一个原因就是:在不同CPU架
构上的交叉编译工具链,只是前缀不一样,后缀都是一样的。因此定义时把前缀和后缀分开,只需要在定
义前缀时区分各种架构即可实现可移植性。
(3)、CROSS_COMPILE在136-182行来确定。CROSS_COMPILE是被ARCH所确定的,只要配置了ARCH=arm,
那么我们就只能在ARM的那个分支去设置CROSS_COMPILE的值。这个设置值只要能保证找到那个交叉编译
工具链即可,不一定非得是全路径的,相对路径也可以。(如果已经将工具链导出到环境变量,并且设置了
符号链接,这样CROSS_COMPILE = arm-linux-就可以)
(4)、实际运用时,我们可以在Makefile中去更改设置CROSS_COMPILE的值,也可以在编译时用make
CROSS_COMPILE=xxxx来设置,而且编译时传参的方法可以覆盖Makefile里面的设置。
************************************************************************************************************
1、autoconfig.mk文件不是源代码提供的,是配置过程自动生成的。这个文件的作用是指导uboot的编译过程,这个文件
的内容就是很多CONFIG_开头的宏,这些宏/变量会影响我们uboot编译过程的走向。这个文件不是凭空产生的,配置
过程也是需要原材料来生产这个文件的。原材料在源码的include/configs/xxx.h头文件。(x210开发板中为include/
configs/x210_sd.h)。
2、链接脚本(config.mk 142-149行)
ifndef LDSCRIPT #LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds.debug ifeq ($(CONFIG_NAND_U_BOOT),y) LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds else LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds endif endif
以上是config.mk的142-149行。
(1)、如果定义了CONFIG_NAND_U_BOOT宏,则链接脚本叫u-boot-nand.lds,如果未定义这个宏则链接脚本叫
u-boot.lds。
(2)、从字面意思分析,即可知:CONFIG_NAND_U_BOOT是在Nand版本情况下才使用的,我们使用的X210都
是iNand版本的,因此这个宏没有的。
(3)、实际在board\samsung\x210目录下有u-boot.lds,这个就是链接脚本。我们在分析uboot的编译链接过程时
就要考虑这个链接脚本。
3、TEXT_BASE(config.mk 156-158行)
ifneq ($(TEXT_BASE),) CPPFLAGS += -DTEXT_BASE=$(TEXT_BASE) endif
(1)、Makefile中在配置X210开发板时,在board/samsung/x210目录下生成了一个文件config.mk,
其中的内容就是:TEXT_BASE = 0xc3e00000相当于定义了一个变量。
(2)、TEXT_BASE是将来我们整个uboot链接时指定的链接地址。因为uboot中启用了虚拟地址映射,因此这个
C3E00000地址就等于0x23E00000(也可能是33E00000具体地址要取决于uboot中做的虚拟地址映射关系)。
4、自动推导规则(config.mk 239-256行)
ifndef REMOTE_BUILD %.s: %.S $(CPP) $(AFLAGS) -o $@ $< %.o: %.S $(CC) $(AFLAGS) -c -o $@ $< %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< else $(obj)%.s: %.S $(CPP) $(AFLAGS) -o $@ $< $(obj)%.o: %.S $(CC) $(AFLAGS) -c -o $@ $< $(obj)%.o: %.c $(CC) $(CFLAGS) -c -o $@ $< endif
5、(1)mkconfig脚本的6个参数
$(@:_config=) arm s5pc11x x210 samsung s5pc110
x210_sd_config里的_config部分用空替换,得到:x210_sd,这就是第一个参数,所以:
$1: x210_sd
$2: arm
$3: s5pc11x
$4: x210
$5: samsumg
$6: s5pc110
所以,$# = 6(2)第23行:其实就是看BOARD_NAME变量是否有值,如果有值就维持不变;如果无值就给他赋值
为$1,实际分析结果:BOARD_NAME=x210_sd
(3)第25行:如果$#小于4,则exit 1(mkconfig脚本返回1)
(4)第26行:如果$#大于6,则也返回1.
所以:mkconfig脚本传参只能是4、5、6,如果大于6或者小于4都不行。
(5)从第33行到第118行,都是在创建符号链接。为什么要创建符号链接?这些符号链接文件的存在就是整个
配置过程的核心,这些符号链接文件(文件夹)的主要作用是给头文件包含等过程提供指向性连接。根本目
的是让uboot具有可移植性。
uboot可移植性的实现原理:在uboot中有很多彼此平行的代码,各自属于各自不同的架构/CPU/开发板,
我们在具体到一个开发板的编译时用符号连接的方式提供一个具体的名字的文件夹供编译时使用。这样就可
以在配置的过程中通过不同的配置使用不同的文件,就可以正确的包含正确的文件。
(6)创建的符号链接:
第一个:在include目录下创建asm文件,指向asm-arm。(46-48行)
第二个:在inlcude/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc110
第三个:在include目录下创建regs.h文件,指向include/s5pc110.h
删除第二个。
第四个:在inlcude/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc11x
第五个:在include/asm-arm下创建一个proc文件,指向include/asm-arm/proc-armv
总结:一共创建了4个符号链接。这4个符号链接将来在写代码过程中,头文件包含时非常有用。譬如一
个头文件包含可能是:#include <asm/xx.h>
6、uboot的链接脚本
(1)uboot的链接脚本和我们之前裸机中的链接脚本并没有本质区别,只是复杂度高一些,文件多一些,
使用到的技巧多一些。
(2)ENTRY(_start)用来指定整个程序的入口地址。所谓入口地址就是整个程序的开头地址,可以认为就是整个
程序的第一句指令。有点像C语言中的main。
(3)之前在裸机中告诉大家,指定程序的链接地址有2种方法:一种是在Makefile中ld的flags用-Ttext
0x20000000来指定;第二种是在链接脚本的SECTIONS开头用.=0x20000000来指定。两种都可以实现相同效果。
其实,这两种技巧是可以共同配合使用的,也就是说既在链接脚本中指定也在ld flags中用-Ttext来指定。两个都
指定以后以-Ttext指定的为准。
(4)uboot的最终链接起始地址就是在Makefile中用-Ttext 来指定的,具体参见2.4.5.2节,注意TEXT_BASE变量。
最终来源是Makefile中配置对应的命令中,在make xxx_config时得到的。
(5)在代码段中注意文件排列的顺序。指定必须放在前面部分的那些文件就是那些必须安排在前16KB内的文件,
这些文件中的函数在前16KB会被调用。在后面第二部分(16KB之后)中调用的程序,前后顺序就无所谓了。
(6)链接脚本中除了.text .data .rodata .bss段等编译工具自带的段之外,编译工具还允许我们自定义段。
譬如uboot总的.u_boot_cmd段就是自定义段。自定义段很重要。
欢迎各位指出不足之处。