现在的位置: 首页 > 综合 > 正文

基于AMCC PPC405ep处理器的uboot移植(4)–U-Boot编译过程

2013年12月06日 ⁄ 综合 ⁄ 共 19313字 ⁄ 字号 评论关闭

1. 顶层Makefile文件分析

(1) 定义主机架构
HOSTARCH := $(shell uname -m | \
sed -e s/i.86/x86/ \
   -e s/sun4u/sparc64/ \
   -e s/arm.*/arm/ \
   -e s/sa110/arm/ \
   -e s/ppc64/powerpc/ \
   -e s/ppc/powerpc/ \
   -e s/macppc/powerpc/\
   -e s/sh.*/sh/)
#sed -e  表示后面跟的是一串命令脚本,而表达式“s/abc/def/”表示要从标准输入中,查找到内容为“abc”的,然后替换成“def”。其中“abc”表达式用可以使用“.”作为通配符。

#命令“uname –m”将输出主机CPU的体系架构类型。作者的电脑使用Intel Core2系列的CPU,因此“uname –m”输出“i686”。 “i686”可以匹配命令“sed -e s/i.86/i386/”中的“i.86”,因此在作者的机器上执行Makefile,HOSTARCH将被设置成“i386” 。

(2)定义主机操作系统类型
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
   sed -e 's/\(cygwin\).*/cygwin/')
#“uname –s”输出主机内核名字,作者使用Linux发行版Ubuntu9.10,因此“uname –s”结果是“Linux”。“tr '[:upper:]' '[:lower:]'”作用是将标准输入中的所有大写字母转换为响应的小写字母。因此执行结果是将HOSTOS 设置为“linux”。

(3)定义执行shell脚步的shell
# Set shell to bash if possible, otherwise fall back to sh
SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi; fi)
#"$$BASH"的作用实质上是生成了字符串“$BASH”(前一个$号的作用是指明第二个$是普通的字符)。若执行当前Makefile的shell中定义了“$BASH”环境变量,且文件“$BASH”是可执行文件,则SHELL的值为“$BASH”。否则,若“/bin/bash”是可执行文件,则SHELL值为“/bin/bash”。若以上两条都不成立,则将“sh”赋值给SHELL变量。

(4)设定编译输出目录
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif
#函数$( origin, variable) 输出的结果是一个字符串,输出结果由变量variable定义的方式决定,若variable在命令行中定义过,则origin函数返回值为"command line"。假若在命令行中执行了“export BUILD_DIR=/tmp/build”的命令,则“$(origin O)”值为“command line”,而BUILD_DIR被设置为“/tmp/build”。

ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)

# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
#若${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),)
# 若$(BUILD_DIR)为空,则将其赋值为当前目录路径(源代码目录)。并检查$(BUILD_DIR)目录是否存在

OBJTREE和LNDIR为存放生成文件的目录,TOPDIR与SRCTREE为源码所在目录
#CURDIR变量指示Make当前的工作目录,由于当前Make在U-Boot顶层目录执行Makefile,因此CURDIR此时就是U-Boot顶层目录
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE := $(CURDIR)
TOPDIR := $(SRCTREE)
LNDIR := $(OBJTREE)
export TOPDIR SRCTREE OBJTREE
#执行完上面的代码后, SRCTREE,src变量就是U-Boot代码顶层目录,而OBJTREE,obj变量就是输出目录,若没有定义BUILD_DIR环境变量,则SRCTREE,src变量与OBJTREE,obj变量都是U-Boot源代码目录

(5)定义变量MKCONFIG:这个变量指向一个脚本,即顶层目录的mkconfig
MKCONFIG := $(SRCTREE)/mkconfig

(6)myppc405ep_config执行的命令

当执行make myppc405ep_config时,因为根目录下的makefile并没有myppc405ep_config目标,所以将执行%_config这个目标,%代表着任意字符。
%_config附件代码如下;

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

%_config:: unconfig
@$(MKCONFIG) -A $(@:_config=)

