U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

  在之前的博文 Linux 之八 完整嵌入式 Linux 环境介绍及搭建过程详解 中我们说了要一步步搭建整个嵌入式 Linux 运行环境,今天继续介绍 U-Boot 相关的内容。我所使用的硬件平台及整个要搭建的嵌入式 Linux 环境见博文 Linux 之八 完整嵌入式 Linux 环境介绍及搭建过程详解,没有特殊说明都是在以上环境中进行验证的,就不过多说明了。

  这篇博文我们仅仅关注 U-Boot 构建过程本身,想要吃透 U-Boot,有太多东西需要学习!最开始我想放到一篇文章中,写着写着内容越来越多,最终超过了 CSDN 编辑器的限制。。。最终决定把内容拆分成多篇文章。你可能需要:

  1. U-Boot 之一 零基础编译 U-Boot 过程详解 及 编译后的使用说明
  2. U-Boot 之二 详解使用 eclipse + J-Link 进行编译及在线调试
  3. U-Boot 之三 U-Boot 源码文件解析及移植过程详解
  4. U-Boot 之五 详解 U-Boot 及 SPL 的启动流程

Kbuild && Kconfig

  Kbuild && Kconfig 隶属于 Linux Kernel Build System。在宏观上,Kbuild && Kconfig 可以统称为 Kbuild,从微观上来说,Kbuild 指的是编译的过程,而 Kconfig 指的在编译之前对内核进行配置的过程(该过程中会编译一些工具来实现配置过程)。关于详细的可以查看 Linux Kernel 的官方文档 The Linux Kernel Documentation 中的 Kernel Build System 章节的介绍。

  1. 本文约定俗成的称呼:构建 指的 Kbuild + Kconfig;配置 指的 Kconfig;编译 指的 Kbuild。
  2. https://www.kernel.org/doc/

  Kbuild && Kconfig 这套构建系统一个显著的特点就是每一级目录都会有单独的相关文件,然后会被上一级相同的文件引用。这样就保证了每一级目录都是相互独立的。尤其是对于源码的维护者来说,这个是至关重要。每个维护者可以只关心他所负责的代码部分,任何更改只需要变更它自己的 Kbuild && Kconfig 相关文件即可。它本身主要包含以下几类文件:

  • Makefile: Kbuild && Kconfig 这套构建系统本身属于 make 功能的扩展,因此,整个工作过程就是一些列 Makefile 文件的调用。其中入口就是根目录下的 Makefile 文件,Makefile 中会调用各种工具以实现不同的功能。
      注意,为了区分不同的功能,在源码中对于 Makefile 的命名有时候会加一个后缀。例如,config.mkMakefile.buildMakefile.clean 等这些都属于 Makefile 文件。
      Makefile 文件无法在线调试,对于理解一个复杂的 Makefile 很不友好。一般我们可以使用 --debug 参数让 make 打印详细的信息来协助理解。或者是在 Makefile 中添加一些打印信息,常用打印方式有两种:

    1. 使用 $(info, xxxx$(xxx))$(warning, xxxx$(xxx))$(error, xxxx$(xxx)),其中,$(xxx) 表示某个变量。这三个命令可以加到 Makefile 的任意地方,注意 $(error, xxxx$(xxx)) 会终止 Make 过程。
    2. 使用 @echo "xxxx $xx xxxx",其中,$(xxx) 表示某个变量。这个命令只能用在目标后边,且前面是个TAB。这个就是标准 Makefile 语法中的一个命令。

    再顺带说一句 make 的工作的一些机制:

    1. 如果给出了参数,则 make 优先去找匹配的规则(匹配规则:完整匹配 > 通配符半匹配 > 完全通配符匹配)去执行;如果没有给出参数,make 会自动找到 Makefile 中第一个目标中没有通配符的规则执行。
    2. 如果中间遇到 include 其他文件,就会紧接着执行 include 的文件,完成后再继续执行本文件。
    3. make 总是从 Makefile 的开头开始解析,并不是找到匹配目标之后仅执行匹配目标的命令。也就是说,在匹配之前,Make 可能已经解析了很多判断条件。
    4. 对于匹配的规则如果有依赖,优先解析依赖。注意,依赖的匹配也符合 1 中所说的规则。
    5. 命令前面加了 @ 字符,则不显示命令本身而只显示它的结果。命令前面加了 - 号,即使这条命令出错,make 也会继续执行后续命令。
    6. 如果 Makefile 中存在多条同名规则,则 make 程序会尝试将他们合并。但是如果这些同名规则都有命令的话,make 会给出警告,并用后面的命令代替前面的命令。
      U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
  • Kconfig 文件: 这个文件就是 Kconfig 系统的菜单项,当我们使用命令:make menuconfig 时,Kconfig 系统读取该文件,根据该文件的内容生成各级菜单。U-Boot 源码根目录下的 Kconfig 就是*的配置菜单,其中会在引入其他目录下的 Kconfig 作为二级菜单,依次类推。

  • .config 文件: 这个文件记录了我们菜单项中的配置的具体值,我们所有对于构建的配置的存放在这个文件中,我们在,Kconfig 系统菜单中的更改,最终都会改写该文件。注意:该文件默认是个隐藏文件,可以使用 ls -al 查看。

  • xxxx_defconfig 文件: xxxx_defconfig 文件就为 Kconfig 系统的菜单项提供一个默认值。不过,Kconfig 系统不会直接读取 xxxx_defconfig 文件,而是需要使用方式是通过 make xxx_deconfig 生成带默认值的 .config。这样,在加载 Kconfig 时,就可以同时加载 .config 以提供默认值。
      简单来说,xxxx_defconfig 就是为了方便支持更多个性化配置,从而可以尽可能少的改动源代码。

  • Kbuild 文件: 这个是 Kbuild 系统使用的文件,该文件用于定义一些源码使用的需要根据编译环境产生的中间文件。

  • config.mk 文件: 用来处理一些编译过程中的环境变量。Linux Kernel 没有这个文件,U-Boot 需要使用它。

  U-Boot 从 v2014.10 版本开始也引入 Kbuild && Kconfig 这套构建系统,相比于原来应该是复杂了不少。但是对于属性 Linux Kernel 的人来说确实是一个好消息!今天我们就来学习一下 U-Boot 中这套系统的具体工作流程。

  Kbuild && Kconfig 这套构建系统中定义了很多命令,我们可以使用 make help 来进行查看(就在根目录的 Makefile 文件中),其中经常用到命令如下图所示:
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

