从零开始的UBOOT的学习3--主Makefile的分析
参考朱有鹏UBOOT全集
前言:很多类似于STM32的单片机,所有的东西,IDE集成开发环境已经帮你配置好怎么使用的,代码段或者数据段怎么在程序中链接,我们的各个文件的.c或者.h文件应该是怎么样的,在Linux的ARM中是没有的,所有的都需要我们自己定制化,所谓的定制化编译过程,也就是写Makefile。
1、UBOOT版本号的确认
(1)UBOOT的VERSION确定:Makefile的24-29行
下面的这一段代码是为了让UBOOT在编译的过程中能够得到自己编译的UBOOT是哪一个版本的。
U_BOOT_VERSION是一个变量:1.3.4
VERSION_FILE也是一个变量,表示版本的表示文件,其实就是include目录下面的version_autogenerated.h
里面的文件的内容是:
#define U_BOOT_VERSION "U-Boot 1.3.4"
VERSION = 1
PATCHLEVEL = 3
SUBLEVEL = 4
EXTRAVERSION =
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
VERSION_FILE = $(obj)include/version_autogenerated.h
2、UBOOT的主机编译时候的架构和操作系统
HOSTARCH和HOSTOS
这两个变量都是环境变量,因为程序中都有一段话:
export HOSTARCH HOSTOS
makefile里面shell uname -m
此处相当于在Ubuntu中直接敲命令uname -m指令
跟shell脚本一样,因为都有Ubuntu中支持的命令
在我的Ubuntu中输入命令后,得到i686
匹配字符后得到的是i386
HOSTOS:主机的操作系统使用的是什么?
得到的操作系统的值是Linux
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
3、UBOOT的编译方式
(1)uboot的静默编译
平时Makefile默认编译的时候会打印出来很多编译信息,但是有时候我们不希望看到这些编译信息,就后台编译即可,这就叫做静默编译。
使用的方法就是make -s,-s会作为MAKEFLAGS传给Makefile
(2)2种编译方法
2.1、编译复杂项目,Makefile提供2种编译管理方法。默认情况下是当前文件夹的.c文件,编译出来的.o文件会放在同一个文件夹下面,这种方式叫做原地编译。原地编译的好处就是处理起来十分的方便。
2.2、原地编译有一些坏处:
第一污染了源文件的目录。 第二一套源代码只能按照一种配置和编译的方式进行处理,无法同时维护2个或者2个以上的配置编译。
*为了解决以上2种缺陷,UBOOT支持单独输出文件夹方式,Linux kernel也支持,而且UBOOT的这种技术就是从Linux kernel学习来的。基本思路就是在编译时另外指定一个输出目录,将来所有的编译生成的.o文件或者生成的其他文件全部全部丢到那个输出目录下面去。源代码不做任何污染,这样输出目录就把全部的编译过程都是在输出目录当中。
具体方法:
make O=输出目录
export BUILD_DIR=输出目录,然后再make
如果两个都指定了,那么O=xx具有更高的优先级,听它的。
两种编译的实现代码在Makefile的78-123行。
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})
3、UBOOT的三个重要的文件目录
(1)OBJTREE、SRCTREE、TOPDIR
OBJTREE:编译出来的.o文件存放的目录,就是编译出来的.o文件就是放置在这个目录里面的。
SRCTREE:编译出来的源代码目录,如果是原地编译的话,那么这两个目录是一样的。
TOPDIR:就是顶层目录的意思
(2)MKCONFIG(Makefile的101行)
这个是Makefile里面定义的一个变量在这里面使用,他的值就是我们在源码根目录的mkconfig。
这个东西就是UBOOT配置阶段的配置脚本。
include目录下面的配置脚本config.mk传给MKCONFIG:
ARCH = arm
CPU = s5pc11x
BOARD = x210
VENDOR = samsung
SOC = s5pc110
我们在下一行134行export导出了这5个变量作为环境变量,所以这两行加起来其实就是为了当前的Makefile定义了5个环境变量而已。之所以不直接给出这5个环境变量的值,是因为我们希望这5个值是可以被人恨容易的,集中的进行修改的,这里之所以能够这样配置是因为我们一开始使用
make x210_sd_config
5、Makefile如何指定交叉编译工具链
Makefile的交叉编译工具链:
接下来有2个很重要的环境变量
一个是ARCH架构,我们是用ARM架构的,值是来自于我们的配置过程,
它的值会影响后面的CROSS_COMPILE环境变量的值。ARCH意义是定义当前编译的目标CPU的结构。
CROSS_COMPILE是定义交叉编译工具链的前缀的,定义这些前缀是为了在后面使用的。(用前缀加上后缀来定义编译过程中用到的各种工具链的工具)
我们之所以把交叉编译工具链分开前缀和后缀,是因为在不同的架构上的交叉编译工具链。只需要在定义前缀时区分各种架构的可实现的可移植性。
如果设置了符号链接的话:
那么这样也是可以的CROSS_COMPILE = arm-linux-就可以
在实际运用中,我们可以在Makefile中去更改设置CROSS_COMPILE的值,也可以在编译时用make CROSS_COMPILE=xxxx来设置,而且编译时传参的方法可以覆盖Makefile里面的设置。
6、UBOOT根目录下面的config.mk文件的作用
这一行非常的重要,在这里添加了一个Makefile
这200多行是我们导入进来的这个文件是为了确定交叉编译工具链的和自动推导原则的。
前面的是定义交叉编译工具链:
(1)$(TOPDIR)/config.mk(主Makefile的185行)
(2)编译工具定义config.mk
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
定义了一大堆的自动推导原则:
如果没有定义远程编译的话。那么就使用下面的自动的推导的原则
1、如果有.S的文件的话,让其编译成.s的文件
2、如果有.S的文件的话,让其编译成.o的文件
3、如果有.c的文件的话,让其编译成.o的文件
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
7、UBOOT根目录下面的autoconfig.mk文件的作用
包含开发板的工具:
autoconfig.mk文件不是源码提供的,是配置过程中自动生成的。
必然有一个原始的原材料:
这个文件的作用就是用来指导整个UBOOT的编译过程的,这个文件的内容其实就是有很多CONFIG_开头的很多的宏(可以理解为变量)。
这些宏或者变量会影响我们UBOOT的编译过程的走向,原理就是条件编译。
在UBOOT的代码中有很多地方使用条件编译就是进行编译的,这个条件编译就是用来使用可移植性的。
使用条件变量匹配不同的开发板。可以说UBOOT的源代码在很大程度是拼凑
这个文件的原材料就是源代码目录的include/configs/xxx.h文件。这个.h头文件里面全部都是宏定义。这些宏定义就是我们对当前开发板的移植。
这个.h头文件里面全部都是宏定义,这些宏定义就是我们对当前开发板的移植。每个开发板的移植都对应这个目录的一个头文件。每一个宏定义都很重要。
这些配置的宏定义就是我们移植UBOOT的关键所在。
CONFIG_开头的东西
arm_config.mk :预处理的属性。
如果没有定义LDSCRIPT 就是一个CONFIG_NAND_UBOOT
这个变量就是我们所说的链接脚本。
如果我们定义了CONFIG_NAND_U_BOOT宏,则链接脚本叫做u-boot-nand.lds
如果没有定义这个宏则链接脚本叫做u-boot.lds
(2)我们从字面意思分析,即可知道
-TEXT_BASE:在shell里面一个>表示创建一个文件
(1)Makefile中在配置我们的x210开发板时,在board/samsung/x210目录下面生成了一个config.mk,其中的内容就是
TEXT_BASE = 0xc3e00000 相当于定义了一个变量。
(2)TEXT_BASE是将来我们链接时的地址,
-Ttext 0x0 :因为我们的UBOOT中启用了虚拟地址映射:
因此我们这个C3E00000地址就等于我们0x23E00000也可能是33E00000
这个具体的地址要取决于UBOOT中做的虚拟地址映射关系。
(3)回顾一下裸机中讲的链接地址的问题,再想一下UBOOT的dnw方式,先下载x210_usb.bin,然后再下载uboot.bin时为什么第二个地址是23E00000
-Ttext 相当于我们的链接地址一样的。
$@:指的是我们的目标
$<:指的是我们的依赖
自动推导规则:我们讲Makefile时提到过。跟我一样学Makefile。
8、UBOOT的主Makefile分析6
190行就是开始放置start.o
编译的那些目标文件。
291行:出现了我们整个UBOOT的主Makefile中的第一个目标,也就是默认的目标,我们直接在UBOOT根目录下make其实就等于make all
一直到382行才完。
(2)目标里面中有一些是比较重要的,比如U-boot是最终编译链接生成的ELF格式的可执行文件。
这些都是编译时添加的一些元素。
(3)UNconfig:按字面意思就是没有配置。
这个符号用来做为各个开发板配置目标的依赖,目标是当我们已经配置过一个开发板时,再次去配置时。
(4)我们配置我们的开发板时:make x210_sd_config肯定是我们的配置目标。还可以配置。
make x210_sd_config
board/samsung/x210/config.mk
如果你想改这个地址,只需要在Makefile里面改就行了。
传了6个参数给mkconfig。
9、UBOOT的配置过程详解1:
board目录下面创建一个TEXT_BASE = 0xc3e00000
mkconfig:传了6个参数
$(@:_config=) x210_sd =号前面的替换成=号后面的
这就是我们的第一个参数
$1:x210_sd
$2:arm
$3:s5pc11x
$4:x210
$5:samsung
$6:s5pc110
$# = 6
mkconfig:这个脚本:
case前3个没有一个被匹配上,跳出while循环。
(2)第23行:执行完其实就是看BOARD_NAME变量是否有值,如果没有值就给它赋值为$1
BOARD_NAME= x210_sd
$# 的范围是在4-6之间。
$#小于4,则exit 1(mkconfig脚本返回1)
(3)第26行:
$#>6也是返回一个exit 1
31行就是开始创建一些符号链接文件。
(5)从第33行到第118行,都是在创建符号链接,为什么要创建符号链接?
这些符号链接的文件的存在就是我们整个配置过程的核心,这些符号链接文件(文件夹)的主要作用是给头文件包含等过程提供指向性的链接,根本目的是让UBOOT具有可移植性。
(6)uboot的可移植性的实现原理,在UBOOT中有很多彼此平行的代码,各自属于各自不同的架构开发板,在具体到一个开发板的编译的时候,用符号链接的方式提供一个具体的名字的文件夹供我们编译时使用。这样就可以在配置的过程中通过不同的配置使用不同的文件,就可以成功的包含正确的文件。
如果你专门使用O=xx那么就需要在里面创建符号链接。
cd ./include
rm -f asm
ln -s asm-$2 asm
(6)创建的符号链接:
第一个:在include目录下创建一个asm文件指向asm-arm(46-48)
第二个:rm -f asm-arm /arch
-o 相当于逻辑或
在我们的include/asm-arm创建一个arch指向include/asm-arm/arch-s5pc110
//后面就开始创建特殊的开发板了
第三个:$3 = s5pc11x
在include目录下创建一个regs.h文件,指向include/s5pc110.h
删除第二个
s5pc110.h ->regs.h
rm -f asm-arm/arch
ln -s arch-$3 asm-arm/arch
rm -f regs.h
ln -s $6.h regs.h
rm -f asm-$2/arch
ln -s arch-$3 asm-$2/arch
第四个:ln -s arch-$3 asm-arm/arch
include/asm-arm/arch-s5pc11x
尽量不修改前面的地方。
第五个:
在include目录下asm-arm下创建一个proc文件,指向include/asm-arm/proc-armv
就是一个普通的目录
一共创建了4个符号链接。
比如一个头文件包含可能是#include
让程序具有可移植性。
uboot的配置详解2:
第123行到129行:mkconfig
include/
config.mk
>箭头表示创建文件
(2)创建config.mk是为了让主Makefile在第133行里面包含了。
思考一下:UBOOT的配置和编译过程的配合,编译的时候需要arch=ARM
CPU=xxx等这些变量来指导编译,配置的时候就是为了为编译阶段提供这些变量。
那为什么不在Makefile中直接定义这些变量去使用。而要在mkconfig脚本中创建config.mk然后又在Makefile中include这些文件呢?
将我们在使用的时候非常的方便,Makefile前面定义这些变量,维护的一些东西,自己做的一块开发板,直接这样。
时刻注意你当前所处的路径,靠你记住的。
APPEND = no追加到config.h文件。
(5)134行到144行,创建(默认情况下)或者是追加(make -a时追加)
(6)这个文件里面的内容就一行:#include
这个文件是我们移植x210最主要的文件。
整个的一个配置文件:
这个文件里面全部都是宏定义。
(7)x210_sd.h会被用来生成一个autoconfig.mk文件,这个文件会被主Makefile引入,指导整个编译过程,这里面的这些宏定义会影响我们对UBOOT中大部分.c文件中一些条件编译的选择。从而实现最终的可移植性。
注意:UBOOT的整个配置过程,很多文件是有关联的,有时候这个文件是在哪个文件创建出来的,有时候这个文件被那个文件包含进去的,有时候这个文件是由那个文件内容生成的决定的。
注意:UBOOT中配置和编译过程所有文件或者全局变量都是字符串形式的,不是指C语言字符串的意思。这意味着我们整个UBOOT的配置过程都是字符串匹配的,所以一定要细心。注意大小写,不要输错字符。最后会出现一些莫名其妙的错误,很难排查,这个是UBOOT移植。
10、UBOOT的链接脚本:
UBOOT的链接脚本和之前在裸机中的链接脚本并没有本质区别。
只是复杂度高一些,文件多一些。
输出的架构是ARM
ENTRY(_start)用来指定整个程序的入口地址,所谓的入口地址就是整个程序的开头地址,可以认为是整个程序的第一句指令。有点像C语言中的main
之前在裸机中告诉大家:指定我们程序链接地址有2钟方法:一种是在Makefil中ld的flags用-Ttext 0x20000000来指定;第二种是在链接脚本中开头用.=0x2000 0000来指定。两种都可以实现相同的效果。
其实这两种技巧可以共同使用的,即在链接脚本中指定,两个都指定以后,-Ttext为更高的优先级。在我们的ld工具的flags来指定。
(5)UBOOT的最终链接的起始地址就是用Makefile中用-Ttext来指定。
具体参见2.4.5.2节。TEXT_BASE变量,最终来源是Makefile中配置对应的命令中。
在代码段中必须注意文件排列的顺序;
会影响这些文件影响我们的.o文件。指定必须放在前面部分的那些文件,必须安排在前16KB内的文件,这些文件中的函数在前面16KB会被调用,
后面后16KB的东西就没什么关系了。
UBOOT的cmd段是讲解命令的阶段。