嵌入式linux之Uboot和系统移植--主Makefile分析
2019-07-13 02:27 发布
生成海报
《 2.uboot和系统移植 - 第 4 部分 -2.4.uboot 配置和编译过程详解》
(朱老师物联网大讲堂笔记)
第一部分、章节目录
1.uboot主 Makefile 分析 1
2.uboot主 Makefile 分析 2
3.uboot主 Makefile 分析 3
4.uboot主 Makefile 分析 4
5.uboot主 Makefile 分析 5
6.uboot主 Makefile 分析 6
7.uboot配置过程详解 1
8.uboot配置过程详解 2
9.uboot的链接脚本
1.uboot主 Makefile 分析 1
1.1、 uboot version 确定( Makefile 的 24-29 行)
Makefile代码部分:
VERSION = 1
PATCHLEVEL = 30
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个用 . 分隔开共同构成了最终的版本号 U_BOOT_VERSION
,这个变量记录了 Makefile 中配置的版本号。
2、 include/version_autogenerated.h 文件是 编译过程中自动生成的一个文件 ,所以源目录中没有,但是编译过后的 uboot中就有了。它里面的内容是一个宏定义,宏定义的值内容就是我们在 Makefile 中配置的 uboot 的版本号。
1.2、 HOSTARCH 和 HOSTOS
Makefile代码部分:
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/')
注:sed的替换功能
test = abcdefgabc
Test1 = $(test) | sed -e s/abc/123/
Test2 = $(test) | sed -e s/abc/123/g
@echo $(Test1 )
@echo $(Test2 )
结果:
123defabc
123def123
abc被替换成了 123 ,如果不加字母 g ,结果就变成了只有第一个 abc 被替换
1、 HOSTARCH 这个名字: HOST是主机,就是当前在做开发用的这台电脑就叫主机; ARCH 是 architecture( 架构 ) 的缩写,表示 CPU 的架构。所以 HOSTARCH 就表示主机的 CPU 的架构。
2、直接在 shell 中执行 uname
-m 得到 i686,得到的值其实你当前执行这个命令的 电脑的 CPU的版本号 。
3、 shell 中的 | 叫做管道 , 管道的作用就是把管道前面一个运算式的输出作为后面一个的输入再去做处理,最终的输出才是我们整个式子的输出 。
4、这两个环境变量是主机的 操作系统 和 主机的 CPU架构 ,得出后保存备用,后面自然会用到。
2.uboot主 Makefile 分析 2
2.1、静默编译( 50-54 行)
Makefile代码部分:
#################################################################
# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
#################################################################
1、平时默认编译时命令行会打印出来很多编译信息。但是有时候我们不希望看到这些编译信息,就后台编译即可。这就叫静默编译。
2、使用方法就是编译时 make
-s , -s会作为 MAKEFLAGS 传给 Makefile ,在 50-54行这段代码作用下 XECHO 变量就会被变成空(默认等于 echo ),于是实现了静默编译。
2.2、 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 具有更高优先级,听他的。
(5)两种编译的实现代码在 Makefile 的 78-123 行,如下
Makefile代码部分:
#########################################################################
#
# 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.
#
# When none of the above methods is used the local build is performed and
# the object files are placed in the source directory.
#
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
#########################################################################
3.uboot主 Makefile 分析 3
3.1、 OBJTREE 、 SRCTREE 、 TOPDIR
(1) OBJTREE : 编译出的 .o文件存放的目录的根目录 。
在默认编译下, OBJTREE等于当前目录;
在 O=xx编译下, OBJTREE 就等于我们设置的那个输出目录。
(2) SRCTREE :
源码目录 ,其实就是源代码的根目录,也就是当前目录。
总结:在默认编译下, OBJTREE和 SRCTREE 相等 ;在 O=xx这种编译下 OBJTREE 和 SRCTREE 不相等。 Makefile 中定义这两个变量,其实就是为了记录编译后的 .o 文件往哪里放,就是为了实现 O=xx 的这种编译方式的。
3.2、 MKCONFIG ( Makefile 的 101 行)
Makefile中定义的一个变量(在这里定义,在后面使用),它的值就是我们源码根目录下面的 mkconfig 。这个 mkconfig是一个脚本,这个脚本就是 uboot 配置阶段的配置脚本 。
3.3、 include $(obj)include/config.mk ( 133 行)
Makefile代码部分:
# 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)我们在下一行( 134 行) export 导出了这 5 个变量作为环境变量。所以着两行加起来其实就是为当前 makefile 定义了 5 个环境变量而已。之所以不直接给出这 5 个环境变量的值,是因为我们希望这 5 个值是可以被人很容易的、集中的配置的。
(4)这里的配置值来自于 2589 行那里的配置项。如果我们要更改这里的某个配置值要到 2589 行那里调用 MKCONFIG 脚本传参时的参数。
Makefile代码部分:
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
3.4、 ARCH CROSS_COMPILE
(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- 就可以)
Makefile代码部分:
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
(4)实际运用时,我们可以在 Makefile 中去更改设置 CROSS_COMPILE 的值,也可以在编译时用 make
CROSS_COMPILE=xxxx 来设置,而且编译时传参的方法可以覆盖 Makefile 里面的设置。
4.uboot主 Makefile 分析 4
4.1、 $(TOPDIR)/config.mk (主 Makefile 的 185 行)
4.2、编译工具定义( config.mk 94-107 行)
4.3、包含开发板配置项目( config.mk, 112 行)
(1)autoconfig.mk文件不是源码提供的,是配置过程自动生成的。
(2)这个文件的作用就是用来指导整个 uboot 的编译过程。这个文件的内容其实就是很多 CONFIG_ 开头的宏(可以理解为变量),这些宏 / 变量会影响我们 uboot 编译过程的走向(原理就是条件编译)。在 uboot 代码中有很多地方使用条件编译进行编写,这个条件编译是用来实现可移植性的。(可以说 uboot 的源代码在很大程度来说是拼凑起来的,同一个代码包含了各种不同开发板的适用代码,用条件编译进行区别。)
(3)这个文件不是凭空产生的,配置过程也是需要原材料来产生这个文件的。原材料在源码目录的 inlcude/configs/xxx.h 头文件。( X210 开发板中为 include/configs/x210_sd.h )。这个 h 头文件里面全都是宏定义,这些宏定义就是我们对当前开发板的移植。每一个开发板的移植都对应这个目录下的一个头文件,这个头文件里每一个宏定义都很重要,这些配置的宏定义就是我们移植 uboot 的关键所在。
5.uboot主 Makefile 分析 5
5.1、链接脚本( 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)实际在 boardsamsungx210 目录下有 u-boot.lds ,这个就是链接脚本。我们在分析 uboot 的编译链接过程时就要考虑这个链接脚本。
5.2、 TEXT_BASE ( config.mk
156-158 行)
(1)Makefile中在配置 X210 开发板时,在 board/samsung/x210 目录下生成了一个文件 config.mk ,其中的内容就是: TEXT_BASE
= 0xc3e00000 相当于定义了一个变量。
(2)TEXT_BASE是将来我们 整个 uboot链接时指定的链接地址 。因为 uboot中启用了虚拟地址映射,因此这个 C3E00000 地址就等于 0x23E00000( 也可能是 33E00000 具体地址要取决于 uboot 中做的虚拟地址映射关系 ) 。
(3)回顾裸机中讲的链接地址的问题,再想想 dnw 方式先下载 x210_usb.bin 然后再下载 uboot.bin 时为什么第二个地址是 23E00000.
6. uboot主 Makefile 分析 6
Makefile代码部分:
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
endif
all: $(ALL)
(1)291行出现了整个主 Makefile 中第一个目标 all (也就是默认目标,我们直接在 uboot 根目录下 make 其实就等于 make
all ,就等于 make 这个目标)
(2)目标中有一些比较重要的。譬如: u-boot 是最终编译链接生成的 elf 格式的可执行文件,
(3)unconfig字面意思来理解就是未配置。这个符号用来做为我们各个开发板配置目标的依赖。目标是当我们已经配置过一个开发板后再次去配置时还可以配置。
(4)我们配置开发板时使用: make x210_sd_config ,因此分析 x210_sd_config 肯定是主 Makefile 中的一个目标。
7.uboot配置过程详解 1
(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}" ] || BOARD_NAME="$1"
其实就是看 BOARD_NAME变量是否有值,如果有值就维持不变;如果无值就给他赋值为 $1 ,实际分析结果: BOARD_NAME=x210_sd
(3) 第 25行: [
$# -lt 4 ] && exit 1
如果 $#小于 4 ,则 exit
1 ( mkconfig 脚本返回 1 )
(4) 第 26行: [
$# -gt 6 ] && exit 1
如果 $#大于 6 ,则也返回 1.
所以: mkconfig脚本传参只能是 4 、 5 、 6 ,如果大于 6 或者小于 4 都不行。
(5) 从第 33行到第 118 行,
(6) 都是在创建符号链接。为什么要创建符号链接?这些符号链接文件的存在就是整个配置过程的核心,这些符号链接文件(文件夹)的主要作用是给头文件包含等过程提供指向性连接。根本目的是让 uboot具有可移植性。
uboot可移植性的实现原理:在 uboot 中有很多彼此平行的代码,各自属于各自不同的架构 /CPU/ 开发板,我们在具体到一个开发板的编译时用符号连接的方式提供一个具体的名字的文件夹供编译时使用。这样就可以在配置的过程中通过不同的配置使用不同的文件,就可以正确的包含正确的文件。
创建的符号链接:
第一个 :
cd ./include
rm -f asm
ln -s asm-$2 asm
在 include目录下创建 asm 文件,指向 asm-arm 。( 46-48 行)
第二个 : ln
-s ${LNPREFIX}arch-$3 asm-$2/arch
在 inlcude/asm-arm下创建一个 arch 文件,指向 include/asm-arm/arch-s5pc110
第三个:
# create link for s5pc1xx SoC
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
在 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
8. uboot配置过程详解 2
#
# 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
(1)创建 include/config.mk 文件( mkconfig 文件 123-129 行)
(2)创建 include/config.mk 文件是为了让主 Makefile 在第 133 行去包含的
(3)思考: uboot 的配置和编译过程的配合。编译的时候需要 ARCH=arm 、 CPU=xx 等这些变量来指导编译,配置的时候就是为编译阶段提供这些变量。那为什么不在 Makefile 中直接定义这些变量去使用,而要在 mkconfig 脚本中创建 config.mk 文件然后又在 Makefile 中 include 这些文件呢?
(4)理解这些脚本时,时刻要注意自己当前所处的路径。
(5)创建(默认情况) / 追加( make -a 时追加) include/config.h 文件( mkconfig 文件的 134-141 行)。
(6)这个文件里面的内容就一行 #include ,这个头文件是我们移植 x210 开发板时,对开发板的宏定义配置文件。这个文件是我们移植 x210 时最主要的文件。
(7)x210_sd.h文件会被用来生成一个 autoconfig.mk 文件,这个文件会被主 Makefile 引入,指导整个编译过程。这里面的这些宏定义会影响我们对 uboot 中大部分 .c 文件中一些条件编译的选择。从而实现最终的可移植性。
注意: uboot的整个配置过程,很多文件之间是有关联的(有时候这个文件是在那个文件中创建出来的;有时候这个文件被那个文件包含进去;有时候这个文件是由那个文件的内容生成的决定的)
注意: uboot中配置和编译过程,所有的文件或者全局变量都是字符串形式的(不是指的 C 语言字符串的概念,指的是都是字符组成的序列)。这意味着我们整个 uboot 的配置过程都是字符串匹配的,所以一定要细节, 注意大小写 ,要注意不要输错字符,因为一旦错一个最后会出现一些莫名其妙的错误,很难排查,这个是 uboot移植过程中新手来说最难的地方。
9.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 变量。最终来源是
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