构建过程

  在 Kbuild && Kconfig 这套构建系统中,源码中使用的有些文件是要靠 Kbuild && Kconfig 这套系统来生成的,直接在源码中是找不到。这就要求我们必须要了解 Kbuild && Kconfig 是如何工作的,更重要的是要知道 Kbuild && Kconfig 会产生哪些源码使用的文件。

  整个构建过程的这个入口就是源码根目录下的 Makefile 文件。下面我们先来整体看一下这个文件的总体结构,并且对其中的规则进行一下说明。其大体可以分为三部分:
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
整个文件的前半部分就是定义一堆符号,检测工作环境、处理各种参数,基本没有实际与编译源码相关的命令。下面重点介绍一些(注意,由于我在其中添加了一些打印信息,导致行号与原文件有区别):

  • HOST_ARCH 赋值。HOST_ARCH 代表我们当前指定的 主机 的类型。当我们没有指定的时候,它就是我们的 PC 架构(例如我的 HOST_ARCH_X86_64),指定 交叉编译器 后(例如,我们编译时 CROSS_COMPILE=arm-none-eabi- ARCH=arm make -j4),他就是我们指定的架构(HOST_ARCH_ARM)。
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
  • 处理 make 参数:make V=1make -s 等。这里导出了 quietQKBUILD_VERBOSE,用以指示编译过程的显示方式。
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
  • 处理编译的当前目录以及编译输出目录。其中,我们可以使用环境变量 KBUILD_OUTPUT 或者 指定 make O=xxx 的方式指定输出目录。而编译的源目录 KBUILD_SRC 根据注释仅用于在 OBJ 目录时,而且一般不给使用者使用。代码可以简化为以下逻辑:
    if `KBUILD_SRC` 为空
    	检查 O=xxx 参数,如果有指定,则赋值给 `KBUILD_OUTPUT`
    	定义伪目标_all ,且 _all 为空目标,避免后面include其余文件的时候寻找隐含的第一个目标。后面肯定还会定义 _all, 而且肯定不为空
    	if `KBUILD_OUTPUT` 不为空
    		`KBUILD_OUTPUT` = 创建并指定的目录
    		到 `KBUILD_OUTPUT` 继续执行 make (其中需要过滤掉一些内容,防止无限嵌套)。因为 在 `KBUILD_OUTPUT` 中执行的 Makefile 就是当前这个 Makefile
    		skip-makefile 赋值为 1
    	endif
    endif
    
    if skip-makefile 为空。不空表示上面已经在 `KBUILD_OUTPUT` 中执行过了
    	执行其他各种操作
    endif
    
    整个makefie 结束
    
      这里需要注意的就是,如果指定了输出目录,将转到 输出目录中去执行 Makefile,本 Makefile 的执行就结束了。skip-makefile 被赋值为 1,此后只有在 skip-makefile 不为 1 时才会继续执行。
  • 再接下来就是判断 make C=x 参数以及处理是编译整个 U-Boot 还是编译的模块(由环境变量 SUBDIRSmake M=xxx 执行),如果是模块,赋值给 KBUILD_EXTMOD(赋值厚就不空)。
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
      前面有说过,开始定义了一个空的目标 _all,这里再次定义目标 _all。这里的 _all 根据 KBUILD_EXTMOD 的值依赖于 all 或者是 modules,但是命令还是为空。这里我们不关心模块,重点关注 allall 又依赖 .binman_stmpinputs,如下图所示:
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
    关于 all 后文会有详细的介绍。
  • 继续下来就是检查编译工具链。并做了一些重命名以统一编译时的显示。
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
  • 再继续就是 kconfig 和 kbuild 共用的一些规则 scripts_basicoutputmakefile 的定义。然后检查要执行的操作是否需要 .config 文件,最终出现以下逻辑:
    if 是多个目标(mixed-targets=1)
    	一个一个处理
    else if 配置(config-targets=1),即我们执行的 make xxxconfig 时的处理
          config: scripts_basic outputmakefile FORCE
              $(Q)$(MAKE) $(build)=scripts/kconfig $@
          %config: scripts_basic outputmakefile FORCE
              $(Q)$(MAKE) $(build)=scripts/kconfig $@
    else 这里就是 编译过程以及 非 config(例如 clean) 的处理了
          scripts: scripts_basic scripts_dtc include/config/auto.conf
              $(Q)$(MAKE) $(build)=$(@)
         if 需要 .confg 
             检查  .confg 文件
          else
              include/config/auto.conf: ;
         endif
         
         all 的定义(实际编译文件等等)
         
         非 config(例如 clean、check)等的定义
    endif
     
     Makefile 结束
    
    也就是在此之前的内容,无论给出啥命令,都会被 make 解析,然后从这里开始分道扬镳!

  至此,就开始根据给出的目标的不同开始区分具体操作类型了,后续我们单独章节来介绍!***注意,由于我在 Makefile 中添加了一些打印信息,导致行号与原文件有区别!***下面是我大体整理的一个流程图:
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

