备注:分析的是OK210开发板自带的uboot_smdkv210,可能有些部分和其他版本不太一样,但是原理都类似。
编译u-boot的步骤
make forlinx_linux_config
make
首先生成配置文件,然后编译源码,依次看看这些命令都干了些什么事情
配置过程
#forlinx add
forlinx_linux_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x smdkc110 samsung s5pc110 linux
@echo "TEXT_BASE = 0xcc800000" > $(obj)board/samsung/smdkc110/config.mk
清除上一次配置的文件
此目标依赖unconfig目标,因此先调用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
执行的操作主要是删除上一次配置生成的配置文件。
开始配置操作
在Makefile中以@开头的命令表示,在命令执行的时候不在终端上打印信息。
$(MKCONFIG)变量在Makefile的101行有定义,如下:
MKCONFIG := $(SRCTREE)/mkconfig
export MKCONFIG
$(@:_config=)这句话的意思是将 forlinx_linux_config的_config用空白代替只剩下forlinx_linux,
其中$(srcfiles:xxx=ccc)是Makefile的规则,用等号后边的ccc替代srcfiles中等号前边的xxx,
$(@) = $@ 代表目标,即在命令行输入的make forlinx_linux_config中的forlinx_linux_config。
因此@$(MKCONFIG) $(@:_config=) arm s5pc11x smdkc110 samsung s5pc110 linux
的意思就是调用u-boot根目录的mkconfig脚本并将 forlinx_linux arm s5pc11x smdkc110 samsung s5pc110 linux作为参数传递给脚本。
mkconfig文件
在文件的头部的注释已经交代了文件的作用和调用的格式
# Script to create header files and links to configure
# U-Boot for a specific board.
#
# Parameters: Target Architecture CPU Board [VENDOR] [SOC]
#
即
$0 = mkconfig (脚本文件名)
$1 = Target (目标) forlinx_linux
$2 = Architecture (架构) arm
$3 = CPU (CPU型号)s5pc11x
$4 = Board (开发板名)smdkc110
$5 = VENDOR (生产商)samsung
$6 = SOC (芯片名)s5pc110
最后一个linux是OK210的工程师自己添加的用于区别Android版本的内核和linux的内核,没有什么实质用途。(个人猜测)
APPEND=no # Default: Create new config file
BOARD_NAME="" # Name to print in make output
while [ $# -gt 0 ] ; do
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
*) break ;;
esac
done
接下来定义了两个变量为后边配置过程做准备,用途注释已经写的很清楚,APPEND=no是表示需要重新创建配置文件的标志。
再然后循环:如果参数个数大于零就执行循环,然后再循环里判断第一个参数(脚本可以有参数选项),很明显都不符合,所以直接跳出循环。
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"
[ $# -lt 4 ] && exit 1
[ $# -gt 7 ] && exit 1
echo "Configuring for ${BOARD_NAME} board..."
将BOARD_NAME赋值为forlinx_linux,然后判断参数个数,小于4大于7(一般情况为大于6应该还是OK210的工程师修改了)就直接退出脚本,放弃执行。
创建软连接
接下来创建很多链接文件,这些操作是mkconfig脚本的主要工作,主要创建的链接文件如下:
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
./include/asm --> asm-$2 (asm-arm)
# create link for s5pc11x SoC
if [ "$3" = "s5pc11x" ] ; then
rm -f regs.h
ln -s $6.h regs.h
rm -f asm-$2/arch
ln -s arch-$3 asm-$2/arch
fi
regs.h --> $6.h (s5pc110.h)
asm-$2/arch --> arch-$3(arch-s5pc11x)
u-boot支持很多种开发板,很多种类型的CPU,所以要有一种通用得配置方式来方便移植;
u-boot甚至linux内核都采用了这种方式:针对不同的处理器或者板子都有自己的头文件和文件夹,但是真正的编译过程却不直接使用,而是在配置阶段创建这些头文件或者文件夹的软连接,并命名为一种通用的名字。在编译的时候通过这些软连接访问真正需要的文件。
例如,在u-boot的include目录下有一个asm的目录,此目录就是在配置完后生成的一个软连接,当配置的是arm架构时,此文件是asm-arm的软连接。
创建make时用到的包含文件 config.mk
#
# Create include file for Make
#
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
创建开发板特定的头文件 config.h
#
# 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
#forlinx add
if [ "$7" = "linux" ]
then
echo "#define CONFIG_LINUX_FORLINX 1" >>config.h
else
echo "#define CONFIG_ANDROID_FORLINX 1" >>config.h
fi
#echo "#include " >>config.h
#forlinx change for linux and android
echo "#include " >>config.h
因为上边已经把APPEND设置为NO,所以执行> config.h 即创建config.h头文件,并向config.h中写入
/* Automatically generated - do not edit */
"#define CONFIG_LINUX_FORLINX 1
以及#include
同样是OK210的工程师加入的
#include 即#include
注意:创建软连接的操作全都是在u-boot根目录的include子目录中执行的。
配置脚本mkconfig结束。
mkconfig脚本小结
一共创建了三个连接文件,
./include/asm --> asm-$2 (asm-arm)
regs.h --> $6.h (s5pc110.h)
asm-$2/arch --> arch-$3(asm-arm -> arch-s5pc11x)
以及include/config.mk和include/config.h,其中include/config.mk文件中记录着u-boot的版本信息,include/config.h中包含了 configs/$1.h,此文件里记录着是很多很多以CONFIG_开头的宏,是开发板u-boot的主配置信息。
主Makefile
版本号相关
VERSION = 1
PATCHLEVEL = 3
SUBLEVEL = 4
EXTRAVERSION =
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
VERSION_FILE = $(obj)include/version_autogenerated.h
Makefile刚开始的时候仍然是定义当前使用的u-boot的版本,此版本和linux内核版本类似都由多个字段组成,各字段意义如下:
VERSION : 主版本号
PATCHLEVEL :次版本号
SUBLEVEL : 修正版本号
EXTRAVERSION:扩展版本号 (可以自己定义,用于区别自己移植的版本号 )
U_BOOT_VERSION 就是以上几个字段的组合字符串。
VERSION_FILE 保存的是u-boot版本号的文件,其中obj变量在后边定义。version_autogenerated.h文件也在后边生成(此文件不是u-boot源码目录本身有的文件,是由配置过程自动生成的),其内容如下:
#define U_BOOT_VERSION "U-Boot 1.3.4"
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
这一段话是检测编译时主机的架构和操作系统,其中使用了sed工具进行了相应的处理,
sed -e s/i.86/i386/ 意思就是不管是i686\i386还是i586,都用i386替代。
tr '[:upper:]' '[:lower:]'的意思是将操作系统名字中可能存在的大写字母全部转换为小写字母。
笔者用的是i686的Linux操作系统,因此执行的结果是 HOSTARCH=i386, HOSTOS=linux,
最后调用export导出为环境变量供其他地方使用。
静默编译
所谓静默编译就是在编译的时候不打印任何信息(错误和警告信息除外)。
# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
这段代码的意思是,在执行make时输入的参数中搜索’s’,如果有的话就给XECHO赋值为echo,否则就赋值为空,然后在其他地方用XECHO来打印信息,就实现了是否启用静默编译。
make -s就启用了静默编译。
指定目录编译
u-boot除了支持静默编译外还支持在不同目录进行编译,即将配置过程生成的文件以及编译产生的中间文件全都输出到一个指定的目录,从而避免污染源码目录。
从56-76的注释中可以看出有两种方式指定
1、在命令行通过make O=/tmp/build 指定输出目录为/tmp/build
2、指定BUILD_DIR=/tmp/build环境变量
注意:通过阅读根目录下的README文件,如果要在其他目录进行编译,在配置阶段和编译阶段都要指定输出的目录位置,如下:
1. Add O= to the make command line invocations:
make O=/tmp/build distclean
make O=/tmp/build NAME_config
make O=/tmp/build all
2. Set environment variable BUILD_DIR to point to the desired location:
export BUILD_DIR=/tmp/build
make distclean
make NAME_config
make all
Makefile中的78-93行完成工作就是,如果指定了O参数,就判断O后边指定的目录存在不存在,如果不存在就创建,并把指定的路径赋值给BUILD_DIR变量 ——
然后就是给几个下边变量赋值
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR)) //输出目录
SRCTREE := $(CURDIR) //源码目录
TOPDIR := $(SRCTREE) //顶层目录(源码根目录)
LNDIR := $(OBJTREE) //链接目录
export TOPDIR SRCTREE OBJTREE //导出为环境变量
MKCONFIG := $(SRCTREE)/mkconfig //mkconfig配置脚本的路径
export MKCONFIG //导出为环境变量
然后是编译要用的$(obj) 和$(src)
# $(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
如果编译输出目录和源码目录不是通一个目录,就将源码目录的路径赋值给$(src),将输出的路径赋值给$(obj)。
否则就置空。
确定目标架构和交叉编译工具
从124行开始一直到182行都是在确定要使用的交叉编译工具。
其中在133行的时候导入了一个配置文件
# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
$(obj)include/config.mk此文件在执行mkconfig脚本的时候生成,里边保存着开发板以及CPU的型号和架构信息
ARCH = arm
CPU = s5pc11x
BOARD = smdkc110
VENDOR = samsung
SOC = s5pc110
然后再将这些信息导出为环境变量。
ifeq ($(ARCH),arm)
CROSS_COMPILE = arm-linux-
endif
export CROSS_COMPILE
再然后就是指定交叉编译工具,并将CROSS_COMPILE导出为环境变量了。
此外
在185行导入了U-boot根目录下的config.mk文件
# load other configuration
include $(TOPDIR)/config.mk
此文件主要完成以下工作
1、定义交叉编译工具,例如 CC=arm-linux-gcc, LD=arm-linux-ld等等。
2、导入配置文件
$(OBJTREE)/include/autoconf.mk
$(TOPDIR)/$(ARCH)_config.mk
$(TOPDIR)/cpu/$(CPU)/config.mk
$(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk
$(VENDOR)/$(BOARD)
$(TOPDIR)/board/$(BOARDDIR)/config.mk
3、编译选项相关的一大坨,一般不用管
4、make的自动推导规则
定义u-boot编译的目标
OBJS是编译时的目标
LIBS是依赖的库
此外还有GCC的库等等
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND) $(obj)u-boot.dis
ifeq ($(ARCH),blackfin)
ALL += $(obj)u-boot.ldr
第一个目标all
all: $(ALL)