uboot的Makefile分析
1.uboot的版本号
VERSION = 1 //主版本号
PATCHLEVEL = 3 //次版本号
SUBLEVEL = 4 //第三版本号
EXTRAVERSION = //另外附加的版本号信息
Makefile最终生成一个变量U_BOOT_VERSION,这个变量记录了makefile配置的版本号。
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
VERSION_FILE = $(obj)include/version_autogenerated.h //放到此.h中
version_autogenerated.h 是编译过程中自动生成的,里面定义了一个宏,这个宏就是我们Makefile配置的uboot版本号。
例:#define U_BOOT_VERSION "U-Boot 1.3.4" (启动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
由代码可以看出,在shell中执行uname -m可以得到电脑CPU的版本号。Shell中的“|”叫做管道,管道的作用是把前面的输出作为后面的输入,最终个输出是我们整个试子的输出。
HOST + ARCH :表示主机的CPU架构。
3.静默编译
为了只打印想要的结果,一般就会静默执行。
# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
Endif
使用静默编译的方法为:编译时使用make -s,s会作为MAKEFLAGS传给makefile,XECHO 就会变成空(,前面表示空,有了S就非空),实现了静默编译。
4.两种编译方法
1):默认情况下是在当前编译文件夹下的,o文件会被编译到当前的文件夹中,这种方法叫做原地编译。
优点:处理方法简单。
缺点:是源文件目录变得臃肿,一套源代码只能按照一种配置和编译方法进行处理,无法同时维护2种或2种以上的配置编译方式。
用法:直接默认就行。
2):支持输出文件夹单独输出文件夹的编译。
编译时将输出结果放到同一个目录下,方便管理。
用法:
#U-boot build supports producing a object files to the separate external
# directory. Two use cases are supported:
# 1) Add O= to the make command line
# 'make O=/tmp/build all'
# 2) 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.
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 //导出变量
第一种:make o=输出目录
第二种:export BUILD_DIR=输出目录,然后再make(如果两种都指定了,那么会执行make o=,此表达会有更高的优先级)
4.OBJTREE、SRCTREE、TOPDIR
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR)) //目标文件
SRCTREE := $(CURDIR) //源文件
TOPDIR := $(SRCTREE)
OBJTREE:编译出的.o文件存放的根目录。在默认的情况下,OBJTREE等于当前目录,如果指定相应目录,make o=xx,OBJTREE就是指定的目录。
SRCTREE:源码目录。(在默认情况下OBJTREE和SRCTREE相等)
TOPDIR:源码目录的根目录。
5.MKCONFIG
MKCONFIG是makefile定义的一个变量,它的值就是根目录下的mkconfig,这个makefile是一个脚本,是uboot配置阶段的配置脚本。
6.include $(obj)include/config.mk
include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
在配置过程中,也就是make x210_sd_config之后才生成这个文件,在此我们配置的内容为:
ARCH = arm
CPU = s5pc11x
BOARD = x210
VENDOR = samsung
SOC = s5pc110
最后export导出的5个变量,就是为了给makefile提供了5个环境变量。而这5个变量的来源是通过调用mkconfig脚本后生成的:
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
7.ARCH CROSS_COMPILE
ARCH:是上面部分导出的,它的值会影响后面的CROSS_COMPILE,意义就是定义当前编译的目标CPU和架构。
CROSS_COMPILE:是定义交叉编译工具链的前缀。原因:不同CPU架构上的交叉编译工具链前缀不一样,而他们的后缀一样,因此用前缀区分不同的CPU架构。
例:
ifeq ($(ARCH),arm)
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
经过ARCH提供arm后,就执行后面语句。
在实际运用时,可以在makefile中去更改设置CROSS_COMPILE的值,也可以在编译时用make CROSS_COMPILE=xxxx来设置,而且编译传参可以覆盖makefile里面的配置。
9.链接脚本。
include $(TOPDIR)/config.mk,执行uboot根目录下的config.mk脚本,里面定义了工具链的一些符号:
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
而且包含开发板配置项目:
sinclude $(OBJTREE)/include/autoconf.mk
Autoconf.mk解析:这个文件就是在配置过程中生成的,作用就是指导整个uboot编译过程。里面都是CONFIG_定义的宏/变量,这些宏会指导整个编译的流程(原理就是条件编译,uboot中有很多地方使用条件编译,这样使得uboot的移植性更强)。这个文件生成的源材料是源码目录下的inlcude/configs/xxx.h头文件。
ifeq ($(CONFIG_NAND_U_BOOT),y)
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds
else
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
Endif
如果定义了CONFIG_NAND_U_BOOT,生成的链接脚本叫做u-boot-nand.lds,如果未定义就会生成u-boot.lds。
ifneq ($(TEXT_BASE),)
CPPFLAGS += -DTEXT_BASE=$(TEXT_BASE)
Endif
在makefile配置的过程中,在/board/samsung/x210目录下生成一个config.mk文件,其中的内容就是:TEXT_BASE = 0xc3e00000相当于定义了一个变量。
TEXT_BASE是uboot连接时的链接地址,因为uboot中启用了虚拟地址映射,因此这个C3E00000地址就等于0x23E00000(也可能是33E00000具体地址要取决于uboot中做的虚拟地址映射关系)
#####################################################################
export CONFIG_SHELL HPATH HOSTCC HOSTCFLAGS CROSS_COMPILE \
AS LD CC CPP AR NM STRIP OBJCOPY OBJDUMP \
MAKE
xport TEXT_BASE PLATFORM_CPPFLAGS PLATFORM_RELFLAGS CPPFLAGS CFLAGS AFLAGS
#####################################################################
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
以上就是makefile的自动推导规则。
9.all: $(ALL)
目标all。就是默认的目标,在根目录下执行make时,相当make -all,目标生成的可执行文件为elf格式。
Uboot的配置过程
1.makefile脚本的6个参数。
smdkv210single_rev02_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x smdkc110 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/smdkc110/config.mk
x210_sd_config里的_config部分用空替换,得到:x210_sd,这就是第一个参数,所以:
$1: x210_sd
$2: arm
$3: s5pc11x
$4: x210
$5: samsumg
$6: s5pc110
所以,$# = 6
while [ $# -gt 0 ] ; do
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
*) break ;;
esac
Done
Maconfig的内容:
[ "${BOARD_NAME}" ] || BOARD_NAME="$1" //判断BOARD_NAME是否有值。
[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1
mkconfig脚本传参只能是4、5、6,如果大于6或者小于4都不行。
往后的:
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
.............
以上都是在创建符号链接,创建的这些符号链接是整个配置过程是核心,这些符号链接文件(文件夹)的主要作用是给头文件包含等过程提供指向性连接。根本目的是让uboot具有可移植性。
创建的符号链接:
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
rm -f asm-$2/arch
if [ -z "$6" -o "$6" = "NULL" ] ; then
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
ln -s ${LNPREFIX}arch-$6 asm-$2/arch //第二个
Fi
if [ "$2" = "arm" ] ; then
rm -f asm-$2/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc //第三个
Fi
if [ "$3" = "s5pc1xx" ] ; then
rm -f regs.h
ln -s $6.h regs.h
rm -f asm-$2/arch
ln -s arch-$3 asm-$2/arch //第四个
Fi
if [ "$2" = "arm" ] ; then
rm -f asm-$2/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc //第五个
Fi
第一个:在include目录下创建asm文件,指向asm-arm。
第二个:在include/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc110。
第三个:在include目录下创建regs.h文件,指向include/s5pc110.h删除第二个。
第四个:在include/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc11x
第五个:在include/asm-arm下创建一个proc文件,指向include/asm-arm/Proc-armv
总结:一共创建了4个符号链接。这4个符号链接将来在写代码过程中,头文件包含时非常有用。譬如一个头文件包含可能是:#include <asm/xx.h>
创建include/config.mk文件
echo "ARCH = $2" > config.mk
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
创建include/config.mk文件是为了让主Makefile在第133行去包含的。
x210_sd.h文件会被用来生成一个autoconfig.mk文件,这个文件会被主Makefile引入,指导整个编译过程。
Uboot的连接脚本
ENTRY(_satrt)
用来指定整个程序的入口,想C语言的main。
2种指定程序的连接地址:一种是makefile中ld的flags用-Ttext 0x20000000
来指定;第二种是在连接脚本的SECTIONS开头用.=0x2000000,来指定。如果两种指定,那么会以-Ttext为准。
uboot的最终链接起始地址就是在Makefile中用-Ttext 来指定的,TEXT_BASE变量。最终来源是Makefile中配置对应的命令中,在make xxx_config时得到的。
在代码段中要注意文件的排列的顺序,指定必须放在前面部分的那些文件就是那些必须安排在16kb的文件,这些文件会在前16kb被调用,后面的16k的顺序就不那么重要了。
连接脚本这跑那个除了.text .data .rodata bss等编译器工具自带的段以外,编译工具还允许我们自定义段,譬如uboot总的.u_boot_cmd段就是自定义段。