Kconfig

  在 Kbuild && Kconfig 这套构建系统中,当我们使用 make xxxconfg 类似的命令时,就会执行 Kconfig 流程。例如,当执行 make menuconfig 时会出现一个配置界面,允许开发者通过类似于 UI 的方式来对内核进行配置,之所为我们可以看到这个类似于 UI 的界面,就是因为 Kconfig 从中产生了多个文件和工具来实现的。

Kconfig 语法可以从 https://www.kernel.org/doc/html/latest/kbuild/kconfig-language.html 里来学习。

  当我们在 U-Boot 根目录执行 make menuconfig 或者 make xxx_deconfig 时,make 命令便会读取 U-Boot 根目录下的 Makefile 文件,然后解析并匹配 Makefile 文件中的规则 。而 xxxconfig 就会匹配根目录下 Makefile 文件中的如下图示的规则(% 可以匹配任意非空字符串,所以 menuconfig、xxx_deconfig 都匹配):
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

  • Q 是否显示信息信息,在 Makefile 前面有赋值为 @
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
  • MAKE: 就是指的可执行程序 make
  • $(build): 定义在 ./scripts/Kbuild.include 中:181行
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
    那么将以上命令展开之后就是:make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfig 或者 $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
  • $(srctree): 在 Makefile 前面有赋值为 .$(KBUILD_SRC)
  • scripts_basic: 就定义在 U-Boot 根目录的 Makefile 中
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
    那么将以上命令第一句规则展开之后就是:make -f $(srctree)/scripts/Makefile.build obj=scripts/basic ,第二句规则没啥可说的,就是移除个文件。
  • outputmakefile: 就定义在 U-Boot 根目录的 Makefile 中,如下所示:
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
    从规则代码可以看出只有当 KBUILD_SRC 不为空时才有效,而对于 KBUILD_SRC 一般都是空,非空的情况下一般也不使用。因此,这里的依赖 outputmakefile 这里就是空。对于具体见如下说明:
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
  • FORCE 是没有规则和依赖的,所以每次都会重新生成 FORCE。当 FORCE 作为其他目标的依赖时,由于 FORCE 总是被更新过的,因此依赖所在的规则总是会执行的。
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

  经过上面的分析,最终由两条语句 make -f $(srctree)/scripts/Makefile.build obj=scripts/basicmake -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfig 或者 make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig xxx_defconfig 是我们需要进一步来解析的。接下来就要进入 /scripts/Makefile.build 这个文件了。

