• 正点原子嵌入式linux驱动开发——U-boot顶层Makefile详解


    在学习uboot源码之前,要先看一下顶层Makefile,分析gcc版本代码的时候一定是先从顶层Makefile开始的,然后再是子Makefile,这样通过层层分析Makefile即可了解整个工程的组织结构顶层Makefile也就是uboot根目录下的Makefile文件,由于顶层Makefile文件内容比较多,所以我们将其分开来看。

    U-Boot工程目录分析

    为了方便查看和操作,uboot启动源码分析就在Windows下进行,将正点原子提供的uboot源码进行解压,解压完成以后的目录如下图所示:
    未编译的uboot目录文件
    上图是正点原子提供的未编译的uboot源码目录,在分析uboot源码之前一定要先在Ubuntu中编译一下uboot源码,因为编译过程会生成一些文件,而生成的这些恰恰是分析uboot源码不可或缺的文件。

    参考上一篇笔记的内容中的方法编译正点原子提供的uboot源码,然后将其发送到Windows下,编译后的目录如下图所示:
    编译后的uboot源码文件
    对比以上两图中的文件,可以看到编译后的uboot要比没编译之前多了好多文件,这些文件夹或文件的含义如下图所示:
    uboot目录列表
    重点关注的文件夹和文件如下所示:

    1、arch文件夹

    这个文件夹里面存放着和架构有关的文件,如下图所示:
    arch文件夹
    从上图可以看出有很多架构,比如arm、m68k、x86等,本篇笔记是采用STM32MP157,也就是用的ARM芯片,所以只需要关心arm文件夹即可,打开arm文件夹里面内容如下图所示:
    arm文件夹
    上图只截取了一部分,还有一部分mach-xxx的文件夹。mach开头的文件夹是跟具体的设备有关的,比如“mach-exynos”就是跟三星的exyons系列CPU有关的文件。本篇笔记是针对STM32MP1,所以要关注“mach-stm32mp”这个文件夹另外“cpu”这个文件夹也是和cpu架构有关,打开以后如下图所示:
    cpu文件夹
    从上图可以看出有多种ARM架构相关的文件夹,STM32MP1使用的Cortex-A7内核,Cortex-A7属于armv7,所以我们要关心“armv7”这个文件夹。cpu文件夹里面有个名为“u-boot.lds”的链接脚本文件,这个就是ARM芯片所使用的u-boot链接脚本文件!armv7这个文件夹里面的文件都是跟ARMV7架构有关的,是我们分析uboot启动源码的时候需要重点关注的

    2、board文件夹

    board文件夹就是和具体的板子有关的,打开此文件夹,里面全是不同的板子,正点原子的开发板肯定也在里面(正点原子添加的),borad文件夹里面有个名为“st”的文件夹如下图所示:
    board文件夹
    所有使用ST芯片的板子都放到此文件夹中,打开“st”文件夹,如下图所示:
    st文件夹
    上图中有stm32f429-discovery、stm32f469-evaluation、stm32f746-discovery等目录,这些是ST官方的STM32开发板。stm32mp1这个目录就是针对STM32MP1系列芯片对应的板子

    3、configs文件夹

    此文件夹为uboot配置文件夹,uboot是可配置的,但要从头开始一个一个项目的配置,那就太麻烦了,因此一般半导体或者开发板厂商都会制作好一个配置文件。可以在这个做好的配置文件基础上来添加自己想要的功能,这些半导体厂商或者开发板厂商制作好的配置文件统一命名为“xxx_defconfig”,xxx表示开发板名字,这些 defconfig文件都存放在configs文件夹,因此,ST官方开发板和正点原子的开发板配置文件肯定也在这个文件夹中,如下图所示:
    正点原子开发板配置文件
    上图中stm32mp157d_atk_defconfig就是正点原子STM32MP157开发板对应的默认配置文件。使用“make xxx_defconfig”命令即可配置uboot,比如:

    make stm32mp157d_atk_defconfig

    上述命令就是配置正点原子STM32MP157核心板所使用的uboot。

    注意!在编译uboot之前一定要使用 defconfig来配置 uboot!

    在上一篇学习uboot使用中编译uboot的时候,有下面一行命令:

    make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157d_atk_defconfig

    这个就是调用stm32mp157d_atk_defconfig来配置uboot,只是这个命令还带了一些其它参数而已,ARCH用来指定CPU架构为arm,CROSS_COMPILE用来指定所使用的交叉编译器
    为arm-none-linux-gnueabihf-gcc。

    4、.u-boot.xxx_cmd文件

    .u-boot.xxx_cmd是一系列的文件,这些文件都是编译生成的,都是一些命令文件,比如文件.u-boot.bin.cmd,看名字应该是和u-boot.bin有关的,此文件的内容如下:

    cmd_u-boot.bin := cp u-boot-dtb.bin u-boot.bin
    
    • 1

    .u-boot.bin.cmd里面定义了一个变量:cmd_u-boot.bin,此变量的值为“cp u-boot-dtb.bin u-boot.bin”,也就是拷贝一份u-boot-dtb.bin文件,并且重命名为u-boot.bin,这个就是u-boot.bin的来源,来自于文件u-boot-dtb.bin。

    u-boot-dtb.bin是怎么来的呢?文件.u-boot-dtb.bin.cmd就是用于生成u-boot.dtb.bin的,此文件内容如下:

    cmd_u-boot-dtb.bin := cat u-boot-nodtb.bin dts/dt.dtb > u-boot-dtb.bin
    
    • 1

    cmd_u-boot-dtb.bin用于将设备树.dtb文件和uboot的bin文件结合起来,STM32MP157的uboot用到了设备树 (Device Tree),因此最终生成的uboot bin文件里面要有.dtb内容。u-boot-nodtb.bin就是原始的、不含dtb的uboot bin文件

    u-boot-nodtb.bin是用.u-boot-nodtb.bin.cmd文件生成的,内容如下:

    cmd_u-boot-nodtb.bin := arm-none-linux-gnueabihf-objcopy --gap-fill=0xff -j .text -j .secure_text -j .secure_data -j .rodata -j .hash -j .data -j .got -j .got.plt -j .u_boot_list -j .rel.dyn -j .binman_sym_table -j .text_rest -j .dtb.init.rodata -j .efi_runtime -j .efi_runtime_rel -O binary u-boot u-boot-nodtb.bin
    
    • 1

    这里用到了arm-none-linux-gnueabihf-objcopy,使用objcopy将ELF格式的u-boot文件转换为二进制的u-boot-nodtb.bin文件

    文件u-boot是ELF格式文件,文件.u-boot.cmd用于生成u-boot,文件内容如下:

    cmd_u-boot := arm-none-linux-gnueabihf-ld.bfd -pie --gc-sections -Bstatic --no-dynamic-linker -Ttext 0xC0100000 -o u-boot -T u-boot.lds arch/arm/cpu/armv7/start.o --start-group arch/arm/cpu/built-in.o arch/arm/cpu/armv7/built-in.o arch/arm/lib/built-in.o arch/arm/mach-stm32mp/built-in.o board/st/common/built-in.o board/st/stm32mp1/built-in.o cmd/built-in.o common/built-in.o disk/built-in.o drivers/built-in.o drivers/dma/built-in.o drivers/gpio/built-in.o drivers/i2c/built-in.o drivers/net/built-in.o drivers/net/phy/built-in.o drivers/power/built-in.o drivers/power/battery/built-in.o drivers/power/domain/built-in.o drivers/power/fuel_gauge/built-in.o drivers/power/mfd/built-in.o drivers/power/pmic/built-in.o drivers/power/regulator/built-in.o drivers/serial/built-in.o drivers/spi/built-in.o drivers/usb/cdns3/built-in.o drivers/usb/common/built-in.o drivers/usb/dwc3/built-in.o drivers/usb/emul/built-in.o drivers/usb/eth/built-in.o drivers/usb/gadget/built-in.o drivers/usb/gadget/udc/built-in.o drivers/usb/host/built-in.o drivers/usb/musb-new/built-in.o drivers/usb/musb/built-in.o drivers/usb/phy/built-in.o drivers/usb/ulpi/built-in.o env/built-in.o fs/built-in.o lib/built-in.o net/built-in.o --end-group arch/arm/lib/eabi_compat.o arch/arm/lib/lib.a -Map u-boot.map; true
    
    • 1

    .u-boot.cmd使用到了arm-none-linux-gnueabihf-ld.bfd,也就是链接工具,使用ld.bfd将各个built-in.o文件链接在一起就形成了u-boot文件。uboot在编译的时候会将同一个目录中的所有.c文件都编译在一起,并命名为built-in.o,相当于将众多的.c文件对应的.o文件集合在一起,这个就是u-boot文件的来源。

    如果要向STM32MP157内部烧写uboot,此时烧写的是u-boot.stm32文件,而不是u-boot.bin文件u-boot.stm32是由文件.u-boot.stm32.cmd来完成的,此文件内容如下:

    cmd_u-boot.stm32 := ./tools/mkimage -T stm32image -a 0xC0100000 -e 0xC0100000 -d u-boot.bin u-boot.stm32 >u-boot.stm32.log && cat u-boot.stm32.log
    
    • 1

    可以看出,这里用到了工具tools/mkimage,和stm32image,通过这两个工具将u-boot.bin转换为u-boot.stm32。

    文件.u-boot.lds.cmd就是用于生成u-boot.lds链接脚本的,由于.u-boot.lds.cmd文件内容太多,这里就不列出来了。uboot根目录下的u-boot.lds链接脚本就是来源于arch/arm/cpu/u-boot.lds文件。

    5、Makefile文件

    这个是顶层Makefile文件,Makefile是支持嵌套的,也就是顶层 Makefile可以调用子目录中的Makefile文件。Makefile嵌套在大项目中很常见,一般大项目里面所有的源代码都不会放到同一个目录中,各个功能模块的源代码都是分开的,各自存放在各自的目录中。每个功能模块目录下都有一个Makefile,这个Makefile只处理本模块的编译链接工作,这样所有的编译链接工作就不用全部放到一个Makefile中,可以使得Makefile变得简洁明了。uboot源码根目录下的Makefile是顶层Makefile,会调用其它的模块的 Makefile文件,比如drivers/adc/Makefile。当然了,顶层Makefile要做的工作可远不止调用子目录 Makefile这么简单,关于顶层Makefile的内容稍后会有详细的讲解。

    6、u-boot.xxx文件

    u-boot.xxx同样也是一系列文件,包括u-boot、u-boot-dtb.bin、u-boot-nodtb.bin、u-boot.bin、u-boot.cfg、u-boot.dtb、u-boot.lds、u-boot.map、u-boot.srec、u-boot.stm32和u-boot.sym,这些文
    件的含义如下:

    • u-boot:编译出来的ELF格式的uboot镜像文件;
    • u-boot-dtb.bin:编译出来的含有设备树.dtb的uboot镜像文件;
    • u-boot-nodtb.bin:编译出来的不含有设备树.dtb的uboot镜像文件,和u-boot.bin一样;
    • u-boot.bin:编译出来的二进制格式的uboot可执行镜像文件;
    • u-boot.cfg:uboot的另外一种配置文件;
    • u-boot.dtb:uboot设备树编译后的.dtb文件;
    • u-boot.lds:链接脚本;
    • u-boot.map:uboot映射文件,通过查看此文件可以知道某个函数被链接到了哪个地址上;
    • u-boot.srec:S-Record格式的镜像文件;
    • u-boot.stm32:最终要写到 STM32MP157的 uboot文件;
    • u-boot.sym:uboot符号文件。

    7、.config文件

    uboot配置文件,使用命令“make xxx_defconfig”配置uboot以后就会自动生成, ,.config内容如下 (太长,有省略):

    #
    # Automatically generated file; DO NOT EDIT.
    # U-Boot 2020.01-stm32mp-r1 Configuration
    #
    CONFIG_CREATE_ARCH_SYMLINK=y
    # CONFIG_ARC is not set
    CONFIG_ARM=y
    # CONFIG_M68K is not set
    # CONFIG_MICROBLAZE is not set
    # CONFIG_MIPS is not set
    # CONFIG_NDS32 is not set
    # CONFIG_NIOS2 is not set
    # CONFIG_PPC is not set
    # CONFIG_RISCV is not set
    # CONFIG_SANDBOX is not set
    # CONFIG_SH is not set
    # CONFIG_X86 is not set
    # CONFIG_XTENSA is not set
    CONFIG_SYS_ARCH="arm"
    CONFIG_SYS_CPU="armv7"
    CONFIG_SYS_SOC="stm32mp"
    CONFIG_SYS_VENDOR="st"
    CONFIG_SYS_BOARD="stm32mp1"
    CONFIG_SYS_CONFIG_NAME="stm32mp1"
    ……
    #
    # Boot commands
    #
    # CONFIG_CMD_BOOTD is not set
    CONFIG_CMD_BOOTM=y
    CONFIG_CMD_BOOTZ=y
    CONFIG_BOOTM_LINUX=y
    CONFIG_BOOTM_NETBSD=y
    ……
    CONFIG_LIB_DATE=y
    # CONFIG_UNIT_TEST is not set
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    可以看出.config文件中都是以“CONFIG_”开始的配置项,这些配置项就是Makefile中的变量,因此后面都跟有相应的值,uboot的顶层Makefile或子Makefile会调用这些变量值。在.config中会有大量的变量值为 y’,这些为‘y’的变量一般用于控制某项功能是否使能,为‘y’的话就表示功能使能,比如:

    CONFIG_CMD_BOOTM=y

    如果使能了bootd这个命令的话,CONFIG_CMD_BOOTM就为‘y’。在cmd/Makefile中有如下代码:

    # SPDX-License-Identifier: GPL-2.0+
    #
    # (C) Copyright 2004-2006
    # Wolfgang Denk, DENX Software Engineering, wd@denx.de.
    
    ifndef CONFIG_SPL_BUILD
    # core command
    obj-y += boot.o
    obj-$(CONFIG_CMD_BOOTM) += bootm.o
    obj-y += help.o
    obj-y += version.o
    
    ……
    CFLAGS_ethsw.o := -Wno-enum-conversion
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在以上示例代码中,有如下一行代码:

    obj-$(CONFIG_CMD_BOOTM) += bootm.o
    
    • 1

    CONFIG_CMD_BOOTM=y,将其展开就是:

    obj-y += bootm.o
    
    • 1

    也就是给obj-y追加了一个“bootm.o”,obj-y包含着所有要编译的文件对应的.o文件,这里表示需要编译文件cmd/bootm.c。相当于通过“CONFIG_CMD_BOOTD=y”来使能bootm这个命令,进而编译cmd/bootm.c这个文件,这个文件实现了命令bootm。在uboot和Linux内核中都是采用这种方法来选择使能某个功能 ,编译对应的源码文件

    8、README

    README文件描述了uboot的详细信息,包括uboot该如何编译、uboot中各文件夹的含义、相应的命令等等。建议详细的阅读此文件,可以进一步增加对uboot的认识。

    关于 uboot根目录中的文件和文件夹的含义就讲解到这里,接下来就要开始分析uboot的顶层 Makefile了

    U-Boot顶层Makefile分析

    用VScode打开编译后的uboot,在阅读uboot 源码之前,肯定是要先看一下顶层Makefile,分析gcc 版本代码的时候一定是先从顶层Makefile 开始的,然后再是子Makefile,这样通过层层分析 Makefile 即可了解整个工程的组织结构。顶层Makefile也就是uboot 根目录下的Makefile 文件,由于顶层Makefile文件内容比较多,所以我们将其分开来看。

    版本号

    顶层Makefile一开始是版本号,内容如下:

    VERSION = 2020
    PATCHLEVEL = 01
    SUBLEVEL =
    EXTRAVERSION = -stm32mp-r1
    NAME =
    
    • 1
    • 2
    • 3
    • 4
    • 5

    VERSION是主版本号,PATCHLEVEL是补丁版本号,SUBLEVEL是次版本号,这三个一起构成了uboot的版本号,比如当前的uboot版本号就是“2020.01”。 EXTRAVERSION是附加版本信息, NAME是和名字有关的

    MAKEFLAGS变量

    make是支持递归调用的,也就是在Makefile中使用“make”命令来执行其他的Makefile文件,一般都是子目录中的Makefile文件。假如在当前目录下存在一个“subdir”子目录,这个子目录中又有其对应的Makefile文件,那么这个工程在编译的时候其主目录中的Makefile就可以调用子目录中的Makefile,以此来完成所有子目录的编译。主目录的Makefile可以使用如下代码来编译这个子目录

    $(MAKE) -C subdir

    $(MAKE)就是调用“make”命令 ,-C指定子目录。有时候我们需要向子make传递变量,使用“export”来导出要传递给子make的变量即可,如果不希望哪个变量传递给子make的话就使用“unexport”来声明不导出 :

    export VARIABLE //导出变量给子 make
    unexport VARIABLE //不导出变量给子 make

    有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使“nexport”声明,否则在整个make的执行过程中,它们的值始终自动的传递给子make。在uboot的主Makefile中有如下代码:

    MAKEFLAGS += -rR --include-dir=$(CURDIR)

    上述代码使用“+=”来给变量MAKEFLAGS追加了一些值,“-rR”表示禁止使用内置的隐含规则和变量定义,“–include-dir”指明搜索路径 ,“$(CURDIR)”表示当前目录。

    命令输出

    uboot默认编译是不会在终端中显示完整的命令,都是短命令,如下图所示:
    终端短命令输出
    在终端中输出短命令虽然看起来很清爽,但是不利于分析uboot的编译过程。可以通过设置变量“V=1”来实现完整的命令输出,这个在调试uboot的时候很有用,结果如下图所示:
    终端完整命令输出
    顶层Makefile中控制命令输出的代码如下:

    ifeq ("$(origin V)", "command line")
      KBUILD_VERBOSE = $(V)
    endif
    ifndef KBUILD_VERBOSE
      KBUILD_VERBOSE = 0
    endif
    
    ifeq ($(KBUILD_VERBOSE),1)
      quiet =
      Q =
    else
      quiet=quiet_
      Q = @
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Makefile部分代码截图

    上述代码中先使用ifeq来判断"$(origin V)"和"command line"是否相等。这里用到了Makefile中的函数origin,origin和其他的函数不一样,它不操作变量的值,origin用于告诉你变量是哪来的,语法为:

    $(origin )

    variable是变量名,origin函数的返回值就是变量来源,因此$(origin V)就是变量V的来源。如果变量V是在命令行定义的那么它的来源就是"command line",这样 "$(origin V)"和 "command line"就相等了。当这两个相等的时候变量KBUILD_VERBOSE就等于V的值,比如在命令行中输入“V=1“的话那么KBUILD_VERBOSE=1。如果没有在命令行输入V的话KBUILD_VERBOSE=0。

    第97行判断KBUILD_VERBOSE是否为 1,如果KBUILD_VERBOSE为1的话变量quiet和Q都为空,如果KBUILD_VERBOSE=0的话变量quiet为“quiet_”,变量Q为“@””,综上所述:

    V=1的话:

    KBUILD_VERBOSE=1
    quiet= 空
    Q= 空

    V=0或命令行不定义V:

    KBUILD_VERBOSE=0
    quiet= quiet_
    Q= @

    Makefile中会用变量quiet和Q来控制编译的时候是否在终端输出完整的命令,在顶层Makefile中有很多如下所示的命令:

    $(Q)$(MAKE) $(build)=tools

    如果V=0的话上述命令展开就是“@ make $(build)=tools”,make在执行的时候默认会在终端输出命令,但是在命令前面加上“@”就不会在终端输出命令了。当V=1的时候Q就为空,上述命令就是“make $(build)=tools”,因此在make执行的过程,命令会被完整的输出在终端上。

    有些命令会有两个版本,比如:

    quiet_cmd_sym ?= SYM $@
    cmd_sym ?= $(OBJDUMP) -t $< > $@

    sym命令分为“quiet_cmd_sym”和“cmd_sym”两个版本,这两个命令的功能都是一样的区别在于 make执行的时候输出的命令不同。quiet_cmd_xxx命令输出信息少,也就是短命令,而cmd_xxx命令输出信息多,也就是完整的命令

    如果变量quiet为空的话,整个命令都会输出。
    如果变量quiet为“quiet_”的话,仅输出短版本。
    如果变量quiet为“silent_”的话,整个命令都不会输出。

    静默输出

    上一小节讲了,设置V=0或者在命令行中不定 V的话,编译uboot的时候终端中显示的短命令,但是还是会有命令输出,有时候我们在编译 uboot的时候不需要输出命令,这个时候就可以使用uboot的静默输出功能。编译的时候使用“make -s”即可实现静默输出,顶层 Makefile中相应的代码如下:

    # If the user is running make -s (silent mode), suppress echoing of
    # commands
    
    ifneq ($(filter 4.%,$(MAKE_VERSION)),)	# make-4
    ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
      quiet=silent_
    endif
    else					# make-3.8x
    ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
      quiet=silent_
    endif
    endif
    
    export quiet Q KBUILD_VERBOSE
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Makefile部分代码截图

    第 108行判断当前正在使用的编译器版本号是否为 4.x,判断 $(filter4.%,$(MAKE_VERSION))和“ ”(空)是否相等,如果不相等的话就成立,执行里面的语句。也就是说$(filter 4.%,$(MAKE_VERSION))不为空的话条件就成立,这里用到了Makefile中 filter函数,这是个过滤函数,函数格式如下:

    $(filter ,)

    filter函数表示以pattern模式过滤text字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此 $(filter 4.%,$(MAKE_VERSION))的含义就是在字符串“MAKE_VERSION”中找出符合“4.%”的字符 (%为通配符),MAKE_VERSION是make工具的版本号,ubuntu18.04里面默认自带的make工具版本号为4.1,可以输入“ make -v”查看。因此 $(filter 4.%,$(MAKE_VERSION))不为空,条件成立,执行109-111行的语句。

    第109行也是一个判断语句,如果$(filter %s ,$(firstword x$(MAKEFLAGS)))不为空的话条件成立,变量quiet等于“silent_”。这里也用到了函数filter,在$(firstword x$(MAKEFLAGS)))中过滤出符合“%s”的单词。到了函数firstword,函数firstword是获取首单词,函数格式如下:

    $(firstword )

    firstword函数用于取出text字符串中的第一个单词,函数的返回值就是获取到的单词。当使用“make -s”编译的时候,“-s”会作为MAKEFLAGS变量的一部分传递给Makefile。在顶层Makfile中添加如下图的代码:
    顶层Makefile添加代码
    上图中的两行代码用于输出$(firstword x$(MAKEFLAGS))的结果,用以下命令
    去测试静脉输出:

    make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- -s

    结果如下图所示:
    修改顶层Makefile后的执行结果
    从上图可以看出第一个单词是“xrRs”,将$(filter %s ,$(firstword x$(MAKEFLAGS)))展开就是$(filter %s, xrRs),而$(filter %s, xrRs)的返回值肯定不为空,条件成立,quiet=silent_。第118行使用export导出变量quiet、Q和KBUILD_VERBOSE。

    设置编译结果输出目录

    uboot可以将编译出来的目标文件输出到单独的目录中,在make的时候使用“O”来指定
    输出目录
    ,比如“make O=out”就是设置目标文件输出到out目录中。这么做是为了将源文件和编译产生的文件分开,当然也可以不指定O参数,不指定的话源文件和编译产生的文件都在同一个目录内一般我们不指定O参数。顶层Makefile中相关的代码如下:

    # kbuild supports saving output files in a separate directory.
    # To locate output files in a separate directory two syntaxes are supported.
    # In both cases the working directory must be the root of the kernel src.
    # 1) O=
    # Use "make O=dir/to/store/output/files/"
    #
    # 2) Set KBUILD_OUTPUT
    # Set the environment variable KBUILD_OUTPUT to point to the directory
    # where the output files shall be placed.
    # export KBUILD_OUTPUT=dir/to/store/output/files/
    # make
    #
    # The O= assignment takes precedence over the KBUILD_OUTPUT environment
    # variable.
    
    # KBUILD_SRC is set on invocation of make in OBJ directory
    # KBUILD_SRC is not intended to be used by the regular user (for now)
    ifeq ($(KBUILD_SRC),)
    
    # OK, Make called in directory where kernel src resides
    # Do we want to locate output files in a separate directory?
    ifeq ("$(origin O)", "command line")
      KBUILD_OUTPUT := $(O)
    endif
    
    # That's our default target when none is given on the command line
    PHONY := _all
    _all:
    
    # Cancel implicit rules on top Makefile
    $(CURDIR)/Makefile Makefile: ;
    
    ifneq ($(KBUILD_OUTPUT),)
    # Invoke a second make in the output directory, passing relevant variables
    # check that the output directory actually exists
    saved-output := $(KBUILD_OUTPUT)
    KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
    								&& /bin/pwd)
    ……
    endif # ifneq ($(KBUILD_OUTPUT),)
    endif # ifeq ($(KBUILD_SRC),)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    顶层Makefile部分代码截图

    第142行判断“O”是否来自于命令行,如果来自命令行的话条件成立KBUILD_OUTPUT就为$(O),因此变量KBUILD_OUTPUT就是输出目录。

    第152行判断KBUILD_OUTPUT是否为空。

    第156行调用mkdir命令,创建KBUILD_OUTPUT目录,并且将创建成功以后的绝对路径赋值给KBUILD_OUTPUT。至此,通过O指定的输出目录就存在了。

    代码检查

    uboot支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层Makefile中的代码如下:

    # Call a source code checker (by default, "sparse") as part of the
    # C compilation.
    #
    # Use 'make C=1' to enable checking of only re-compiled files.
    # Use 'make C=2' to enable checking of *all* source files, regardless
    # of whether they are re-compiled or not.
    #
    # See the file "doc/sparse.txt" for more details, including
    # where to get the "sparse" utility.
    
    ifeq ("$(origin C)", "command line")
      KBUILD_CHECKSRC = $(C)
    endif
    ifndef KBUILD_CHECKSRC
      KBUILD_CHECKSRC = 0
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    顶层Makefile部分代码截图

    第193行判断C是否来源于命令行,如果C来源于命令行,那就将C赋值给变量KBUILD_CHECKSRC,如果命令行没有C的话KBUILD_CHECKSRC就为 0。

    模块编译

    在uboot中允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层Makefile中的代码如下:

    # Use make M=dir to specify directory of external module to build
    # Old syntax make ... SUBDIRS=$PWD is still supported
    # Setting the environment variable KBUILD_EXTMOD take precedence
    ifdef SUBDIRS
      KBUILD_EXTMOD ?= $(SUBDIRS)
    endif
    
    ifeq ("$(origin M)", "command line")
      KBUILD_EXTMOD := $(M)
    endif
    
    # If building an external module we do not care about the all: rule
    # but instead _all depend on modules
    PHONY += all
    ifeq ($(KBUILD_EXTMOD),)
    _all: all
    else
    _all: modules
    endif
    
    ifeq ($(KBUILD_SRC),)
            # building in the source tree
            srctree := .
    else
            ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
                    # building in a subdirectory of the source tree
                    srctree := ..
            else
                    srctree := $(KBUILD_SRC)
            endif
    endif
    objtree		:= .
    src		:= $(srctree)
    obj		:= $(objtree)
    
    VPATH		:= $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
    
    export srctree objtree VPATH
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    顶层Makefile部分代码截图

    第203行判断是否定义了SUBDIRS,如果定义了SUBDIRS,变量KBUILD_EXTMOD=SUBDIRS,这里是为了支持老语法make SUBIDRS=dir。

    第207行判断是否在命令行定义了M,如果定义了的 KBUILD_EXTMOD=$(M)。

    第214行判断KBUILD_EXTMOD是否为空,如果为空的话目标 _all依赖all,因此要先编译出all;否则的话默认目标 _all依赖modules,要先编译出modules,也就是编译模块;一般情况下我们不会在uboot中编译模块,所以此处会编译all这个目标。

    第220行判断KBUILD_SRC是否为空,如果为空的话就设置变量srctree为当前目录,即srctree为“.”,一般不设置KBUILD_SRC。

    第231行设置变量objtree为当前目录。

    第232和233行分别设置变量src和obj,都为当前目录。

    第235行设置VPATH。

    第237行导出变量scrtree、objtree和VPATH。

    获取主机架构和系统

    接下来顶层Makefile会获取主机架构和系统,也就是我们电脑的架构和系统,代码如下:

    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/)
    
    HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
    	    sed -e 's/\(cygwin\).*/cygwin/')
    
    export	HOSTARCH HOSTOS
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    顶层Makefile部分代码截图
    第244行定义了一个变量HOSTARCH,用于保存主机架构,这里调用shell命令“uname -m”获取架构名称,结果如下图所示:
    uname -m命令
    从上图可以看出当前电脑主机架构为“x86_64”,shell中的“|”表示管道,意思是将左边的输出作为右边的输入,sed -e是替换命令,“sed -e s/i.86/x86/”表示将管道输入的字符串中的“i.86”替换为“x86”,其他的“sed -e s”命令同理。对于教程中电脑而言 HOSTARCH=x86_64。

    第254行定义了变量HOSTOS,此变量用于保存主机OS的值,先使用shell命令“uname -s”来获取主机OS,结果如下图所示:
    uname -s命令
    从上图可以看出此时的主机OS为“Linux”,使用管道将“Linux”作为后面“tr ‘[:upper:]’ ‘[:lower:]’”的输入,“tr ‘[:upper:]’ ‘[:lower:]’”表示将所有的大写字母替换为小写字母,因此得到“linux”。最后同样使用管道,将“linux”作为“sed -e 's/(cygwin).*/cygwin/”的输入,用于将cygwin.*替换为cygwin。因此, HOSTOS=linux。

    第257行导出HOSTARCH=x86_64,HOSTOS=linux。

    设置目标架构、交叉编译器和配置文件

    编译uboot的时候需要设置目标板架构和交叉编译器,“make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf-”就是用于设置ARCH和 CROSS_COMPILE,在
    顶层Makefile中代码如下:

    # set default to nothing for native builds
    ifeq ($(HOSTARCH),$(ARCH))
    CROSS_COMPILE ?=
    endif
    
    KCONFIG_CONFIG	?= .config
    export KCONFIG_CONFIG
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    顶层Makefile部分代码截图

    第262行判断HOSTARCH和ARCH这两个变量是否相等,主机架构(变量 HOSTARCH)是x86_64,而我们编译的是ARM版本uboot,肯定不相等。所以CROSS_COMPILE= arm-none-linux-gnueabihf-。从上述代码可以看出,每次编译uboot的时候都要在make命令后面设置ARCH和CROSS_COMPILE,使用起来很麻烦,可以直接修改顶层Makefile,在里面加入ARCH和 CROSS_COMPILE的定义,如下图所示:
    定义ARCH和CROSS_COMPILE

    按上图所示,直接在顶层Makefile里面定义ARCH和CROSS_COMPILE,就不用每次编译的时候都要在make命令后面定义ARCH和CROSS_COMPILE。

    继续回到示例代码中,第266行定义变量KCONFIG_CONFIG,uboot是可以配置的,这里设置配置文件为.config,.config默认是没有的,需要使用命令“make xxx_defconfig”对uboot进行配置,配置完成以后就会在uboot根目录下生成.config。默认情况下.config和xxx_defconfig内容是一样的,因为.config就是从xxx_defconfig复制过来的。如果后续自行调整uboot的一些配置参数,那么这些新的配置参数就添加到了.config中,而不是xxx_defconfig。相当于xxx_defconfig只是一些初始配置,而.config里面的才是实时有效的配置

    调用scripts/Kbuild.include

    主Makefile会调用文件scripts/Kbuild.include这个文件,顶层Makefile中代码如下:

    # We need some generic definitions (do not try to remake the file).
    scripts/Kbuild.include: ;
    include scripts/Kbuild.include
    
    • 1
    • 2
    • 3

    上述示例代码中使用“include”包含了文件scripts/Kbuild.include,此文件里面定义了很多变量,如下所示:

    ####
    # kbuild: Generic definitions
    
    # Convenient variables
    comma   := ,
    quote   := "
    squote  := '
    empty   :=
    space   := $(empty) $(empty)
    pound := \#
    
    ###
    # Name of target with a '.' as filename prefix. foo/bar.o => foo/.bar.o
    dot-target = $(dir $@).$(notdir $@)
    
    ###
    # The temporary file to save gcc -MD generated dependencies must not
    # contain a comma
    depfile = $(subst $(comma),_,$(dot-target).d)
    
    ###
    # filename of target with directory and extension stripped
    basetarget = $(basename $(notdir $@))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在uboot的编译过程中会用到scripts/Kbuild.include中的这些变量,后面用到的时候再分析。

    交叉编译工具变量设置

    上面我们只是设置了CROSS_COMPILE的名字,但是交叉编译器其他的工具还没有设置,顶层Makefile中相关代码如下:

    AS		= $(CROSS_COMPILE)as
    # Always use GNU ld
    ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
    LD		= $(CROSS_COMPILE)ld.bfd
    else
    LD		= $(CROSS_COMPILE)ld
    endif
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    导出其他变量

    接下来在顶层Makefile会导出很多变量,代码如下:

    export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION
    export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
    export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CC
    export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
    export MAKE LEX YACC AWK PERL PYTHON PYTHON2 PYTHON3
    export HOSTCXX HOSTCXXFLAGS CHECK CHECKFLAGS DTC DTC_FLAGS
    
    export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS
    export KBUILD_CFLAGS KBUILD_AFLAGS
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这些变量中大部分都已经在前面定义了,重点来看一下下面这几个变量:

    ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR

    这7个变量在顶层 Makefile是找不到的,说明这7个变量是在其他文件里面定义的,先来看一下这7个变量都是什么内容,在顶层Makefile中输入如下图所示的内容:
    输出变量值
    修改好顶层Makefile以后执行如下命令:

    make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- mytest

    结果如下图所示:
    变量结果
    从上图可以看到这7个变量的值,uboot根目录下有个文件叫做config.mk,这7个变量就是在config.mk里面定义的,打开config.mk内容如下:

    # SPDX-License-Identifier: GPL-2.0+
    #
    # (C) Copyright 2000-2013
    # Wolfgang Denk, DENX Software Engineering, wd@denx.de.
    #########################################################################
    
    # This file is included from ./Makefile and spl/Makefile.
    # Clean the state to avoid the same flags added twice.
    #
    # (Tegra needs different flags for SPL.
    #  That's the reason why this file must be included from spl/Makefile too.
    #  If we did not have Tegra SoCs, build system would be much simpler...)
    PLATFORM_RELFLAGS :=
    PLATFORM_CPPFLAGS :=
    PLATFORM_LDFLAGS :=
    LDFLAGS :=
    LDFLAGS_FINAL :=
    LDFLAGS_STANDALONE :=
    OBJCOPYFLAGS :=
    # clear VENDOR for tcsh
    VENDOR :=
    #########################################################################
    
    ARCH := $(CONFIG_SYS_ARCH:"%"=%)
    CPU := $(CONFIG_SYS_CPU:"%"=%)
    ifdef CONFIG_SPL_BUILD
    ifdef CONFIG_TEGRA
    CPU := arm720t
    endif
    endif
    BOARD := $(CONFIG_SYS_BOARD:"%"=%)
    ifneq ($(CONFIG_SYS_VENDOR),)
    VENDOR := $(CONFIG_SYS_VENDOR:"%"=%)
    endif
    ifneq ($(CONFIG_SYS_SOC),)
    SOC := $(CONFIG_SYS_SOC:"%"=%)
    endif
    
    # Some architecture config.mk files need to know what CPUDIR is set to,
    # so calculate CPUDIR before including ARCH/SOC/CPU config.mk files.
    # Check if arch/$ARCH/cpu/$CPU exists, otherwise assume arch/$ARCH/cpu contains
    # CPU-specific code.
    CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)
    
    sinclude $(srctree)/arch/$(ARCH)/config.mk	# include architecture dependend rules
    sinclude $(srctree)/$(CPUDIR)/config.mk		# include  CPU	specific rules
    
    ifdef	SOC
    sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk	# include  SoC	specific rules
    endif
    ifneq ($(BOARD),)
    ifdef	VENDOR
    BOARDDIR = $(VENDOR)/$(BOARD)
    else
    BOARDDIR = $(BOARD)
    endif
    endif
    ifdef	BOARD
    sinclude $(srctree)/board/$(BOARDDIR)/config.mk	# include board specific rules
    endif
    
    ifdef FTRACE
    PLATFORM_CPPFLAGS += -finstrument-functions -DFTRACE
    endif
    
    #########################################################################
    
    RELFLAGS := $(PLATFORM_RELFLAGS)
    
    PLATFORM_CPPFLAGS += $(RELFLAGS)
    PLATFORM_CPPFLAGS += -pipe
    
    LDFLAGS += $(PLATFORM_LDFLAGS)
    LDFLAGS_FINAL += -Bstatic
    
    export PLATFORM_CPPFLAGS
    export RELFLAGS
    export LDFLAGS_FINAL
    export LDFLAGS_STANDALONE
    export CONFIG_STANDALONE_LOAD_ADDR
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    这里代码截图太麻烦了,直接复制过来,下面的讲解行数可以数一下,或者就对着内容来找是哪一行,也不会很麻烦。

    第24行定义变量ARCH,值为$(CONFIG_SYS_ARCH:“%”=%),也就是提取CONFIG_SYS_ARCH里面双引号“”之间的内容。比如CONFIG_SYS_ arm”的话,ARCH=arm。

    第25行定义变量CPU,值为$(CONFIG_SYS_CPU:“%”=%)。

    第31行定义变量BOARD,值为$(CONFIG_SYS_BOARD:“%”=%)。

    第33行定义变量VENDOR,值为$(CONFIG_SYS_VENDOR:“%”=%)。

    第36行定义变量SOC,值为$(CONFIG_SYS_SOC:“%”=%)。

    第43行定义变量CPUDIR,值为arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)。

    第45行的sinclude和include的功能类似,在Makefile中都是读取指定文件内容,这里读取文件$(srctree)/arch/$(ARCH)/config.mk的内容,sinclude读取的文件如果不存在的话不会报错

    第46行读取文件$(srctree)/$(CPUDIR)/config.mk的内容。

    第49行读取文件$(srctree)/$(CPUDIR)/$(SOC)/config.mk的内容。

    第53行定义变量BOARDDIR,如果定义了VENDOR那么BOARDDIR=$(VENDOR)/$(BOARD),否则的 BOARDDIR=$(BOARD)。

    第59行读取文件$(srctree)/board/$(BOARDDIR)/config.mk。

    接下来需要找到CONFIG_SYS_ARCH、CONFIG_SYS_CPU、CONFIG_SYS_BOARD、CONFIG_SYS_VENDOR和CONFIG_SYS_SOC这5个变量的值 。这5个变量在uboot根目录
    下的.config文件中有定义
    ,定义如下:

    CONFIG_SYS_ARCH="arm"
    CONFIG_SYS_CPU="armv7"
    CONFIG_SYS_SOC="stm32mp"
    CONFIG_SYS_VENDOR="st"
    CONFIG_SYS_BOARD="stm32mp1"
    CONFIG_SYS_CONFIG_NAME="stm32mp1"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    那么就可以根据以上示例代码看出各个变量的值,因此可以推导出config.mk读取的文件有:

    arch/arm/config.mk
    arch/arm/cpu/armv7/config.mk
    arch/arm/cpu/armv7/stm32mp/config.mk (此文件不存在)
    board/st/stm32mp1/config.mk (此文件不存在)

    make xxx_deconfig过程

    在编译uboot之前要使用“make xxx_defconfig”命令来配置uboot,那么这个配置过程是如何运行的呢?在顶层Makefile中有如下代码:

    # To make sure we do not include .config for any of the *config targets
    # catch them early, and hand them over to scripts/kconfig/Makefile
    # It is allowed to specify more targets when calling make, including
    # mixing *config targets and build targets.
    # For example 'make oldconfig all'.
    # Detect when mixed targets is specified, and make a second invocation
    # of make so .config is not included in this case either (for *config).
    
    version_h := include/generated/version_autogenerated.h
    timestamp_h := include/generated/timestamp_autogenerated.h
    defaultenv_h := include/generated/defaultenv_autogenerated.h
    
    no-dot-config-targets := clean clobber mrproper distclean \
    			 help %docs check% coccicheck \
    			 ubootversion backup tests check qcheck
    
    config-targets := 0
    mixed-targets  := 0
    dot-config     := 1
    
    ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
    	ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
    		dot-config := 0
    	endif
    endif
    
    ifeq ($(KBUILD_EXTMOD),)
            ifneq ($(filter config %config,$(MAKECMDGOALS)),)
                    config-targets := 1
                    ifneq ($(words $(MAKECMDGOALS)),1)
                            mixed-targets := 1
                    endif
            endif
    endif
    
    ifeq ($(mixed-targets),1)
    # ===========================================================================
    # We're called with mixed targets (*config and build targets).
    # Handle them one by one.
    
    PHONY += $(MAKECMDGOALS) __build_one_by_one
    
    $(filter-out __build_one_by_one, $(MAKECMDGOALS)): __build_one_by_one
    	@:
    
    __build_one_by_one:
    	$(Q)set -e; \
    	for i in $(MAKECMDGOALS); do \
    		$(MAKE) -f $(srctree)/Makefile $$i; \
    	done
    
    else
    ifeq ($(config-targets),1)
    # ===========================================================================
    # *config targets only - make sure prerequisites are updated, and descend
    # in scripts/kconfig to make the *config target
    
    KBUILD_DEFCONFIG := sandbox_defconfig
    export KBUILD_DEFCONFIG KBUILD_KCONFIG
    
    config: scripts_basic outputmakefile FORCE
    	$(Q)$(MAKE) $(build)=scripts/kconfig $@
    
    %config: scripts_basic outputmakefile FORCE
    	$(Q)$(MAKE) $(build)=scripts/kconfig $@
    
    else
    # ===========================================================================
    # Build targets only - this includes vmlinux, arch specific targets, clean
    # targets and others. In general all targets except *config targets.
    
    # Additional helpers built in scripts/
    # Carefully list dependencies so we do not try to build scripts twice
    # in parallel
    PHONY += scripts
    scripts: scripts_basic include/config/auto.conf
    	$(Q)$(MAKE) $(build)=$(@)
    
    ifeq ($(dot-config),1)
    # Read in config
    -include include/config/auto.conf
    ……
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    同理这一段代码截图太长了,就对着找一下以下讲解内容的行就可以了。

    第483行定义了变量version_h,这变量保存版本号文件,此文件是自动生成的。文件include/generated/version_autogenerated.h内容如下图所示:
    版本号文件
    第484行定义了变量timestamp_h,此变量保存时间戳文件,此文件也是自动生成的。文件include/generated/timestamp_autogenerated.h内容如下图所示:
    时间戳文件
    第487行定义了变量no-dot-config-targets。

    第491行定义了变量config-targets,初始值为0。

    第492行定义了变量mixed-targets,初始值为0。

    第493行定义了变量dot-config,初始值为1。

    第495行将MAKECMDGOALS中不符合no-dot-config-targets的部分过滤掉,剩下的如果不为空的话条件就成立。MAKECMDGOALS是make的一个环境变量,这个变量会保存你所指定的终极目标列表,比如执行“make mx6ull_alientek_emmc_defconfig”,那么MAKECMDGOALS就为mx6ull_alientek_emmc_defconfig。很明显过滤后为空,所以条件不成立,变量dot-config依旧为1

    第501行判断KBUILD_EXTMOD是否为空,如果KBUILD_EXTMOD为空的话条件成立,经过前面的分析可知KBUILD_EXTMOD为空,所以条件成立

    第502行将MAKECMDGOALS中不符合“config”和 “%config”的部分过滤掉,如果剩下的部分不为空条件就成立,很明显此处条件成立,变量config-targets=1

    第504行统计MAKECMDGOALS中的单词个数,如果不为1的话条件成立。此处调用Makefile中的words函数来统计单词个数, words函数格式如下:

    $(words )

    很明显,MAKECMDGOALS的单词个数是1个,所以条件不成立,mixed-targets继续为
    0
    。综上所述,这些变量值如下:

    config-targets = 1
    mixed-targets = 0
    dot-config = 1

    第510行如果变量mixed-targets为1的话条件成立,很明显,条件不成立。

    第527行如果变量config-targets为1的话条件成立,很明显,条件成立,执行这个分支。

    第535行,没有目标与之匹配,所以不执行。

    第538行,有目标与之匹配,当输入“make xxx_defconfig”的时候就会匹配到%config目标,目标“%config”依赖于scripts_basic、outputmakefile和FORCE。FORCE在顶层Makefile后面有如下定义:

    PHONY += FORCE
    FORCE:

    可以看出FORCE是没有规则和依赖的,所以每次都会重新生成FORCE。当FORCE作为其他目标的依赖时,由于FORCE总是被更新过的,因此依赖所在的规则总是会执行的。依赖scripts_basic和outputmakefile在顶层Makefile中的内容如下:

    # Basic helpers built in scripts/
    PHONY += scripts_basic
    scripts_basic:
    	$(Q)$(MAKE) $(build)=scripts/basic
    	$(Q)rm -f .tmp_quiet_recordmcount
    
    # To avoid any implicit rule to kick in, define an empty command.
    scripts/basic/%: scripts_basic ;
    
    PHONY += outputmakefile
    # outputmakefile generates a Makefile in the output directory, if using a
    # separate output directory. This allows convenient use of make in the
    # output directory.
    outputmakefile:
    ifneq ($(KBUILD_SRC),)
    	$(Q)ln -fsn $(srctree) source
    	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
    	    $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    第469行,判断KBUILD_SRC是否为空,只有变量KBUILD_SRC不为空的时候outputmakefile才有意义,经过我们前面的分析KBUILD_SRC为空,所以outputmakefile无效;只有scripts_basic是有效的。

    第457-459行是scripts_basic的规则,其对应的命令用到了变量Q、MAKE和build,其中:

    Q=@或为空
    MAKE=make

    变量build是在scripts/Kbuild.include文件中有定义,定义如下:

    ###
    # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
    # Usage:
    # $(Q)$(MAKE) $(build)=dir
    build := -f $(srctree)/scripts/Makefile.build obj
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从以上示例代码可以看出build=-f $(srctree)/scripts/Makefile.build obj,经过前面的分析可知,变量srctree为 “.”,因此:

    build=-f ./scripts/Makefile.build obj

    scripts_basic展开以后如下:

    scripts_basic:
    @make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
    @rm -f . tmp_quiet_recordmcount //也可以没有@

    scripts_basic会调用文件./scripts/Makefile.build,这个我们后面再分析。

    接着回到原先最长的顶层Makefile示例代码中的%config处,内容如下:

    %config: scripts_basic outputmakefile FORCE
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

    将命令展开就是:

    @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

    同样也跟文件./scripts/Makefile.build有关,我们后面再分析此文件。使用如下命令配置uboot并观察其配置过程:

    make stm32mp157d_atk_defconfig V=1

    配置过程如下图所示:
    uboot配置过程
    从上图可以看出,上文的分析是正确的,接下来就要结合下面两行命令重点分析一下文件scripts/Makefile.build。

    1.scripts_basic目标对应的命令

    @make -f ./scripts/Makefile.build obj=scripts/basic
    2.%config目标对应的命令
    @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

    Makefile.build脚本分析

    从上一小节可知,“make xxx_defconfig”配置uboot的时候如下两行命令会执行脚本scripts/Makefile.build:

    @make -f ./scripts/Makefile.build obj=scripts/basic
    @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

    依次来分析一下:

    1、scripts_basic目标对应的命令

    scripts_basic目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/basic。打开文
    件scripts/Makefile.build,有如下代码:

    # Modified for U-Boot
    prefix := tpl
    src := $(patsubst $(prefix)/%,%,$(obj))
    ifeq ($(obj),$(src))
    prefix := spl
    src := $(patsubst $(prefix)/%,%,$(obj))
    ifeq ($(obj),$(src))
    prefix := .
    endif
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Makefile.build代码段截图
    第7行定义了变量prefix值为tpl。

    第8行定义了变量src,这里用到了函数patsubst,此行代码展开后为:

    $(patsubst tpl/%,%, scripts/basic)

    patsubst是替换函数,格式如下:

    $(patsubst ,,)

    此函数用于在text中查找符合pattern的部分,如果匹配的话就用replacement替换掉。pattern是可以包含通配符“%”,如果replacement中也包含通配符“%”,那么replacement中的这个“%”将是pattern中的那个“%”所代表的字符串。函数的返回值为替换后的字符串。因此,第8行就是在“scripts/basic”中查找符合“tpl/%”的部分,然后将“tpl/”取消掉,但是“scripts/basic”没有“tpl/”,所以 src=scripts/basic

    第9行判断变量obj和src是否相等,相等的话条件成立,很明显,此处条件成立。

    第10行和第7行一样,只是这里处理的是“spl”,“scripts/basic”里面也没有“spl/”,所以src继续为scripts/basic

    第12行因为变量obj和src相等,所以prefix=.

    继续分析scripts/Makefile.build,有如下代码:

    # The filename Kbuild has precedence over Makefile
    kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
    kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
    include $(kbuild-file)
    
    • 1
    • 2
    • 3
    • 4

    Makefile.build代码段截图
    将kbuild-dir展开后为:

    $(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic),

    因为没有以“/”为开头的单词,所以$(filter /%, scripts/basic)的结果为空,kbuild-dir=./scripts/basic

    将kbuild-file展开后为:

    $(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild, ./scripts/basic/Makefile)

    因为scrpts/basic目录中没有Kbuild这个文件,所以 kbuild-file= ./scripts/basic/Makefile。最后将57行展开,即:

    include ./scripts/basic/Makefile

    也就是读取scripts/basic下面的Makefile文件。

    继续分析scripts/Makefile.build,如下代码:

    __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
    	 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
    	 $(subdir-ym) $(always)
    	@:
    
    • 1
    • 2
    • 3
    • 4

    Makefile.build代码段截图
    __build是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标:__build。在顶层Makefile中,KBUILD_BUILTIN为1,KBUILD_MODULES为0,因此展开后目标 __build为:

    __build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
    @:

    可以看出目标 __build有5个依赖:builtin-target、lib-target、extra-y、subdir-ym和always。这5个依赖的具体内容我们就不通过源码来分析了,直接在scripts/Makefile.build中输入下图所示内容,将这5个变量的值打印出来:
    输出变量
    执行如下命令:

    make stm32mp157d_atk_defconfig V=1

    结果如下图所示:
    输出结果
    从上图可以看出,只有always有效,因此__build最终为:

    __build: scripts/basic/fixdep
    @:

    __build依赖于scripts/basic/fixdep,所以要先scripts/basic/fixdep.c编译,生成fixdep,前面已经读取了scripts/basic/Makefile文件。

    综上所述,scripts_basic目标的作用就是编译出scripts/basic/fixdep这个软件

    2、%config目标对应的命令

    %config目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig,各个变量值如下:

    src= scripts/kconfig
    kbuild-dir = ./scripts/kconfig
    kbuild-file = ./scripts/kconfig/Makefile
    include ./scripts/kconfig/Makefile

    可以看出,Makefilke.build会读取scripts/kconfig/Makefile中的内容,此文件有如下所示内容:

    %_defconfig: $(obj)/conf
    	$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
    
    # Added for U-Boot (backward compatibility)
    %_config: %_defconfig
    	@:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    目标%_defconfig刚好和输入的xxx_defconfig匹配,所以会执行这条规则。依赖为$(obj)/conf,展开后就是scripts/kconfig/conf。接下来就是检查并生成依赖scripts/kconfig/conf。
    conf是主机软件。不用过多纠结conf是如何编译产生的,如果一定要看,可以输入如下命令重新配置uboot,在重新配置uboot的过程中就会输出conf编译信息。

    make distclean
    make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157d_atk_defcon fig V=1

    结果如下图所示:
    编译过程
    得到scripts/kconfig/conf以后就要执行目标%_defconfig 的命令:

    $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

    相关变量值如下:

    silent=-s或为空
    SRCARCH=..
    Kconfig=Kconfig

    将其展开就是:

    @ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig

    上述命令用到了xxx_defconfig文件,比如mx6ull_alientek_emmc_defconfig。这里会将mx6ull_alientek_emmc_defconfig中的配置输出到.config 文件中,最终生成uboot根目录下的.config文件

    这个就是命令make xxx_defconfig执行流程,总结一下如下图所示:
    make xxx_defconfig执行流程图

    make过程

    配置好uboot以后就可以直接make编译了,因为没有指明目标,所以会使用默认目标,主Makefile中的默认目标如下:

    # That's our default target when none is given on the command line
    PHONY := _all
    _all:
    
    • 1
    • 2
    • 3

    目标 _all又依赖于all,如下所示:

    # If building an external module we do not care about the all: rule
    # but instead _all depend on modules
    PHONY += all
    ifeq ($(KBUILD_EXTMOD),)
    _all: all
    else
    _all: modules
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果KBUILD_EXTMOD为空的话_all依赖于all。这里不编译模块,所以KBUILD_EXTMOD肯定为空,_all的依赖就是all。在主Makefile中all目标规则如下:

    all:		$(ALL-y)
    ifeq ($(CONFIG_DEPRECATED),y)
    	$(warning "You have deprecated configuration options enabled in your .config! Please check your configuration.")
    ifeq ($(CONFIG_SPI),y)
    ifneq ($(CONFIG_DM_SPI)$(CONFIG_OF_CONTROL),yy)
    	$(warning "The relevant config item with associated code will remove in v2019.07 release.")
    endif
    endif
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    从上述代码的第一行可以看出,all目标依赖 $(ALL-y),而在顶层Makefile中,ALL-y如下:

    # Always append ALL so that arch config.mk's can add custom ones
    ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check
    
    ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
    ifeq ($(CONFIG_SPL_FSL_PBL),y)
    ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin
    else
    ifneq ($(CONFIG_SECURE_BOOT), y)
    # For Secure Boot The Image needs to be signed and Header must also
    # be included. So The image has to be built explicitly
    ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
    endif
    endif
    ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin
    ifeq ($(CONFIG_MX6)$(CONFIG_IMX_HAB), yy)
    ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot-ivt.img
    else
    ifeq ($(CONFIG_MX7)$(CONFIG_IMX_HAB), yy)
    ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot-ivt.img
    else
    ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img
    endif
    endif
    ALL-$(CONFIG_TPL) += tpl/u-boot-tpl.bin
    ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb
    ifeq ($(CONFIG_SPL_FRAMEWORK),y)
    ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img
    endif
    ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb
    ifneq ($(CONFIG_SPL_TARGET),)
    ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)
    endif
    ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
    ALL-$(CONFIG_EFI_APP) += u-boot-app.efi
    ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi
    
    ifneq ($(BUILD_ROM)$(CONFIG_BUILD_ROM),)
    ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.rom
    endif
    
    # Build a combined spl + u-boot image for sunxi
    ifeq ($(CONFIG_ARCH_SUNXI)$(CONFIG_SPL),yy)
    ALL-y += u-boot-sunxi-with-spl.bin
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    从以上示例代码可以看出,ALL-y包含u-boot.srec、u-boot.bin、u-boot.sym、System.map、u-boot.cfg和binary_size_check这几个文件。根据uboot的配置情况也可能包含其他的文件,比如:

    ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin

    CONFIG_ONENAND_U_BOOT就是uboot中跟ONENAND配置有关的,如果我们使能了ONENAND,那么在.config配 置文件中就会有“CONFIG_ONENAND_U_BOOT=y”这一句。相
    当于CONFIG_ONENAND_U_BOOT是个变量,这个变量的值为 y”,所以展开以后就是:

    ALL-y += u-boot-onenand.bin

    这个就是.config里面的配置参数的含义,这些参数其实都是变量,后面跟着变量值,会在顶层Makefile或者其他Makefile中调用这些变量。

    ALL-y里面有个u-boot.bin,这个就是我们最终需要的uboot二进制可执行文件,所作的所有工作就是为了它。在顶层Makefile中找到u-boot.bin目标对应的规则,如下所示:

    ifeq ($(CONFIG_MULTI_DTB_FIT),y)
    ……
    else ifeq ($(CONFIG_OF_SEPARATE),y)
    u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
    	$(call if_changed,cat)
    
    u-boot.bin: u-boot-dtb.bin FORCE
    	$(call if_changed,copy)
    else
    u-boot.bin: u-boot-nodtb.bin FORCE
    	$(call if_changed,copy)
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    顶层Makefile代码段截图
    第1133行判断CONFIG_OF_SEPARATE是否等于y,如果相等,那条件就成立。在include/config/auto.conf文件中CONFIG_OF_SEPARAT=y,所以条件成立。

    第1134行就是目标u-boot-dtb.bin的规则,目标u-boot-dtb.bin依赖于u-boot-nodtb.bin,命令为$(call if_changed,copy),第1137行就是目标u-boot.bin的规则,目标u-boot.bin依赖于u-boot-dtb.bin,他们都是调用了if_changed,if_changed是一个函数,这个函数在scripts/Kbuild.include中有定义,而顶层Makefile中会包含scripts/Kbuild.include文件,这个前面已经说过了。

    if_changed在 Kbuild.include中的定义如下:

    ###
    # if_changed      - execute command if any prerequisite is newer than
    #                   target, or command line has changed
    # if_changed_dep  - as if_changed, but uses fixdep to reveal dependencies
    #                   including used config symbols
    # if_changed_rule - as if_changed but execute rule instead
    # See Documentation/kbuild/makefiles.txt for more info
    ……
    # Execute command if command has changed or prerequisite(s) are updated.
    #
    if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
    	@set -e;                                                             \
    	$(echo-cmd) $(cmd_$(1));                                             \
    	printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Kbuild.include代码段截图
    第227行为if_changed的描述,根据描述,在一些先决条件比目标新的时候,或者命令行有改变的时候,if_changed就会执行一些命令。

    第257行就是函数if_changed,if_changed函数引用的变量比较多,也比较绕,我们只需要知道它可以从u-boot-nodtb.bin生成u-boot.bin就行了

    既然u-boot.bin依赖于u-boot-dtb.bin,u-boot-dtb.bin又依赖于u-boot-nodtb.bin,那么肯定要先生成u-boot-nodtb.bin文件,顶层Makefile中相关代码如下:

    u-boot-nodtb.bin: u-boot FORCE
    	$(call if_changed,objcopy)
    	$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
    	$(BOARD_SIZE_CHECK)
    
    • 1
    • 2
    • 3
    • 4

    目标u-boot-nodtb.bin又依赖于u-boot,顶层Makefile中u-boot相关规则如下:

    u-boot:	$(u-boot-init) $(u-boot-main) u-boot.lds FORCE
    	+$(call if_changed,u-boot__)
    ifeq ($(CONFIG_KALLSYMS),y)
    	$(call cmd,smap)
    	$(call cmd,u-boot__) common/system_map.o
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    目标u-boot依赖于u-boot_init、u-boot-main和u-boot.lds,u-boot_init和 u-boot-main是两个变量,在顶层Makefile中有定义,值如下:

    u-boot-init := $(head-y)
    u-boot-main := $(libs-y)
    
    • 1
    • 2

    $(head-y)跟CPU架构有关,我们使用的是ARM芯片,所以head-y在arch/arm/Makefile中
    被指定为:

    head-y := arch/arm/cpu/$(CPU)/start.o

    根据前文的分析,我们知道CPU=armv7,因此head-y展开以后就是:

    head-y := arch/arm/cpu/armv7/start.o

    因此:

    u-boot-init= arch/arm/cpu/armv7/start.o

    $(libs-y)在顶层Makefile中被定义为uboot所有子目录下build-in.o的集合,代码如下:

    libs-y += lib/
    libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
    libs-$(CONFIG_OF_EMBED) += dts/
    libs-y += fs/
    libs-y += net/
    libs-y += disk/
    libs-y += drivers/
    libs-y += drivers/dma/
    libs-y += drivers/gpio/
    libs-y += drivers/i2c/
    libs-y += drivers/net/
    libs-y += drivers/net/phy/
    libs-y += drivers/power/ \
    ……
    libs-y += cmd/
    libs-y += common/
    libs-y += env/
    libs-$(CONFIG_API) += api/
    libs-$(CONFIG_HAS_POST) += post/
    libs-$(CONFIG_UNIT_TEST) += test/ test/dm/
    libs-$(CONFIG_UT_ENV) += test/env/
    libs-$(CONFIG_UT_OPTEE) += test/optee/
    libs-$(CONFIG_UT_OVERLAY) += test/overlay/
    
    libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)
    
    libs-y := $(sort $(libs-y))
    
    u-boot-dirs	:= $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples
    
    u-boot-alldirs	:= $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-))))
    
    libs-y		:= $(patsubst %/, %/built-in.o, $(libs-y))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    从上面的代码可以看出,libs-y都是uboot各子目录的集合,最后:

    libs-y := $(patsubst %/, %/built-in.o, $(libs-y))

    这里调用了函数patsubst,将 libs-y中的“/”替换为“/built-in.o”,比如“drivers/dma/”就变为了“drivers/dma/built-in.o”,相当于将libs-y改为所有子目录中built-in.o文件的集合。那么u-boot-main就等于所有子目录中built-in.o的集合。

    这个规则就相当于将以u-boot.lds为链接脚本,将arch/arm/cpu/armv7/start.o和各个子目录
    下的built-in.o链接在一起生成u-boot

    u-boot.lds的规则如下:

    u-boot.lds: $(LDSCRIPT) prepare FORCE
    	$(call if_changed_dep,cpp_lds)
    
    • 1
    • 2

    接下来的重点就是各子目录下的built-in.o是怎么生成的,以drivers/gpio/built-in.o为例,在drivers/gpio/目录下会有个名为.built-in.o.cmd的文件,此文件内容如下:

    cmd_drivers/gpio/built-in.o := arm-none-linux-gnueabihf-ld.bfd -r -o drivers/gpio/built-in.o drivers/gpio/gpio-uclass.o drivers/gpio/stm32_gpio.o

    从命令“cmd_drivers/gpio/built-in.o”可以看出drivers/gpio/built-in.o这个文件是使用ld命令由文件drivers/gpio/gpio-uclass.o和drivers/gpio/stm32_gpio.o生成而来的,stm32_gpio.o是stm32_gpio.c编译生成的.o文件,这个是ST的STM32系列的GPIO驱动文件。这里用到了ld的“-r”参数,参数含义如下:

    -r –relocateable: 产生可重定向的输出,比如,产生一个输出文件它可再次作为ld的输入,这经常被叫做“部分链接”,当我们需要将几个小的.o文件链接成为一个.o文件的时候,需要使用此选项

    最终将各个子目录中的built-in.o文件链接在一起就形成了u-boot,使用如下命令编译uboot就可以看到链接的过程:

    make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157d_atk_defconfig V=1
    make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- DEVICE_TREE=stm32mp157d-atk all V=1

    编译的时候会有如下图的输出:
    编译内容输出
    将其整理一下,内容如下:

    arm-none-linux-gnueabihf-ld.bfd -pie --gc-sections -Bstatic --no-dynamic-linker -Ttext
    0xC0100000 -o u-boot -T u-boot.lds
    arch/arm/cpu/armv7/start.o --start-group
    arch/arm/cpu/built-in.o
    arch/arm/cpu/armv7/built-in.o
    arch/arm/lib/built-in.o
    arch/arm/mach-stm32mp/built-in.o
    board/st/common/built-in.o
    board/st/stm32mp1/built-in.o
    cmd/built-in.o
    common/built-in.o
    disk/built-in.o drivers/built-in.o
    drivers/dma/built-in.o
    drivers/gpio/built-in.o
    drivers/i2c/built-in.o
    drivers/net/built-in.o
    drivers/net/phy/built-in.o
    drivers/power/built-in.o
    drivers/power/battery/built-in.o
    drivers/power/domain/built-in.o
    drivers/power/fuel_gauge/built-in.o
    drivers/power/mfd/built-in.o
    drivers/power/pmic/built-in.o
    drivers/power/regulator/built-in.o
    drivers/serial/built-in.o drivers/spi/built-in.o
    drivers/usb/cdns3/built-in.o
    drivers/usb/common/built-in.o
    drivers/usb/dwc3/built-in.o
    drivers/usb/emul/built-in.o
    drivers/usb/eth/built-in.o
    drivers/usb/gadget/built-in.o
    drivers/usb/gadget/udc/built-in.o
    drivers/usb/host/built-in.o
    drivers/usb/musb-new/built-in.o
    drivers/usb/musb/built-in.o
    drivers/usb/phy/built-in.o
    drivers/usb/ulpi/built-in.o
    env/built-in.o
    fs/built-in.o
    lib/built-in.o
    net/built-in.o --end-group
    arch/arm/lib/eabi_compat.o
    arch/arm/lib/lib.a

    可以看出最终是用arm-none-linux-gnueabihf-ld.bfd命令将arch/arm/cpu/armv7/start.o和其他众多的built_in.o链接在一起,形成u-boot。

    目标all除了u-boot.bin以外还有其他的依赖,比如u-boot.srec 、u-boot.sym 、System.map、u-boot.cfg和binary_size_check等等,这些依赖的生成方法和u-boot.bin很类似,自行查看一下顶层Makefile,就不详细的讲解了。

    总结一下“make”命令的流程,如下图所示:
    make命令流程
    上图就是“make”命令的执行流程,关于uboot的顶层Makefile就分析到这里,重点是“make xxx_defconfig”和“make”这两个命令的执行流程:

    • make xxx_defconfig:用于配置uboot,这个命令最主要的目的就是生成.config 文件。
    • make:用于编译uboot,这个命令的主要工作就是生成二进制的u-boot.bin文件和其他的一些与uboot有关的文件,比如u-boot.stm32等等。

    关于uboot的顶层Makefile就分析到这里,重点是使用uboot,要做的是缕清uboot的流程。

    总结

    文件结构

    本篇比较重要的就是uboot的configs文件夹,里面是配置uboot的各个配置文件,正点原子的开发板配置文件就是stm32mp157d_atk_defconfig文件;.config是使用“make xxx_defconfig”配置uboot后自动生成的,这些配置量会有大量定义赋值,赋值为‘y’即代表使能,以此来控制需要编译的文件对应的.o文件,以此来编译.c文件(uboot和Linux内核都是这种方法来选择使能某个功能,编译对应源码)。

    顶层Makefile源码配置部分

    uboot的顶层Makefile默认编译输出短命令,可以通过“V=1”来输出完整命令,也可以通过“make -s”实现静默输出;使用“make O=<输出指定目录>”控制输出目录(一般不用,就直接编译生成到同一目录);使用“make C=1”实现代码检查,“make C=2”检查所有源码文件;使用“make M=dir”编译单独某个模块;

    以上的判断源码完成后,uboot的顶层Makefile会获取主机架构和系统,然后设置目标架构、交叉编译器和配置文件,然后调用scripts/Kbuild.include文件,该文件中定义了很多变量;然后会设置交叉编译器的工具变量;导出其他变量(export),一共有7个,来自于config.mk文件,得到的结果入下:

    ARCH=arm
    CPU=armv7
    BOARD=stm32mp1
    SOC=stm32mp
    CPUDIR=arch/arm/cpu/armv7
    BOARDDIR=st/stm32mp1

    最重要的来了:
    编译uboot之前需要“make xxx_defconfig”来配置uboot,经过解读源码,变量配置结果如下:

    config-targets = 1
    mixed-targets = 0
    dot-config = 1

    并会执行如下两行脚本:

    @make -f ./scripts/Makefile.build obj=scripts/basic
    @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

    第一个scripts_basic目标作用就是编译出scripts/basic/fixdep这个软件;而第二个就是对应的%config目标,变量值如下:

    src= scripts/kconfig
    kbuild-dir = ./scripts/kconfig
    kbuild-file = ./scripts/kconfig/Makefile
    include ./scripts/kconfig/Makefile

    就会因为这些变量的配置执行到scripts/kconfig/conf,生成后就会执行如下命令:

    @ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig

    最终通过上述命令生成.config文件。

    顶层Makefile的编译make过程

    因为没有制定目标,所以make会使用默认目标_all,而_all依赖all,而all依赖ALL-y,从源码解读出ALL_y包含u-boot.srec、u-boot.bin、u-boot.sym、System.map、u-boot.cfg和binary_size_check这几个文件(这一部分包含哪些文件就要看是否该变量被使能‘y’)。

    ALL_Y包含u-boot.bin,就是最后要使用的uboot二进制可执行文件,在配置中,其依赖于u-boot-dtb.bin,而这个文件依赖于u-boot-nodtb.bin,他们都调用了函数if_changed,该函数在scripts/Kbuild.include定义,通过这个函数来在u-boot-nodtb.bin中生成u-boot.bin;u-boot-nodtb.bin是依赖于u-boot的,而u-boot依赖u-boot_init、u-boot-main和u-boot.lds,前两者是变量,分别为:

    u-boot-init= arch/arm/cpu/armv7/start.o
    u-boot-main := $(libs-y)

    其中libs-y最终就是所有子目录中built-in.o的集合。也就是说u-boot最后相当于以u-boot.lds为链接脚本,将arch/arm/cpu/armv7/start.o和各个子目录下的built-in.o链接在一起生成u-boot;而bulit-in.o文件就是使用ld命令配合“-r”参数从ST提供的STM32系列驱动文件来编译生成的。

    make全过程

  • 相关阅读:
    面试官:你了解git cherry-pick吗?
    护理管理学复习题及参考答案
    抓包工具大全整理
    任务调度线程池基本介绍
    重金属行业经销商渠道管理系统:完善客户管理体系,规范渠道销售管理
    IT项目管理必备软件,这10款精心整理的项目管理工具请收好!
    Esbuild Bundler HMR
    想要提高客户留资率?一个留资机器人就够了!
    Tomcat启动时出现乱码的解决方式
    【Android】固件结构
  • 原文地址:https://blog.csdn.net/xhj12138/article/details/133516196