uboot主Makefile分析1
- uboot住Makefile分析参考:https://www.2cto.com/kf/201607/522424.html
uboot version确定(Makefile的24-29行)
- uboot的版本号分3个级别:
- VERSION:主板本号,PATCHLEVEL:次版本号,SUBLEVEL:再次版本号,EXTRAVERSION:另外附加的版本信息,这4个用.分隔开共同构成了最终的版本号;
- Makefile中版本号最终生成了一个变量U_BOOT_VERSION,这个变量记录了Makefile中配置的版本号。
- $(obj)这个变量是在后面定义并赋值的,其值是编译输出路径为:include/version_autogenerated.h文件是编译过程中自动生成的一个文件,所以源目录中没有,但是编译过后的uboot中就有了。它里面的内容是一个宏定义,宏定义的值内容就是我们在Makefile中配置的uboot的版本号。
- 验证方法:自己修改主Makefile中几个Version有关的变量,然后重新编译uboot,然后烧录到SD卡中,从SD卡启动,然后去看启动时uboot打印出来的版本信息,看看变化是不是和自己的分析一致。
VERSION = # 主版本号
PATCHLEVEL = # 次版本号
SUBLEVEL = # 次次版本号
EXTRAVERSION = # 外部版本号
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) #变量形式为:1.2..xx
VERSION_FILE = $(obj)include/version_autogenerated.h
HOSTARCH和HOSTOS(Makefile的31-43行)
- HOSTARCH这个名字:HOST是主机,就是当前在做开发用的这台电脑就叫主机;ARCH是architecture(架构)的缩写,表示CPU的架构。所以HOSTARCH就表示主机的CPU的架构。
- makefile的函数调用与变量调用很类似,格式是$( function arguments),其实上面一大段的意思是:变量HOSTARCH的值是一个函数的返回值 shell是makefile中的一个函数,$(shell XXX)会被解析成执行shell命令XXX;此处是执行了一条 uname -m | sed -e ……,符号 \是makefile的换行符 其中,| 是shell语法中的管道结构,例如:XXX | YYY ,表达式XXX 的输出将作为表达式YYY的输入,YYY的输出才是整句表达式的输出 uname -m 指令将输出负责编译的主机cpu架构,比如ixx86; sed -e是替换命令,比如把ixx86替换为i386 ,由此可见这个HOSTARCH变量的值将得到负责编译的主机cpu架构。大部分情况下我们得到的都是i386;
- 这两个环境变量是主机的操作系统和主机的CPU架构,得出后保存备用,后面自然会用到。
HOSTARCH := $(shell uname -m | \ # | 表示管道,作用:将前一个的输出作为后一个的输入
sed -e s/i./i386/ \ # sed 是一个字符串替换的工具
-e s/sun4u/sparc64/ \ # -e表示在一行中执行多个sed命令,s表示用后面的信息替换前面的信息.
-e s/arm.*/arm/ \ # 例如:s/i./i386/,表示将 i. 替换成 i386,
-e s/sa110/arm/ \ # shell uname -m 得到电脑的CPU版本号,为i686
-e s/powerpc/ppc/ \
-e s/ppc64/ppc/ \
-e s/macppc/ppc/)
- 这个HOSTOS变量和上一句HOSTARCH变量的原理类似,管道第一部分uname -s会得到负责编译的主机的OS,比如Linux, 管道第二部分是将大写转换成小写 ,管道第三部分的意思是如果前面一个部分得到了cygwin系统,则格式要转换一下。不必深究,因为cygwin基本没人用…… 由此可见这个HOSTOS变量的值将得到负责编译的主机操作系统,大部分情况下我们得到的都是linux
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \ # shell uname -s 得到系统名 Linux
sed -e 's/\(cygwin\).*/cygwin/') # tr '[:upper:]' '[:lower:]' 把字符全部转换成小写,为 linux export HOSTARCH HOSTOS
静默编译(50-54行)
- (1)平时默认编译时命令行会打印出来很多编译信息。但是有时候我们不希望看到编译信息,就后台编译即可。这就叫静默编译。
- (2)使用方法就是编译时make -s,-s会作为 MAKEFLAGS 传给Makefile,在50-54行这段代码作用下XECHO变量就会被变成空(默认等于echo),于是实现了静默编译。
- 这整段是为了实现make的静默选项功能,其中,findstring 一个函数,$(findstring s,$ (MAKEFLAGS))功能是从$(MAKEFLAGS)中找出字符‘s’,, $(MAKEFLAGS)是make的flag(选项),如果在控制台中输入make -s,则$(MAKEFLAGS)的值为‘s’ ,如果 $(findstring s, $(MAKEFLAGS)) 没找到 ‘ s’,这个表式的值为空,则ifeq()为真,即make时无需静默 ,无需静默的实现方法是令变量XECHO值为关键字echo,因为makefile中每次需要打印都会使用XECHO,而不是直接使用echo本身 ,静默make的实现方法是令变量XECHO值为空,当makefile需要打印时会调用XECHO,由于其为空,故无法打印make信息 注:编译工具链的信息是永远打印的,和make的静默选项无关
#########################################################################
# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS))) # findstring是一个函数,功能是从 $(MAKEFLAGS) 中查找字符 s
XECHO = echo # 如果没有找到,则打印编译信息
else
XECHO = : # 如果没有找到,则把 XECHO设为空,不打印,从而实现静默编译
endif
2种编译方法(原地编译和单独输出文件夹编译)
- (1)编译复杂项目,Makefile提供2种编译管理方法。默认情况下是当前文件夹中的.c文件,编译出来的.o文件会放在同一文件夹下。这种方式叫原地编译。原地编译的好处就是处理起来简单。
- (2)原地编译有一些坏处:第一,污染了源文件目录。第二的缺陷就是一套源代码只能按照一种配置和编译方法进行处理,无法同时维护2个或2个以上的配置编译方式。
- (3)为了解决以上2种缺陷,uboot支持单独输出文件夹方式的编译(linux kernel也支持,而且uboot的这种技术就是从linux kernel学习来的)。基本思路就是在编译时另外指定一个输出目录,将来所有的编译生成的.o文件或生成的其他文件全部丢到那个输出目录下去。源代码目录不做任何污染,这样输出目录就承载了本次配置编译的所有结果。
- (4)具体用法:默认的就是原地编译。如果需要指定具体的输出目录编译则有2种方式来指定输出目录。(具体参考Makefile 56-76行注释内容)
- 第一种:make O=输出目录
- 第二种:export BUILD_DIR=输出目录 然后再make
- 如果两个都指定了(既有BUILD_DIR环境变量存在,又有O=xx),则O=xx具有更高优先级,听他的。
#########################################################################
#
# U-boot build supports producing a object files to the separate external
# directory. Two use cases are supported:
#
# ) Add O= to the make command line
# 'make O=/tmp/build all'
#
# ) Set environement variable BUILD_DIR to point to the desired location
# 'export BUILD_DIR=/tmp/build'
# 'make'
#
# The second approach can also be used with a MAKEALL script
# 'export BUILD_DIR=/tmp/build'
# './MAKEALL'
#
# Command line 'O=' setting overrides BUILD_DIR environent variable.
#
# When none of the above methods is used the local build is performed and
# the object files are placed in the source directory.
#
编译 方法实践
linux中,在~/x210v3_bsp/uboot#中,依次执行:make distclean; make O=output/ x210_sd_config; make; make不成功,估计是移植的问题,不能再当前目录编译。
两种编译的实现(Makefile的78-123行)
ifdef O
ifeq ("$(origin O)", "command line") # $(origin O) 中origin是函数名,$(origin O)的功能是返回变量O的来源
BUILD_DIR := $(O) # 如果make时输入:make O=/tmp/build,那么makefile会认为定义了变量O,
endif # $(O) 的值为:/tmp/build
endif ifneq ($(BUILD_DIR),) # 如果变量BUILD_DIR不为空,
saved-output := $(BUILD_DIR) # 则变量saved-output的值为BUILD_DIR,即saved-output的值也是输出文件夹路径
- 上面这段是方法一的具体实现,如果make时输入参数 O=/tmp/build,那么makefile会认为定义了变量O,于是乎这段代码会开始执行 其中,$(origin O)中origin是函数名,$(origin O)的功能是返回变量O的来源;由此可知,如果O的来源是控制台命令,则变量BUILD_DIR的值就是变量O的值 然后进行一个判断,如果变量BUILD_DIR不为空,则变量saved-output的值为BUILD_DIR,即saved-output的值也是输出文件夹路径
# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}) # 如果该文件夹不存在,则创建 $(BUILD_DIR)
- 上面这句是shell语法中简写的if表达式,其作用是当输出文件夹路径不存在时就创建它 其中包含两个表达式,表达式1||表达式2,当表达式1为真时,表达式2不会被解释器执行,因为总结果一定为真,(尽管总的结果没有意义)。唯有表达式1为假时,解释器才会去执行表达式2,这样就能用逻辑表达式来实现if语句的功能了 表达式1中,方括号是固定用法,是为了突出里面表达式的作用是判断语句。-p是shell中判断路径是否存在的符号,如果路径存在则为表达式1为真;如果路径不存在,表达式1为假,解释器会执行表达式2,即创建输出文件夹路径。
# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}) # 如果该文件夹不存在,则创建 $(BUILD_DIR) # Verify if it was successful.
BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd) # 确保输出文件夹路径创建成功,&&的功能是连续执行两句语句,
# 当cd $(BUILD_DIR)执行完后,调用pwd命令,即打印当前路径赋值给BUILD_DIR; $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist)) # $(if xxx,yyy,zzz)是makefile的判断函数,
endif # ifneq ($(BUILD_DIR),) # 如果xxx为真,则执行yyy并返回值,否则执行zzz并返回值,
- 上面这整个一段代码功能是确保输出文件夹路径创建成功,&&的功能是连续执行两句语句,当cd $(BUILD_DIR)执行完后,执行/bin/pwd,即打印当前路径; $(if xxx,yyy,zzz)是makefile的判断函数,如果xxx为真,则执行yyy并返回值,否则执行zzz并返回值,由此可知如果未成功创建BUILD_DIR,就会输出错误打印信息; 最后一句应该是被注释掉了……
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))# 将来要编译的.o文件的目录,即输出目录
SRCTREE := $(CURDIR) # 要编译的源代码的目录,CURDIR(即current direction,当前源码所在的目录)
TOPDIR := $(SRCTREE) # TOPDIR 的值为 SRCTREE 的值即当前源码所在目录
LNDIR := $(OBJTREE) # LNDIR(应该是放链接产生的文件)的值和OBJTREE相同
export TOPDIR SRCTREE OBJTREE
- 上面这段代码是设置并导出了很多和路径有关的环境变量 如果BUILD_DIR不为空,OBJTREE(放产生的.o文件)的值就为BUILD_DIR,如果为空,则OBJTREE的值就为CURDIR(即current direction,当前源码所在的目录) SRCTREE的值为当前源码所在目录,TOPDIR的值为SRCTREE的值即当前源码所在目录,LNDIR(应该是放链接产生的文件)的值和OBJTREE相同。
- OBJTREE、SRCTREE、TOPDIR
- OBJTREE:编译出的.o文件存放的目录的根目录。在默认编译下,OBJTREE等于当前目录;在O=xx编译下,OBJTREE就等于我们设置的那个输出目录。
- SRCTREE: 源码目录,其实就是源代码的根目录,也就是当前目录。
- 总结:在默认编译下,OBJTREE和SRCTREE相等;在O=xx这种编译下OBJTREE和SRCTREE不相等。Makefile中定义这两个变量,其实就是为了记录编译后的.o文件往哪里放,就是为了实现O=xx的这种编译方式的。
MKCONFIG := $(SRCTREE)/mkconfig # CURIDR变量,这是一个MAKEFILE的内嵌变量,代表当前路径,将变量MKCONFIG的
export MKCONFIG # 值设置为当前源码目录下的mkconfig文件,并将其导出至全局
- 上面这段代码是判断OBJTREE的值和SRCTREE的值是否不相等,即判断是否使用了“单独外部路径编译”,如果使用了这个功能,则REMOTE_BUILD值为1,并将其导出至全局
# $(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)) # 判断 $(OBJTREE) 与 $(SRCTREE) 是否相等
obj := $(OBJTREE)/
src := $(SRCTREE)/
else
obj :=
src :=
endif
export obj src # Make sure CDPATH settings don't interfere
unexport CDPATH
- 这一段的意思非常简单,判断OBJTREE的值和SRCTREE的值是否不相等,即判断是否使用了“单独外部路径编译”功能,如果使用了这个功能, 则将obj的值赋为OBJTREE的值,将src的值赋为SRCTREE的值;反之,值都为空。最后将他们全部导出至全局。
include $(obj)include/config.mk(133行)
- include/config.mk不是源码自带的(在没有编译过的源码目录下是找不到这个文件的),要在配置过程(make x210_sd_config)中才会生成这个文件。因此这个文件的值和我们配置过程有关,是由配置过程根据我们的配置自动生成的。
- 我们X210在iNand情况下配置生成的config.mk内容为:
# $:= arm
# $:= s5pc11x
# $:= x210
# $:= samsumg
# $:= s5pc11x
- 我们在下一行(134行)export导出了这5个变量作为环境变量。所以着两行加起来其实就是为当前makefile定义了5个环境变量而已。之所以不直接给出这5个环境变量的值,是因为我们希望这5个值是可以被人很容易的、集中的配置的。
ifeq ($(ARCH),powerpc) # 这三句是powerpc架构的名称转换
ARCH = ppc
endif # $(wildcard xxx) 参数xxx是一个文件名格式(可使用通配符),这个函数的返回值是一列和格式匹配且真实存在的文件的名称。
# 但是这句应该没什么意义,因为连endif都没有
ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk)) # load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk # config.mk这个文件其实uboot源码中是不存在的,它是由配
export ARCH CPU BOARD VENDOR SOC # 置过程中由mkconfig这个脚本创建的,他们的值在后面会设
- 这里的配置值来自于2589行那里的配置项。如果我们要更改这里的某个配置值,要到2589行那里更改调用MKCONFIG脚本传参时的参数。
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
ARCH、CROSS_COMPILE
- ARCH是定义当前编译的目标CPU的架构。上面导出的,ARCH的值来自于我们的配置过程,它的值会影响后面的CROSS_COMPILE环境变量的值。
- CROSS_COMPILE是定义交叉编译工具链的前缀的。定义这些前缀是为了在后面用(用前缀加上后缀来定义编译过程中用到的各种工具链中的工具)。我们把前缀和后缀分开还有一个原因就是:在不同CPU架构上的交叉编译工具链,只是前缀不一样,后缀都是一样的。因此定义时把前缀和后缀分开,只需要在定义前缀时区分各种架构即可实现可移植性。
- CROSS_COMPILE在136-182行来确定。CROSS_COMPILE是被ARCH所确定的,只要配置了ARCH=arm,那么我们就只能在ARM的那个分支去设置CROSS_COMPILE的值。这个设置值只要能保证找到那个交叉编译工具链即可,不一定非得是全路径的,相对路径也可以。(如果已经将工具链导出到环境变量,并且设置了符号链接,这样CROSS_COMPILE = arm-linux-就可以)
ifndef CROSS_COMPILE # ARCH 的值,会直接影响 CROSS_COMPILE 的值
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE =
else
ifeq ($(ARCH),ppc)
CROSS_COMPILE = ppc_8xx-
endif
ifeq ($(ARCH),arm) # 由 ARCH 来确定 CROSS_COMPILE 的值
#CROSS_COMPILE = arm-linux- # 如果linux中的交叉编译工具链导出到环境变量,并设置了符号链接,则可以用此方法
#CROSS_COMPILE = /usr/local/arm/4.4.-eabi-cortex-a8/usr/bin/arm-linux-
#CROSS_COMPILE = /usr/local/arm/4.2.-eabi/usr/bin/arm-linux-
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
endif
- 实际运用时,我们可以在Makefile中去更改设置CROSS_COMPILE的值,也可以在编译时用make CROSS_COMPILE=xxxx来设置,而且编译时传参的方法可以覆盖Makefile里面的设置。
$(TOPDIR)/config.mk(185行)
- 在主Makefile中第190行(含注释)将 config.mk 包含进来
export CROSS_COMPILE # load other configuration
include $(TOPDIR)/config.mk # 导入TOPDIR路径下的config.mk文件(将另一个Makefile文件导入,将在原地展开)
编译工具定义(config.mk 94-107行)
#
# Include the make variables (CC, etc...)
#
AS = $(CROSS_COMPILE)as # 汇编工具
LD = $(CROSS_COMPILE)ld # 链接工具
CC = $(CROSS_COMPILE)gcc # 编译工具
CPP = $(CC) -E # 预处理
AR = $(CROSS_COMPILE)ar # 归档工具
NM = $(CROSS_COMPILE)nm # 列出object文件中的符号
LDR = $(CROSS_COMPILE)ldr #
STRIP = $(CROSS_COMPILE)strip #
OBJCOPY = $(CROSS_COMPILE)objcopy # 转换可执行文件格式工具
OBJDUMP = $(CROSS_COMPILE)objdump # 反汇编工具
RANLIB = $(CROSS_COMPILE)RANLIB # 产生归档文件索引
包含开发板配置项目(config.mk, 112行)
######################################################################### # Load generated board configuration
sinclude $(OBJTREE)/include/autoconf.mk ifdef ARCH
sinclude $(TOPDIR)/$(ARCH)_config.mk # include architecture dependend rules
endif
ifdef CPU
sinclude $(TOPDIR)/cpu/$(CPU)/config.mk # include CPU specific rules
endif
ifdef SOC
sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk # include SoC specific rules
endif
ifdef VENDOR
BOARDDIR = $(VENDOR)/$(BOARD)
else
BOARDDIR = $(BOARD)
endif
ifdef BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules
endif #########################################################################
- autoconfig.mk文件不是源码提供的,是配置过程自动生成的。
- 这个文件的作用就是用来指导整个uboot的编译过程。这个文件的内容其实就是很多CONFIG_开头的宏(可以理解为变量),这些宏/变量会影响我们uboot编译过程的走向(原理就是条件编译)。在uboot代码中有很多地方使用条件编译进行编写,这个条件编译是用来实现可移植性的。(可以说uboot的源代码在很大程度来说是拼凑起来的,同一个代码包含了各种不同开发板的适用代码,用条件编译进行区别。)
- 这个文件不是凭空产生的,配置过程也是需要原材料来产生这个文件的。原材料在源码目录的inlcude/configs/xxx.h头文件。(X210开发板中为include/configs/x210_sd.h)。这个h头文件里面全都是宏定义,这些宏定义就是我们对当前开发板的移植。每一个开发板的移植都对应这个目录下的一个头文件,这个头文件里每一个宏定义都很重要,这些配置的宏定义就是我们移植uboot的关键所在。
链接脚本(config.mk 142-149行)
- 在config.mk 的第147行将 u-boot.lds 包含进来
- 如果定义了CONFIG_NAND_U_BOOT宏,则链接脚本叫u-boot-nand.lds,如果未定义这个宏则链接脚本叫u-boot.lds。
ifndef LDSCRIPT
#LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds.debug
ifeq ($(CONFIG_NAND_U_BOOT),y) # 在autoconfig.h中查找,其中并没有这个定义,我们用的是inand
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds
else
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
endif
endif
- 从字面意思分析,即可知:CONFIG_NAND_U_BOOT是在Nand版本情况下才使用的,我们使用的X210都是iNand版本的,因此这个宏没有的。
- 实际在board\samsung\x210目录下有u-boot.lds,这个就是链接脚本。我们在分析uboot的编译链接过程时就要考虑这个链接脚本。
TEXT_BASE(config.mk 156-158行)
- Makefile中在配置X210开发板时,在board/samsung/x210目录下生成了一个文件config.mk,其中的内容就是:TEXT_BASE = 0xc3e00000相当于定义了一个变量。
- TEXT_BASE是将来我们整个uboot链接时指定的链接地址。因为uboot中启用了虚拟地址映射,因此这个C3E00000地址就等于0x23E00000(也可能是33E00000具体地址要取决于uboot中做的虚拟地址映射关系)。
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
- 回顾裸机中讲的链接地址的问题,再想想dnw方式先下载x210_usb.bin然后再下载uboot.bin时为什么第二个地址是23E00000.
自动推导规则(config.mk 239-256行)
- 我们在讲Makefile时提到过自动推导规则,具体理解可以参考《跟我一起学Makefile》
######################################################################### 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
uboot主Makefile分析
- 291行出现了整个主Makefile中第一个目标all(也就是默认目标,我们直接在uboot根目录下make其实就等于make all,就等于make这个目标)
x210_nand_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk smdkv210single_rev02_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x smdkc110 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/smdkc110/config.mk
- 目标中有一些比较重要的。譬如:u-boot 是最终编译链接生成的elf格式的可执行文件,使用OBJCOPY工具转成 u-boot.bin 文件;
- unconfig字面意思来理解就是未配置。这个符号用来做为我们各个开发板配置目标的依赖。目标是当我们已经配置过一个开发板后再次去配置时还可以配置。
unconfig:
@rm -f $(obj)include/config.h $(obj)include/config.mk \
$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp \
$(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep \
$(obj)board/$(VENDOR)/$(BOARD)/config.mk
- 我们配置开发板时使用:make x210_sd_config,因此分析x210_sd_config肯定是主Makefile中的一个目标。
uboot配置过程详解1
mkconfig脚本的6个参数
1 x210_sd_config : unconfig
2 @$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
3 @echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
- $(@:_config=) :$与@结合为$@,为目标,然后再把目标的 _config 部分用空格替换,因此 $(@:_config=) 为 x210_sd;所以这6个参数依次为:
- $# = 6
- $1: x210_sd
- $2: arm
- $3: s5pc11x
- $4: x210
- $5: samsumg
- $6: s5pc110
- 第23行:其实就是看BOARD_NAME变量是否有值,如果有值就维持不变;如果无值就给他赋值为$1,实际分析结果:BOARD_NAME=x210_sd
APPEND=no # Default: Create new config file
BOARD_NAME="" # Name to print in make output while [ $# -gt ] ; do
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
*) break ;; # 跳出while循环
esac
done
- 第25行:如果$#小于4,则exit 1(mkconfig脚本返回1)
- 第26行:如果$#大于6,则也返回1.
- 所以:mkconfig脚本传参个数只能是4、5、6,如果大于6或者小于4都不行。
[ "${BOARD_NAME}" ] || BOARD_NAME="$1" # BOARD_NAME 为空,给 BOARD_NAME 赋值为 x210_sd [ $# -lt ] && exit # 如果 $# 的值小于4 或大于6,则退出mkconfig,返回1
[ $# -gt ] && exit # exit 退出程序并返回一个值
- 第33行到第118行,都是在创建符号链接。为什么要创建符号链接?这些符号链接文件的存在就是整个配置过程的核心,这些符号链接文件(文件夹)的主要作用是给头文件包含等过程提供指向性连接。根本目的是让uboot具有可移植性。
if [ "$SRCTREE" != "$OBJTREE" ] ; then
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/include/asm-$ asm
LNPREFIX="../../include2/asm/"
cd ../include
rm -rf asm-$
rm -f asm
mkdir asm-$
ln -s asm-$ asm # 在include目录下创建asm文件,指向asm-arm
else # 默认路径下编译
cd ./include
rm -f asm
ln -s asm-$ asm # # 在include目录下创建asm文件,指向asm-arm
fi
- uboot可移植性的实现原理:在uboot中有很多彼此平行的代码,各自属于各自不同的架构/CPU/开发板,我们在具体到一个开发板的编译时用符号连接的方式提供一个具体的名字的文件夹供编译时使用。这样就可以在配置的过程中通过不同的配置使用不同的文件,就可以正确的包含正确的文件。
创建的符号链接:
- 第一个:在include目录下创建asm文件,指向asm-arm。(46-48行)
else # 默认路径下编译
cd ./include
rm -f asm
ln -s asm-$ asm # # 在include目录下创建asm文件,指向asm-arm
fi
- 第二个:在inlcude/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc110
rm -f asm-$/arch if [ -z "$6" -o "$6" = "NULL" ] ; then
ln -s ${LNPREFIX}arch-$ asm-$/arch
else
ln -s ${LNPREFIX}arch-$ asm-$/arch # 在inlcude/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc110
fi
- 第三个:在include目录下删除第二个arch.h,创建regs.h文件,指向include/s5pc110.h
- 第四个:在inlcude/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc11x
# create link for s5pc11x SoC
if [ "$3" = "s5pc11x" ] ; then
rm -f regs.h
ln -s $.h regs.h # ln -s ${LNPREFIX}arch-$ asm-$/arch
rm -f asm-$/arch # 删除 62行 创建的arch
ln -s arch-$ asm-$/arch # 在include目录下创建regs.h文件,指向include/s5pc110.h
fi
- 第五个:在include/asm-arm下创建一个proc文件,指向include/asm-arm/proc-armv
if [ "$2" = "arm" ] ; then
rm -f asm-$/proc
ln -s ${LNPREFIX}proc-armv asm-$/proc # 在include/asm-arm下创建一个proc文件,指向include/asm-arm/proc-armv
fi
- 总结:一共创建了4个符号链接。这4个符号链接将来在写代码过程中,头文件包含时非常有用。譬如一个头文件包含可能是:#include <asm/xx.h>
uboot配置过程详解2
- 创建include/config.mk文件(mkconfig文件123-129行)
#
# Create include file for Make
#
echo "ARCH = $2" > config.mk # ARCH = $ = arm
echo "CPU = $3" >> config.mk # CPU = $ = s5pc11x
echo "BOARD = $4" >> config.mk # BOARD = $ = x210 [ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk # VENDOR = $ = samsung [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk # SOC = $ = s5pc110
- 创建include/config.mk文件是为了让主Makefile在第138行去包含的(详解见2.4.3.3节)。
- 思考:uboot的配置和编译过程的配合。编译的时候需要ARCH=arm、CPU=xx等这些变量来指导编译,配置的时候就是为编译阶段提供这些变量。那为什么不在Makefile中直接定义这些变量去使用,而要在mkconfig脚本中创建config.mk文件然后又在Makefile中include这些文件呢?(主要是为了降低编译、配置的难度,如果直接在Makefile中定义,需要定义大量的变量,使程序的维护性和移植性变差)
- 理解这些脚本时,时刻要注意自己当前所处的路径。
- 创建(默认情况)/追加(make -a时追加)include/config.h文件(mkconfig文件的134-141行)。
#
# Create board specific header file
#
if [ "$APPEND" = "yes" ] # Append to existing config file
then
echo >> config.h
else
> config.h # Create new config file
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h exit
- 这个文件里面的内容就一行#include <configs/x210_sd.h>,这个头文件是我们移植x210开发板时,对开发板的宏定义配置文件。这个文件是我们移植x210时最主要的文件。x210_sd.h文件会被用来生成一个autoconfig.mk文件,这个文件会被主Makefile引入,指导整个编译过程。这里面的这些宏定义会影响我们对uboot中大部分.c文件中一些条件编译的选择。从而实现最终的可移植性。注意:uboot的整个配置过程,很多文件之间是有关联的(有时候这个文件是在那个文件中创建出来的;有时候这个文件被那个文件包含进去;有时候这个文件是由那个文件的内容生成的决定的)
- 注意:uboot中配置和编译过程,所有的文件或者全局变量都是字符串形式的(不是指的C语言字符串的概念,指的是都是字符组成的序列)。这意味着我们整个uboot的配置过程都是字符串匹配的,所以一定要细节,注意大小写,要注意不要输错字符,因为一旦错一个最后会出现一些莫名其妙的错误,很难排查,这个是uboot移植过程中新手来说最难的地方。
uboot的链接脚本
- 在config.mk中引入链接脚本u-boot.lds
ifeq ($(CONFIG_NAND_U_BOOT),y) # 在autoconfig.h中查找,其中并没有这个定义,我们用的是inand
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds
else
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
endif
endif
- uboot的链接脚本和我们之前裸机中的链接脚本并没有本质区别,只是复杂度高一些,文件多一些,使用到的技巧多一些。
- ENTRY(_start)用来指定整个程序的入口地址。所谓入口地址就是整个程序的开头地址,可以认为就是整个程序的第一句指令。有点像C语言中的main。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm) // 输出架构
ENTRY(_start) // 用来指定整个程序的入口地址,相当于c语言中的mian()
- 之前在裸机中告诉大家,指定程序的链接地址有2种方法:一种是在Makefile中ld的flags用-Ttext 0x20000000来指定;第二种是在链接脚本的SECTIONS开头用.=0x20000000来指定。两种都可以实现相同效果。其实,这两种技巧是可以共同配合使用的,也就是说既在链接脚本中指定也在ld flags中用-Ttext来指定。两个都指定以后以-Ttext指定的为准。
- uboot的最终链接的起始地址就是在Makefile中用-Ttext 来指定的,具体参见TEXT_BASE节,注意TEXT_BASE变量,最终来源是Makefile中配置对应的命令中,在make xxx_config时得到的。
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
- 在代码段中注意文件排列的顺序。指定必须放在前面部分的那些文件就是那些必须安排在前16KB内的文件,这些文件中的函数在前16KB会被调用。在后面第二部分(16KB之后)中调用的程序,前后顺序就无所谓了。
.text : // 代码段
{
cpu/s5pc11x/start.o (.text)
cpu/s5pc11x/s5pc110/cpu_init.o (.text)
board/samsung/x210/lowlevel_init.o (.text)
cpu/s5pc11x/onenand_cp.o (.text)
cpu/s5pc11x/nand_cp.o (.text)
cpu/s5pc11x/movi.o (.text)
common/secure_boot.o (.text)
common/ace_sha1.o (.text)
cpu/s5pc11x/pmic.o (.text) // 前16K的程序中用到的文件,必须放在前面,优先编译
*(.text)
}
- 链接脚本中除了.text .data .rodata .bss段等编译工具自带的段之外,编译工具还允许我们自定义段。譬如uboot总的.u_boot_cmd段(与u-boot的命令实现机制有密切关系)就是自定义段。自定义段很重要。
= ALIGN();
.rodata : { *(.rodata) } // 只读数据段 . = ALIGN();
.data : { *(.data) } // 普通数据段 . = ALIGN();
.got : { *(.got) } // 自定义段 __u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) } // 自定义段
__u_boot_cmd_end = .; . = ALIGN();
.mmudata : { *(.mmudata) } // 自定义段