Makefile.build

  对于 Makefile.build 文件,目前我们只需要关注下图所示的两部分(说明见注释):
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
接下就是进一步处理 scripts/basic/Makefile 或者 scripts/kconfig/Makefile 了。

make xxx_deconfig

  经过上面的分析,当我执行 make xxx_defconfig 时(例如,我这里的 make stm32f769-disco_defconfig),最终会执行以下两句:make -f $(srctree)/scripts/Makefile.build obj=scripts/basicmake -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

  • make -f $(srctree)/scripts/Makefile.build obj=scripts/basic 展开后是 cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -o scripts/basic/fixdep scripts/basic/fixdep.c 最终在 scripts/basic/ 目录下生成了可执行工具 fixdep
  • make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig xxx_defconfig 展开后是:
     cc -Wp,-MD,scripts/kconfig/.conf.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer   -std=gnu11     -c -o scripts/kconfig/conf.o scripts/kconfig/conf.c
      bison -oscripts/kconfig/zconf.tab.c -t -l scripts/kconfig/zconf.y
      flex -oscripts/kconfig/zconf.lex.c -L scripts/kconfig/zconf.l
      cc -Wp,-MD,scripts/kconfig/.zconf.tab.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer   -std=gnu11    -Iscripts/kconfig -c -o scripts/kconfig/zconf.tab.o scripts/kconfig/zconf.tab.c
      cc   -o scripts/kconfig/conf scripts/kconfig/conf.o scripts/kconfig/zconf.tab.o   
    scripts/kconfig/conf  --defconfig=arch/../configs/stm32f769-eval_defconfig Kconfig
    
    最终在 scripts/kconfig/ 目录下生成了可执行工具 conf,紧接着使用刚生成的 scripts/kconfig/conf 工具根据我们指定的 stm32f769-eval_defconfig 生成 .config 文件。

make menuconfig

  经过上面的分析,当我执行 make menuconfig 时,最终会执行以下两句:make -f $(srctree)/scripts/Makefile.build obj=scripts/basicmake -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfig
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

  • make -f $(srctree)/scripts/Makefile.build obj=scripts/basicmake xxx_deconfig 时的语句是一样的。如果在执行 make menuconfig 之前没有使用 make xxx_deconfig,那么这句就会和 make xxx_deconfig 时一样去执行来生成 scripts/basic/fixdep
  • make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfig 展开后是:
    make -f ./scripts/Makefile.build obj=scripts/basic
    rm -f .tmp_quiet_recordmcount
    make -f ./scripts/Makefile.build obj=scripts/kconfig menuconfig
    set -e; mkdir -p scripts/kconfig/;      /bin/bash scripts/kconfig/mconf-cfg.sh < scripts/kconfig/mconf-cfg.sh > scripts/kconfig/.mconf-cfg.tmp; if [ -r scripts/kconfig/.mconf-cfg ] && cmp -s scripts/kconfig/.mconf-cfg scripts/kconfig/.mconf-cfg.tmp; then rm -f scripts/kconfig/.mconf-cfg.tmp; else : '  UPD     scripts/kconfig/.mconf-cfg'; mv -f scripts/kconfig/.mconf-cfg.tmp scripts/kconfig/.mconf-cfg; fi
      cc -Wp,-MD,scripts/kconfig/.mconf.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer   -std=gnu11    -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -c -o scripts/kconfig/mconf.o scripts/kconfig/mconf.c
      cc -Wp,-MD,scripts/kconfig/lxdialog/.checklist.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer   -std=gnu11    -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -c -o scripts/kconfig/lxdialog/checklist.o scripts/kconfig/lxdialog/checklist.c
      cc -Wp,-MD,scripts/kconfig/lxdialog/.inputbox.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer   -std=gnu11    -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -c -o scripts/kconfig/lxdialog/inputbox.o scripts/kconfig/lxdialog/inputbox.c
      cc -Wp,-MD,scripts/kconfig/lxdialog/.menubox.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer   -std=gnu11    -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -c -o scripts/kconfig/lxdialog/menubox.o scripts/kconfig/lxdialog/menubox.c
      cc -Wp,-MD,scripts/kconfig/lxdialog/.textbox.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer   -std=gnu11    -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -c -o scripts/kconfig/lxdialog/textbox.o scripts/kconfig/lxdialog/textbox.c
      cc -Wp,-MD,scripts/kconfig/lxdialog/.util.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer   -std=gnu11    -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -c -o scripts/kconfig/lxdialog/util.o scripts/kconfig/lxdialog/util.c
      cc -Wp,-MD,scripts/kconfig/lxdialog/.yesno.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer   -std=gnu11    -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -c -o scripts/kconfig/lxdialog/yesno.o scripts/kconfig/lxdialog/yesno.c
      cc   -o scripts/kconfig/mconf scripts/kconfig/mconf.o scripts/kconfig/zconf.tab.o scripts/kconfig/lxdialog/checklist.o scripts/kconfig/lxdialog/inputbox.o scripts/kconfig/lxdialog/menubox.o scripts/kconfig/lxdialog/textbox.o scripts/kconfig/lxdialog/util.o scripts/kconfig/lxdialog/yesno.o   -Wl,-Bsymbolic-functions -lncursesw -ltinfo
    scripts/kconfig/mconf  Kconfig
    
    最终生成 scripts/kconfig/mconf,紧接着使用刚生成的 scripts/kconfig/mconf 工具读取根目录的 Kconfig 文件,随之出现配置界面。

  make xxx_deconfig之后,Kconfig 系统会在 U-Boot 源码根目录下生成 .config 文件,当我们使用 make menuconfig 修改了相关配置之后,Kconfig 系统最终也是修改根目录下的 .config 文件(注意,该文件默认是个隐藏文件,可使用 ls -al 查看),而 .config 文件就记录了我们当前对于 U-Boot 的配置,后续构建时便会读取该文件。

