注:以下知识来源于《朱老师物联网大讲堂》
1 uboot version确定
VERSION = 1
PATCHLEVEL = 3
SUBLEVEL = 4
EXTRAVERSION =
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
VERSION_FILE = $(obj)include/version_autogenerated.h
(1)uboot的版本号分为3个级别:
VERSION:主版本号
PATCHLEVEL:次版本号
SUBLEVEL:子版本号
EXTRAVERSION:另外附加的版本信息(可自定义)
这4个用“ . ”分隔开共同构成了最终的版本号。
(2)Makefile中版本号最终生成了一个变量U_BOOT_VERSION,这个变量记录了Makefile中配置的版本号。
(3)uboot目录include/version_autogenerated.h文件是编译过程中自动生成的一个文件,所以在源目录中没有,只有编译过后才能找到。它里面的内容是一个宏定义,其右值的内容就是上述介绍的uboot的版本号。如果需要进一步分析,可以自行修改主Makefile中几个version有关的变量,然后重新编译uboot,将其烧录进开发板,看启动时uboot打印出来的版本消息。
2 HOSTARCH和HOSTOS
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
(1)在shell中执行 uname-m 会得到一个字符串,它其实是当前所使用电脑的CPU的版本号。
(2)在关于这部分的代码中,会看到字符“ | ”,它在shell中被解释为管道,其作用是把管道前面的一个运算式的输出作为后面的一个输入再去做处理,最终的输出才是整个式子的输出。
(3) HOSTARCH这个词,HOST是主机,即当前负责编译的电脑;ARCH是architecture的缩写,表示CPU所采用的体系结构。连在一起,就表示为主机CPU的架构。
(4)HOSTARCH和HOSTOS为uboot的两个环境变量。
3 静默编译
# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
(1)平时默认编译时命令行会打印出很多编译信息,有时候如果不想看到这些编译信息,可以设置在后台编译,即静默编译。
(2)使用方法就是 make -s,-s会作为MAKEFLAGS传给Makefile,通过以上代码XECHO变量就会被置为空(默认为echo),于是实现了静默编译。
4 两种编译方法(原地编译与重定向编译)
(1)Makefile提供2种编译方法,默认情况下是当前文件夹中的.c文件夹,编译出来的.o文件会放在同一文件夹下,这种方式就叫原地编译。
(2)原地编译的好处就是处理起来简单。但它存在一些坏处,如:污染源文件目录;一套源代码只能按照一种配置和编译方法进行处理,无法同时维护2个或2个以上的配置编译方式。
(3)为了解决以上问题,uboot支持单独输出文件夹方式的编译(重定向编译),Linux kernel也支持,并且uboot就是从其借鉴而来。基本思路就是在编译时另外指定一个输出目录,将来所有编译生成的.o文件和其他文件都放入那个文件夹中,源代码目录没有任何污染。这样输出目录就承载了本次配置编译的所有结果。
(4)相应代码如下:
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
如果需要指定输出目录编译则有两种方式:
①make O = 输出目录
②export BUILD_DIR = 输出目录,然后键入make
若两者都指定了,则①具有更高优先级。
(5)在以上代码中出现OBJTREE,SRCTREE,MKCONFIG变量。
- OBJTREE:代表编译出的.o文件的存放目录。在默认编译下,它属于当前目录;在O = xx编译下,它等于xx。
- SRCTREE:代表源码目录,也就是当前目录。
- MKCONFIG:代表源码根目录下的mkconfig文件。该文件是一个配置脚本,用于uboot配置阶段。
5 include $(obj)include/config.mk
# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
(1)include/config.mk不是源码自带的,要在配置过程中(命令为:make x210_sd_config)中才会生成这个文件。因此这个文件的内容与配置过程有关。
(2)X210开发板在iNand情况下配置生成的config.mk内容为:
ARCH = arm
CPU = s5pc11x
BOARD = x210
VENDOR = samsung
SOC = s5pc110
(3)通过export导出这五个变量作为环境变量。这里的配置值来自后面第2589行的配置项。更改时需要到那里调用MKCONFIG脚本传参时的参数。
6 ARCH CROSS_COMPILE
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
(1)有两个很重要的环境变量。一个是ARCH,值来自于配置过程。它会影响另一个CROSS_COMPILE环境变量的值。ARCH的意义是定义当前编译的目标CPU的架构。
(2)CROSS_COMPILE用来定义交叉编译工具链的前缀。定义前缀的原因就是方便在区分各种架构时实现可移植性。
(3)CROSS_COMPILE是被ARCH所确定的,只要配置了ARCH=arm,那么只能在arm分支去设置CROSS_COMPILE的值。值可以是绝对路径,也可以是相对路径。
(4)实际运用时,我们可以在Makefile中去更改设置CROSS_COMPILE的值,也可以在编译时make CROSS_COMPILE = xx来设置。编译时传参的方法可以覆盖Makefile里面的设置。
7 config.mk
# load other configuration
include $(TOPDIR)/config.mk
主Makefile导入新的makefile——config.mk,重要内容如下:
# 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
LDR = $(CROSS_COMPILE)ldr
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
RANLIB = $(CROSS_COMPILE)RANLIB
该内容是对编译工具的定义。接着是包含开发板的配置项目:
# Load generated board configuration
sinclude $(OBJTREE)/include/autoconf.mk
简要介绍一下autoconfig.mk文件:
- autoconfig.mk文件不是源码提供的,是配置过程自动生成的。
- 该文件的作用是用来指导整个uboot的编译过程,这个文件的内容是很多CONFIG_开头的宏(可以理解为变量),他们会影响uboot编译过程的走向(原理就是条件编译)。在uboot代码中有很多地方使用条件编译,以达到可移植性。(可以说uboot的源代码在很大程度是拼凑起来的,同一个代码包含不同开发板的适用代码,用条件编译进行区别)
- 该文件的配置参考在源码目录的include\configs/xxx.h头文件,头文件里面全都是宏定义,即对当前开发板的移植。每一个开发板的移植都对应这个目录下的一个头文件,这些配置的宏定义就是移植uboot的关键所在。
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_NAND_U_BOOT宏,则链接脚本叫u-boot-nand.lds,反之链接脚本叫u-boot.lds。从字面意思分析,CONFIG_NAND_U_BOOT是在Nand版本情况下使用的,我们使用的X210都是iNand版本的,因此这个宏没有。
ifneq ($(TEXT_BASE),)
CPPFLAGS += -DTEXT_BASE=$(TEXT_BASE)
endif
以上代码段涉及到TEXT_BASE。Makefile中在配置X210开发板时,在board/samsung/x210目录下生成一个文件config.mk,其中内容就是:TEXT_BASE = 0xc3e00000,相当于定义一个变量。
TEXT_BASE是将来我们整个uboot链接时指定的链接地址。因为uboot启用了虚拟地址映射,具体地址取决于该映射关系。
8.主Makefile的编译目标
all: $(ALL)
$(obj)u-boot.hex: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(obj)u-boot.ldr: $(obj)u-boot
$(LDR) -T $(CONFIG_BFIN_CPU) -f -c $@ $< $(LDR_FLAGS)
$(obj)u-boot.ldr.hex: $(obj)u-boot.ldr
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@ -I binary
$(obj)u-boot.ldr.srec: $(obj)u-boot.ldr
$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@ -I binary
- 在此处出现了整个主Makefile中第一个目标all(也就是默认目标,直接在uboot根目录下make其实就等于make all,就等于make这个目标),在目标中有一些比较重要的,比如u-boot是最终编译链接生成的elf格式的可执行文件。
接下来有个内容值得注意:
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
unconfig字面意思是未配置,这个符号用来做为各个开发板配置目标的依赖。目标是当已经配置过一个开发板后再次配置仍然可行。
我们配置开发板时使用:make x210_sd_config,因此可知,x210_sd_config肯定是主Makefile中的一个目标。
9.uboot配置过程详解
(1)mkconfig脚本的6个参数
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
x210_sd_config里的_config部分用空替换,得到x210_sd,这就是第一个参数,所以:
$1 : x210_sd,$2 : arm,以此类推,$# = 6。
下面简要分析一下mkconfig脚本的主要内容:
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"
[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1
第一行:判断BOARD_NAME变量是否有值,如果有值就维持不变;如果无值就赋值为$1,即x210_sd。
第二行和第三行判断参数总个数$#,条件需满足大于等于4且小于等于6,否则返回1。
(2)创建符号链接:
if [ "$SRCTREE" != "$OBJTREE" ] ; then
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/include/asm-$2 asm
LNPREFIX="../../include2/asm/"
cd ../include
rm -rf asm-$2
rm -f asm
mkdir asm-$2
ln -s asm-$2 asm
else
cd ./include
rm -f asm
ln -s asm-$2 asm
fi
这些符号链接文件的主要作用是给头文件包含等过程提供指向性连接,根本目的是让uboot具有可移植性。
uboot实现可移植性的原理:在uboot中有很多彼此平行的代码,各自属于不同的架构/CPU/开发板,我们在具体到一个开发板的编译时用符号链接的方式提供一个具体名字的文件夹供编译时使用。这样就可以在配置的过程中通过不同的配置使用不同的文件,就可以正确地包含文件。
(3)创建include/config.mk文件
#
# Create include file for Make
#
echo "ARCH = $2" > config.mk
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
创建该文件是为了让主Makefile文件去包含。
思考:在uboot配置和编译过程的配合中,编译的时候需要ARCH、CPU等变量来指导编译,那为什么不在主Makefile中直接定义这些变量,反而要通过mkconfig脚本去创建config.mk文件然后又在Makefile中include这些文件呢?
#
# 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
接下来的这段代码是 创建(默认情况)/追加(make -a)include/config.h 文件,该文件的内容就一行,#include<configs/x210_sd.h>,这个头文件是移植x210开发板时,对开发板的宏定义配置文件,也是移植过程中的主要文件。这个头文件会被用来生成一个autoconfig.mk文件,这个文件会被主Makefile引入,指导整个编译过程。具体来说,这里面的宏定义会影响我们对uboot中大部分.c文件中一些条件编译的选择,从而实现最终的可移植性。
注意:
(1)在uboot的整个配置过程中,很多文件是有关联的(有时候一个文件是在另一个文件中创建出来的,有时候一个文件被另一个文件包含进去,有时候一个文件是由另一个文件的内容生成决定的)
(2)在uboot的配置和编译过程中,所有文件或者全局变量都是字符串形式(字符组成的序列)的,这意味着整个uboot的配置过程都是字符串匹配的,所以一定要细心,注意大小写,不要输错字符,因为出错很难排查,这对于新手来说是经常容易犯的错误。
10.uboot的链接脚本(u-boot.lds)
(1)uboot的链接脚本和裸机中的链接脚本并没有本质区别,只是复杂度高一些,文件多一些,使用的技巧多一些。
(2)ENTRY(_start)用来指定整个程序的入口地址,即整个程序的开头,类似于C语言中的main。
(3)指定程序的链接地址有两种:一种是在Makefile中ld的flags用-Text 0x20000000 来指定;一种是在链接脚本的开头用 . = 0x20000000 来指定。两个都指定后,以 -Text 指定的为准。
(4)uboot的最终链接起始地址就是在Makefile中用-Text来制定的,其中涉及到TEXT_BASE变量。最终来源是Makefile中配置对应的命令中,在make xxx_config时得到的。
(5)在代码段中,注意文件的排列顺序。指定必须放在前面的文件就是那些必须安排在前16kb内的文件,这些文件中的函数在前16kb会被调用。
(6)链接脚本除了.text .data .rodata .bss 段等编译工具自带的段外,编译工具还允许自定义段。比如uboot中的.u_boot_cmd段就是自定义段,这个自定义段很重要。