unconfig定义中“@”的作用是执行该命令时不在shell显示;
“obj”变量就是编译输出的目录,因此“unconfig”的作用就是清除上次执行make*_config命令生成的配置文件,主要是include/config.h 和include/config.mk文件;

$(MKCONFIG)在上面指定为“$(SRCTREE)/mkconfig”
$(@:_config=)为将传进来的所有参数中的_config替换为空(其中“@”指规则的目标文件名,在这里就是“myppc405ep_config ”)
注:$(text:patternA=patternB),这样的语法表示把text变量每一个元素中结尾的patternA的文本替换为patternB,然后输出) ,因此$(@:_config=)的作用就是将myppc405ep_config中的_config去掉,得到myppc405ep。
所以“@$(MKCONFIG) -A $(@:_config=) ”实际上就是执行了如下命令:uboot-ppc/mkconfig -A myppc405ep

执行# make myppc405ep_config时,先执行unconfig目标,然后才执行命令 @$(MKCONFIG) -A $(@:_config=)。MKCONFIG 是顶层目录下的mkcofig脚本文件。

(7)make all
ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))  # !config.mk 存在
all:
sinclude $(obj)include/autoconf.mk.dep
sinclude $(obj)include/autoconf.mk
…………………………
else # !config.mk 不存在
all $(obj)u-boot.hex $(obj)u-boot.srec $(obj)u-boot.bin \
$(obj)u-boot.img $(obj)u-boot.dis $(obj)u-boot \
$(filter-out tools,$(SUBDIRS)) $(TIMESTAMP_FILE) \
updater depend dep tags ctags etags cscope $(obj)System.map:
@echo "System not configured - see README" >&2
@ exit 1

(7-1)若include/config.mk 文件存在,则$(wildcard $(obj)include/config.mk)命令执行的结果是“$(obj)include/config.mk”展开的字符串,否则结果为空。
由于include/config.mk是“make <board_name>_config”命令执行过程生成的,若从没有执行过“make <board_name>_config”命令则include/config.mk必然不存在。因此Make就执行else分支的代码,在输出“System not configured - see README”的信息后就返回了。