Kbuild

  这里说的 Kbuild 是微观上的 Kbuild,指的是编译的过程。在经过上面 Kconfig 之后,接下来就是真正的编译过程,这个过程采用的就是 Kbuild 系统。Linux 官方文档地址:https://www.kernel.org/doc/html/latest/kbuild/index.html。

  编译使用的命令是 CROSS_COMPILE=arm-none-eabi- ARCH=arm make -j8,当我们使用该命令之后,make 程序就会读取 U-Boot 根目录的 Makefile 文件,然后解析并匹配 Makefile 文件中的规则。由于这里没有指明目标,Make 会自动找到 makefile 中第一个目标中没有通配符的规则执行,这里就是 _all
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
  注意,这里的 _all 并不是第一个定义的 _all。第一个定义的 _all 是个空命令目标,空命令行可以防止 make 在执行时试图为重建这个目标去查找隐含命令(包括了使用隐含规则中的命令和“ .DEFAULT”指定的命令)。后面重新定义的 _all 才是最终生效的 _all

  如果 KBUILD_EXTMOD 为空的话 _all 依赖于 all。我们不编译模块(没有指定 KBUILD_EXTMOD),所以 KBUILD_EXTMOD 就是空 ,_all 依赖于 all 的。 all 又依赖 .binman_stmpinputs,如下图所示:
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
注意,如果使用的命令是 CROSS_COMPILE=arm-none-eabi- ARCH=arm make all -j8,则直接就会匹配到 all 这条规则,就不会有 _all 啥事了!

  .binman_stamp这个比较简单,就是通过使用 touch 命令更新 .binman_stamp 文件(不存在时会新建)的时间戳保证 binman 总是会被执行。然后检查一些定义,给出提示。
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
  重点就在 inputs,而 inputs 又是依赖于 $(INPUTS-y)。通过将 $(INPUTS-y) 展开后就是 checkarmreloc u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check spl/u-boot-spl.bin u-boot.img u-boot.dtb u-boot-dtb.img 再进一步就是展开及处理各个依赖项了,下面我们重点来介绍一下 checkarmrelocu-boot.srec,其他的大家自行解析。

  • checkarmreloc
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
    将 u-boot 的各项依赖展开就是:u-boot: arch/arm/cpu/armv7m/start.o arch/arm/cpu/built-in.o arch/arm/cpu/armv7m/built-in.o arch/arm/lib/built-in.o arch/arm/mach-stm32/built-in.o board/st/common/built-in.o board/st/stm32f769-eval/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/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/host/built-in.o drivers/usb/mtu3/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 u-boot.lds FORCE,其中:

    • $(u-boot-init):arch/arm/cpu/armv7m/start.o
    • $(u-boot-main):后面一大串 built-in.o
    • $(u-boot-keep-syms-lto):空,不包含任何内容

    因此,我们需要重点来关注 u-boot.lds 这个依赖:
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
    其中,$(LDSCRIPT) 就是我们使用的链接脚本文件,这里就是 ./arch/arm/cpu/u-boot.lds。而 prepare 的内容就比价多了,如下图所示:
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
    中间各种依赖,最终会到 include/config/%.conf: 这条规则。

  • u-boot.srec 这个比较简单,至于它依赖的 u-boot 在上面已经说了。
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

其实到这里基本就可以了,解析来就不继续深入了!下面来一张图:
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

clean、mrproper、distclean

  介绍了以上的工作过程之后,我们再介绍一下三个与清理相关的命令:cleanmrproperdistclean。至于支持的其他命令基本都差不多,大家感兴趣可以自己分析即可。这三个命令的清理程度是逐渐增加,后者包含前者,如下图所示:
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
我们就以 distclean 为例来说明。当我们执行 make distclean 这条命令时,make 读取根目录的 Makefile,然后开始解析,最终匹配到规则 distclean: mrproper,如下所示:
U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

  • 命令部分:第一个 find 命令查找上面指定的文件并删除。其中的 “|” 表示管道,即左边的输出作为右边的输入。rm 命令删除 boards.cfg 和 CHANGELOG。

    关于 find 命令:https://www.cnblogs.com/shenqidu/p/10615593.html
    关于 xargs 命令:https://www.runoob.com/linux/linux-comm-xargs.html

  • 依赖 mrproper:接下来重点看他的依赖 mrproper,由于 mrproper 也有依赖,我把它们整理到一个图中,如下所示:
    U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
    从中可以看出,mrproper 先执行了 clean,然后执行 $(mrproper-dirs)。综合来看,cleanmrproper 结构基本一致,我们将里面各变量展开,其中:

  • 先看 clean

    • 依赖 rm-dirs:展开后就是 .tmp_versionsspl/*
    • 依赖 rm-files:展开后就是include/bmp_logo.h include/bmp_logo_data.h tools/version.h boot* u-boot* MLO* SPL System.map fit-dtb.blob* u-boot-ivt.img.log u-boot-dtb.imx.log SPL.log u-boot.imx.log lpc32xx-* bl31.c bl31.elf bl31_*.bin image.map tispl.bin* idbloader.img flash.bin flash.log defconfig keep-syms-lto.c
    • 依赖 $(clean-dirs)
      • $(clean):定义在 Kbuild.include 文件中: clean := -f $(srctree)/scripts/Makefile.clean obj
      • cmd:定义在 Kbuild.include 文件中: cmd = @$(echo-cmd) $(cmd_$(1)),进一步 $(echo-cmd)就在之上:echo-cmd = $(if $($(quiet)cmd_$(1)),echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
    • 命令部分:
      • $(call cmd,rmdirs)
      • $(call cmd,rmfiles)
      • find xxx | xargs rm -f:在以上清理的基础上再清理一些符号文件,临时文件、脚本文件等
  • mrproper

    • 依赖 rm-dirsinclude/configinclude/generated spl
    • 依赖 rm-files.config.config.oldinclude/autoconf.mkinclude/autoconf.mk.depinclude/config.h
    • 依赖 clean:就是上面的 clean
    • 依赖 $(mrproper-dirs)
    • 命令部分:
      • $(clean):同上
      • cmd:同上

参考

  1. https://loee.xyz/2021/04/27/uboot-%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/
  2. https://www.cnblogs.com/humaoxiao/p/4189411.html
  3. https://github.com/zhaojh329/U-boot-1/blob/master/%E7%AC%AC9%E7%AB%A0-U-boot%E7%BC%96%E8%AF%91%E4%B9%8B%E4%B8%80%E9%85%8D%E7%BD%AE%E8%BF%87%E7%A8%8B.md
  4. https://blog.csdn.net/guyongqiangx/article/details/52558087
  5. https://yunyanan.github.io/exploring_kbuild/
  6. https://opensource.com/article/18/10/kbuild-and-kconfig
  7. https://www.cnblogs.com/syyxy/p/9350999.html
  8. https://blog.csdn.net/q_z_r_s/article/details/80783631
  9. https://blog.csdn.net/linuxweiyh/article/details/100528718
上一篇:RV1126内核驱动模块编译


下一篇:【Java学习】- Interface and implementation