(7-2)下面再来分析“make all”命令正常执行的过程,在Makefile中有如下代码:
all:
sinclude $(obj)include/autoconf.mk.dep
sinclude $(obj)include/autoconf.mk
include/autoconf.mk文件中是与开发板相关的一些宏定义,在Makefile执行过程中需要根据某些宏来确定执行哪些操作。
下面简要分析include/autoconf.mk生成的过程,include/autoconf.mk生成的规则如下:
$(obj)include/autoconf.mk: $(obj)include/config.h
@$(XECHO) Generating $@ ; \
set -e ; \
: Extract the config macros ; \
$(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | \
sed -n -f tools/scripts/define2mk.sed > $@.tmp && \
mv $@.tmp $@
include/autoconf.mk依赖于make <board_name>_config命令生成的include/config.h。因此执行make <board_name>_config命令后再执行make all将更新include/autoconf.mk。

编译选项“-dM”的作用是输出include/common.h中定义的所有宏。

根据上面的规则,编译器提取include/common.h中定义的宏,然后输出给tools/scripts/define2mk.sed脚本处理,处理的结果就是include/autoconf.mk文件。其中tools/scripts/define2mk.sed脚本的主要完成了在include/common.h中查找和处理以“CONFIG_”开头的宏定义的功能。

include/common.h文件包含了include/config.h文件,而include/config.h文件又包含了config_defaults.h,configs/mini2440.h,asm/config.h文件。因此include/autoconf.mk实质上就是config_defaults.h,configs/myppc405ep.h,asm/config.h三个文件中“CONFIG_”开头的有效的宏定义的集合。

(8)
# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk
export    ARCH CPU BOARD VENDOR SOC
将make myppc405ep_config命令生成的include/config.mk包含进来。

若主机架构与开发板结构相同,就使用主机的编译器,而不是交叉编译器
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
若主机与目标机器体系架构相同,则使用gcc编译器而不是交叉编译器。

(9)
# load other configuration
include $(TOPDIR)/config.mk
最后将U-Boot顶层目录下的config.mk文件包含进来,该文件包含了对编译的一些设置。

(10)
# U-Boot objects....order is important (i.e. start must be first)
OBJS  = $(CPUDIR)/start.o
ifeq ($(CPU),x86)
OBJS += $(CPUDIR)/start16.o
OBJS += $(CPUDIR)/resetvec.o
endif
ifeq ($(CPU),ppc4xx)
OBJS += $(CPUDIR)/resetvec.o
endif
ifeq ($(CPU),mpc85xx)
OBJS += $(CPUDIR)/resetvec.o
endif

OBJS := $(addprefix $(obj),$(OBJS))

LIBS  = lib/libgeneric.o
LIBS += lib/lzma/liblzma.o
LIBS += lib/lzo/liblzo.o
……………………

LIBS变量指明了U-Boot需要的库文件,包括平台/开发板相关的目录、通用目录下相应的库,都通过相应的子目录编译得到的。
对于myppc405ep开发板,以上跟平台相关的有以下几个:
$(CPUDIR)/start.o
board/$(VENDOR)/common/lib$(VENDOR).o
$(CPUDIR)/lib$(CPU).o
$(CPUDIR)/$(SOC)/lib$(SOC).o
arch/$(ARCH)/lib/lib$(ARCH).o
其余都是与平台无关的。

ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map
ifeq ($(CONFIG_NAND_U_BOOT),y)
ALL += $(obj)u-boot-nand.bin
endif

ifeq ($(CONFIG_ONENAND_U_BOOT),y)
ALL += $(obj)u-boot-onenand.bin
ONENAND_BIN ?= $(obj)onenand_ipl/onenand-ipl-2k.bin
endif

ifeq ($(CONFIG_MMC_U_BOOT),y)
ALL += $(obj)mmc_spl/u-boot-mmc-spl.bin
endif
对于有的开发板,U-Boot支持在NAND Flash启动,这些开发板的配置文件定义了CONFIG_NAND_U_BOOT,CONFIG_ONENAND_U_BOOT。

u-boot.srec,u-boot.bin,System.map都依赖与u-boot。因此执行“make all”命令将生成u-boot,u-boot.srec,u-boot.bin,System.map 。
其中u-boot是ELF文件,u-boot.srec是Motorola S-Record format文件,System.map 是U-Boot的符号表,u-boot.bin是最终烧写到开发板的二进制可执行的文件。

(11)u-boot.bin文件生成的过程
ELF格式“u-boot”文件生成规则如下:
$(obj)u-boot: depend \
$(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
$(GEN_UBOOT)
ifeq ($(CONFIG_KALLSYMS),y)
smap=`$(call SYSTEM_MAP,u-boot) | \
awk '$$2 ~ /[tTwW]/ {printf $$1 $$3 "\\\\000"}'` ; \
$(CC) $(CFLAGS) -DSYSTEM_MAP="\"$${smap}\"" \
-c common/system_map.c -o $(obj)common/system_map.o
$(GEN_UBOOT) $(obj)common/system_map.o
endif
这里生成的$(obj)u-boot目标就是ELF格式的U-Boot文件了。由于CONFIG_KALLSYMS未定义,因此ifeq ($(CONFIG_KALLSYMS),y)与endif间的代码不起作用。

其中depend,$(SUBDIRS),$(OBJS),$(LIBBOARD),$(LIBS),$(LDSCRIPT), $(obj)u-boot.lds是$(obj)u-boot的依赖,而$(GEN_UBOOT)是编译命令。

下面分析$(obj)u-boot的各个依赖:
(11-1)依赖目标depend
# Explicitly make _depend in subdirs containing multiple targets to prevent
# parallel sub-makes creating .depend files simultaneously.
depend dep: $(TIMESTAMP_FILE) $(VERSION_FILE) \
$(obj)include/autoconf.mk \
$(obj)include/generated/generic-asm-offsets.h
for dir in $(SUBDIRS) $(CPUDIR) $(dir $(LDSCRIPT)) ; do \
$(MAKE) -C $$dir _depend ; done
对于$(SUBDIRS) $(CPUDIR) $(dir $(LDSCRIPT))中的每个元素都进入该目录执行“make _depend”,生成各个子目录的.depend文件,.depend列出每个目标文件的依赖文件。

(11-2)依赖SUBDIRS
SUBDIRS = tools \
 examples/standalone \
 examples/api

$(SUBDIRS): depend
$(MAKE) -C $@ all
执行tools,examples/standalone,examples/api目录下的Makefile。

(11-3)OBJS
在前面定义的OBJS的值是:
OBJS  = $(CPUDIR)/start.o
ifeq ($(CPU),x86)
OBJS += $(CPUDIR)/start16.o
OBJS += $(CPUDIR)/resetvec.o
endif
ifeq ($(CPU),ppc4xx)
OBJS += $(CPUDIR)/resetvec.o
endif
ifeq ($(CPU),mpc85xx)
OBJS += $(CPUDIR)/resetvec.o
endif

它们通过如下代码编译得到:
$(OBJS): depend
$(MAKE) -C $(CPUDIR) $(if $(REMOTE_BUILD),$@,$(notdir $@))
以上规则表明,对于OBJS包含的每个成员,都进入$(CPUDIR)目录(即cpu/ppc4xx)编译它们。

(11-4)LIBBOARD
LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).o
LIBBOARD := $(addprefix $(obj),$(LIBBOARD))

$(LIBBOARD): depend $(LIBS)
$(MAKE) -C $(dir $(subst $(obj),,$@))
这里LIBBOARD的值是 $(obj)board/amcc/mini2440/libmyppc405ep.o。
make执行board/amcc/myppc405ep目录下的Makefile,生成libmyppc405ep.o。

(11-5)LIBS
$(LIBS): depend $(SUBDIRS)
$(MAKE) -C $(dir $(subst $(obj),,$@))
上面的规则表明,对于LIBS中的每个成员,都进入相应的子目录执行“make”命令编译它们。
例如对于LIBS中的“common/libcommon.o”成员,程序将进入common目录执行Makefile,生成libcommon.o。

(11-6)LDSCRIPT
# If board code explicitly specified LDSCRIPT or CONFIG_SYS_LDSCRIPT, use
# that (or fail if absent).  Otherwise, search for a linker script in a
# standard location.
ifndef LDSCRIPT
#LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds.debug
……………………

$(LDSCRIPT): depend
$(MAKE) -C $(dir $@) $(notdir $@)
 “$(MAKE) -C $(dir $@) $(notdir $@)”命令经过变量替换后就是“make -C cpu/ppc4xx/u-boot.lds”。也就是转到cpu/ppc4xx/目录下,执行“make u-boot.lds”命令

(11-7)$(obj)u-boot.lds

u-boot.lds是一个链接脚本,那什么是链接脚本?链接脚本就是程序链接时的的参考文件,其目的是描述输入文件中各段应该怎么样被映射到输出文件,以及程序运行时的内存布局等等。

$(obj)u-boot.lds: $(LDSCRIPT)

$(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@
以上执行结果实质上是将cpu/ppc4xx/u-boot.lds经编译器简单预处理后输出到U-Boot顶层目录下的u-boot.lds文件。其中的cpu/ppc4xx/u-boot.lds文件内容如下:
OUTPUT_ARCH(powerpc)

PHDRS
{
  text PT_LOAD;
  bss PT_LOAD;
}

SECTIONS
{
  /* Read-only sections, merged into text segment: */
  . = + SIZEOF_HEADERS; 这里的点号称为位置计数器,通俗的讲,它代表这当前位置,这条语句的意思就是当前位置定义为SIZEOF_HEADERS地址。
  
  .text      :  定义了该输出位置为代码段,花括号里定义了代码段的具体内容
  {
    *(.text*) 代表所有文件,意思是将所有目标文件的代码段都链接到这一区域
   } :text
    _etext = .;
    PROVIDE (etext = .);
    .rodata    : 只读数据段
   {
    *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
  } :text

  /* Read-write section, merged into data segment: */
  . = (. + 0x00FF) & 0xFFFFFF00;
  _erotext = .;
  PROVIDE (erotext = .);
  .reloc   :
  {
    _GOT2_TABLE_ = .;
    KEEP(*(.got2))
    KEEP(*(.got))
    PROVIDE(_GLOBAL_OFFSET_TABLE_ = . + 4);
    _FIXUP_TABLE_ = .;
    KEEP(*(.fixup))
  }
  __got2_entries = ((_GLOBAL_OFFSET_TABLE_ - _GOT2_TABLE_) >> 2) - 1;
  __fixup_entries = (. - _FIXUP_TABLE_) >> 2;

  .data    : 数据段
  {
    *(.data*)
    *(.sdata*)
  }
  _edata  =  .;
  PROVIDE (edata = .);

  . = .;
  __u_boot_cmd_start = .; 将 __u_boot_cmd_start指定为当前地址
  .u_boot_cmd : { *(.u_boot_cmd) } 存放所有U-Boot命令对应的cmd_tbl_t结构体
  __u_boot_cmd_end = .; 将__u_boot_cmd_end指定为当前地址

  . = .;
  __start___ex_table = .;
  __ex_table : { *(__ex_table) }
  __stop___ex_table = .;

  . = ALIGN(256); ALGN用于产生对齐的代码或数据
  __init_begin = .;
  .text.init : { *(.text.init) }
  .data.init : { *(.data.init) }
  . = ALIGN(256);
  __init_end = .;

#ifdef CONFIG_440
  .bootpg RESET_VECTOR_ADDRESS - 0xffc :
  {
    arch/powerpc/cpu/ppc4xx/start.o (.bootpg)

    /*
     * PPC440 board need a board specific object with the
     * TLB definitions. This needs to get included right after
     * start.o, since the first shadow TLB only covers 4k
     * of address space.
     */
#ifdef CONFIG_INIT_TLB
    CONFIG_INIT_TLB (.bootpg)
#else
    CONFIG_BOARDDIR/init.o (.bootpg)
#endif
  } :text = 0xffff
#endif

  .resetvec RESET_VECTOR_ADDRESS :
  {
    KEEP(*(.resetvec))
  } :text = 0xffff

  . = RESET_VECTOR_ADDRESS + 0x4;

  /*
   * Make sure that the bss segment isn't linked at 0x0, otherwise its
   * address won't be updated during relocation fixups.  Note that
   * this is a temporary fix.  Code to dynamically the fixup the bss
   * location will be added in the future.  When the bss relocation
   * fixup code is present this workaround should be removed.
   */
#if (RESET_VECTOR_ADDRESS == 0xfffffffc)
  . |= 0x10;
#endif

  __bss_start = .; bss段
  .bss (NOLOAD)       :
  {
   *(.bss*)
   *(.sbss*)
   *(COMMON)
  } :bss

  . = ALIGN(4);
  __bss_end__ = . ;
  PROVIDE (end = .);
}
u-boot.lds实质上是U-Boot连接脚本。对于生成的U-Boot编译生成的“u-boot”文件,可以使用objdump命令可以查看它的分段信息:
[root@localhost uboot-ppc]# objdump -x u-boot | more
部分输出信息如下:
u-boot:     file format elf32-big
u-boot
architecture: UNKNOWN!, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0xfffc0000

Program Header:
    LOAD off    0x00000074 vaddr 0xfffc0000 paddr 0xfffc0000 align 2**2
         filesz 0x00040000 memsz 0x00040000 flags rwx
    LOAD off    0x00040074 vaddr 0x00000010 paddr 0x00000010 align 2**2
         filesz 0x00000000 memsz 0x00010ca8 flags rw-

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0002cd7c  fffc0000  fffc0000  00000074  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rodata       0000b792  fffecd7c  fffecd7c  0002cdf0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .reloc        00002a2c  ffff8600  ffff8600  00038674  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .data         00001810  ffffb02c  ffffb02c  0003b0a0  2**2
                  CONTENTS, ALLOC, LOAD, DATA
u-boot.lds还跟U-Boot启动阶段复制代码到RAM空间的过程以及U-Boot命令执行过程密切相关,具体请结合U-Boot源代码理解。

(11-8)编译命令GEN_UBOOT
GEN_UBOOT = \
UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
sed  -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) $$UNDEF_SYM $(__OBJS) \
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
-Map u-boot.map -o u-boot
以上命令使用$(LDFLAGS)作为连接脚本,最终生成“u-boot”文件。

(12)u-boot.bin文件生成过程
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(BOARD_SIZE_CHECK)
从U-Boot编译输出信息中可以知道上面的命令实质上展开为:
ppc_4xx-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin
编译命令中的“-O binary”选项指定了输出的文件为二进制文件。而“--gap-fill=0xff”选项指定使用“0xff”填充段与段间的空闲区域。这条编译命令实现了ELF格式的U-Boot文件到BIN格式的转换。

(13)System.map文件的生成
System.map是U-Boot的符号表,它包含了U-Boot的全局变量和函数的地址信息。将System.map生成的规则如下:
SYSTEM_MAP = \
$(NM) $1 | \
grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
LC_ALL=C sort
$(obj)System.map: $(obj)u-boot
@$(call SYSTEM_MAP,$<) > $(obj)System.map
ppc_4xx-nm u-boot | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | LC_ALL=C sort  > System.ma
也就是将ppc_4xx-nm命令查看u-boot的输出信息经过过滤和排序后输出到System.map。
为了了解System.map文件的作用,打开System.map:
00000010 A __bss_start
00000010 b irq_vecs
00000190 b timestamp
00000194 b decrementer_count
00000198 b images
000002c8 b data.4034
000003e9 A __fixup_entries
000006a2 A __got2_entries
……………………
fffc20a4 T _end_of_vectors
fffc2100 T _start
fffc21d4 T transfer_to_handler
fffc2270 t int_return
fffc2350 t crit_return
……………………
System.map表示的是地址标号到该标号表示的地址的一个映射关系。System.map每一行的格式都是“addr type name”,addr是标号对应的地址值,name是标号名,type表示标号的类型。
U-Boot的编译和运行并不一定要生成System.map,这个文件主要是提供给用户或外部程序调试时使用的。


2. 顶层mkcofig脚本
(1)
APPEND=no # Default: Create new config file
BOARD_NAME="" # Name to print in make output
if [ \( $# -eq 2 \) -a \( "$1" = "-A" \) ] ; then
# Automatic mode
line=`egrep -i "^[[:space:]]*${2}[[:space:]]" boards.cfg` || {
echo "make: *** No rule to make target \`$2_config'.  Stop." >&2
exit 1
}
$#代表传递给程序的参数的总个数,-a的意思就是前后两个都成立,if的判断条件才为真,$1为第一参数,而我们执行的命令是:mkconfig –A myppc405ep,参数个数为2,第一个参数为-A,所以if条件为真。

line=语句是读取boards.cfg文件中的${2}的信息,而我们传递的第二个参数是myppc405ep,所以就是读取boards.cfg文件中的myppc405ep的信息,如果不存在myppc405ep的信息的话,就打印出一条语句:make: *** No rule to make target \`$2_config'. Stop." >&2并退出。
从boards.cfg读出的信息为:myppc405ep powerpc ppc4xx - amcc。

(2)
while [ $# -gt 0 ] ; do 参数个数大于0执行
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%_config}" ; shift ;;
-t) shift ; TARGETS="`echo $1 | sed 's:_: :g'` ${TARGETS}" ; shift ;;
*)  break ;;
esac
done

[ $# -lt 4 ] && exit 1 检查参数合法性,参数个数少于4个或多于6个都被认为是错误的
[ $# -gt 7 ] && exit 1

# Strip all options and/or _config suffixes
CONFIG_NAME="${1%_config}"

[ "${BOARD_NAME}" ] || BOARD_NAME="${1%_config}"

shift的作用是使$1=$2,$2=$3,$3=$4….,而原来的$1将丢失。因此while循环的作用是,依次处理传递给mkconfig脚本的选项。由于mkconfig –A myppc405ep没有这些选项,因此while循环中的代码不会被执行。

最后将BOARD_NAME的值设置为${1%_config}的值,在这里就是“myppc405ep_config”。

(3)创建到目标板相关的目录的链接
# Create link to architecture specific headers

if [ "$SRCTREE" != "$OBJTREE" ] ; then
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/arch/${arch}/include/asm asm
LNPREFIX=${SRCTREE}/arch/${arch}/include/asm/
cd ../include
mkdir -p asm
else
cd ./include
rm -f asm
ln -s ../arch/${arch}/include/asm asm
fi
#若将目标文件设定为输出到源文件所在目录,即"$SRCTREE" == "$OBJTREE",则以上代码在include目录下建立了到arch/powerpc/include/asm目录的符号链接asm。

if [ -z "${soc}" ] ; then
ln -s ${LNPREFIX}arch-${cpu} asm/arch
else
ln -s ${LNPREFIX}arch-${soc} asm/arch
fi
#建立符号链接到asm/arch。若${soc}为空,ln -s ${SRCTREE}/arch/powerpc/include/asm/arch-ppc4xx asm/arch

if [ "${arch}" = "arm" ] ; then
rm -f asm/proc
ln -s ${LNPREFIX}proc-armv asm/proc
fi
若目标板是arm架构,则上面的代码将建立符号连接include/asm-arm/proc,使其链接到目录proc-armv目录

(4)创建include/config.mk文件
# Create include file for Make

echo "ARCH   = ${arch}"  >  config.mk
echo "CPU    = ${cpu}"   >> config.mk
echo "BOARD  = ${board}" >> config.mk

[ "${vendor}" ] && echo "VENDOR = ${vendor}" >> config.mk

[ "${soc}"    ] && echo "SOC    = ${soc}"    >> config.mk

# Assign board directory to BOARDIR variable
if [ -z "${vendor}" ] ; then
    BOARDDIR=${board}
else
    BOARDDIR=${vendor}/${board}
fi
以上代码指定board目录下的一个目录为当前开发板专有代码的目录。
若${vendor}为空则BOARDDIR设置为${board},否则设置为${vendor}/${board}。在这里由于${vendor}不为空,因此BOARDDIR被设置为amcc/myppc405ep.

(5) 创建与目标板相关的文件include/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

for i in ${TARGETS} ; do
i="`echo ${i} | sed '/=/ {s/=/\t/;q } ; { s/$/\t1/ }'`"
echo "#define CONFIG_${i}" >>config.h ;
done

cat << EOF >> config.h
#define CONFIG_BOARDDIR board/$BOARDDIR
#include <config_cmd_defaults.h>
#include <config_defaults.h>
#include <configs/${CONFIG_NAME}.h>
#include <asm/config.h>
EOF

这里的“cat << EOF >> config.h”表示将输入的内容追加到config.h中,直到出现“EOF”这样的标识为止
若APPEND为no,则创建新的include/config.h文件。若APPEND为yes,则将新的配置内容追加到include/config.h文件后面。由于APPEND的值保持“no”,因此config.h被创建了,并添加了如下的内容
#/* Automatically generated - do not edit */
#define CONFIG_BOARDDIR board/amcc/myppc405ep
#include <config_cmd_defaults.h>
#include <config_defaults.h>
#include <configs/${CONFIG_NAME}.h>
#include <asm/config.h>

总结:
mkconfig主要完成3件事:
(1)创建链接文件
(2)创建includ/config.mk
(3)创建includ/config.h

5. 对U-Boot顶层目录下的config.mk文件进行分析:
(1) 设置obj与src
ifneq ($(OBJTREE),$(SRCTREE))
ifeq ($(CURDIR),$(SRCTREE))
dir :=
else
dir := $(subst $(SRCTREE)/,,$(CURDIR))
endif

obj := $(if $(dir),$(OBJTREE)/$(dir)/,$(OBJTREE)/)
src := $(if $(dir),$(SRCTREE)/$(dir)/,$(SRCTREE)/)

$(shell mkdir -p $(obj))
else
obj :=
src :=
endif
由于目标输出到源代码目录下,因此执行完上面的代码后,src和obj都是空。

(2)设置编译选项
PLATFORM_RELFLAGS =
PLATFORM_CPPFLAGS =
PLATFORM_LDFLAGS =
用这3个变量表示交叉编译器的编译选项,在后面Make会检查交叉编译器支持的编译选项,然后将适当的选项添加到这3个变量中。

#
# Option checker (courtesy linux kernel) to ensure
# only supported compiler options are used
#
cc-option = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \
> /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;)
变量CC和CFLAGS在后面的代码定义为延时变量,其中的CC即arm-linux-gcc。函数cc-option用于检查编译器CC是否支持某选项。将2个选项作为参数传递给cc-option函数,该函数调用CC编译器检查参数1是否支持,若支持则函数返回参数1,否则返回参数2 (因此CC编译器必须支持参数1或参数2,若两个都不支持则会编译出错)。

(3)指定交叉编译工具
#
# 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

(4)包含与开发板相关的配置文件
# Load generated board configuration
sinclude $(OBJTREE)/include/autoconf.mk
sinclude $(OBJTREE)/include/config.mk

CPUDIR=arch/$(ARCH)/cpu/$(CPU)
ifneq ($(SRCTREE)/$(CPUDIR),$(wildcard $(SRCTREE)/$(CPUDIR)))
CPUDIR=arch/$(ARCH)/cpu
endif

sinclude $(TOPDIR)/arch/$(ARCH)/config.mk # include architecture dependend rules
sinclude $(TOPDIR)/$(CPUDIR)/config.mk # include  CPUspecific rules
将"\arch\powerpc\config.mk"和“arch\powerpc\cpu\ppc4xx\config.mk”包含进来。这个脚本主要设定了跟处理器相关的编译选项。

ifdef SOC
sinclude $(TOPDIR)/$(CPUDIR)/$(SOC)/config.mk
# include  SoC specific rules
endif
没有定义SOC

ifdef VENDOR
BOARDDIR = $(VENDOR)/$(BOARD)
else
BOARDDIR = $(BOARD)
endif
BOARDDIR的值是amcc/myppc405ep,表示开发板特有的代码所在的目录。

ifdef BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk
# include board specific rules
endif
将“board/amcc/myppc405ep/config.mk”包含进来。但目录下没有找到此文件。

(5)指定隐含的编译规则
# Allow boards to use custom optimize flags on a per dir/file basis
BCURDIR = $(subst $(SRCTREE)/,,$(CURDIR:$(obj)%=%))
ALL_AFLAGS = $(AFLAGS) $(AFLAGS_$(BCURDIR)/$(@F)) $(AFLAGS_$(BCURDIR))
ALL_CFLAGS = $(CFLAGS) $(CFLAGS_$(BCURDIR)/$(@F)) $(CFLAGS_$(BCURDIR))
$(obj)%.s: %.S
$(CPP) $(ALL_AFLAGS) -o $@ $<
$(obj)%.o: %.S
$(CC)  $(ALL_AFLAGS) -o $@ $< -c
$(obj)%.o: %.c
$(CC)  $(ALL_CFLAGS) -o $@ $< -c
$(obj)%.i: %.c
$(CPP) $(ALL_CFLAGS) -o $@ $< -c
$(obj)%.s: %.c

$(CC)  $(ALL_CFLAGS) -o $@ $< -c -S


参考文章:

http://www.cnblogs.com/heaad/archive/2010/07/17/1779806.html

http://redboot.blogbus.com/logs/34991963.html

http://blog.chinaunix.net/space.php?uid=18921523&do=blog&id=165029

抱歉!评论已关闭.