zoukankan      html  css  js  c++  java
  • 2014-10 u-boot make过程分析

    /**
    ******************************************************************************
    * @author    Maoxiao Hu
    * @version   V1.0.0
    * @date       Dec-2014
    ******************************************************************************
    * < COPYRIGHT 2014 ISE of SHANDONG UNIVERSITY >
    *******************************************************************************
    **/
     
    Based on u-boot-2014-10.
     
    当我们已经做完make xxx_defconfig后(这个流程可以参看:《2014-10 u-boot make xxx_defconfig 过程分析》),在源码顶层目录生成.config文件,然后我们执行make命令,下面是它的流程:
    make默认make all所有的目标,而all的定义如下:
     
    all:        $(ALL-y)
     
    需要条件$(ALL-y),而$(ALL-y)的定义如下:
     

    ALL-y += u-boot.srec u-boot.bin System.map binary_size_check

     
    需要条件:
    1、u-boot.srec

    u-boot.srec: u-boot FORCE

        $(call if_changed,objcopy)

    2、u-boot.bin

    u-boot.bin: u-boot FORCE

        $(call if_changed,objcopy)

        $(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))

        $(BOARD_SIZE_CHECK)

    3、System.map

    System.map: u-boot

     

            @$(call SYSTEM_MAP,$<) > $@

     

    4、binary_size_check
     binary_size_check: u-boot.bin FORCE

         @file_size=$(shell wc -c u-boot.bin | awk '{print $$1}') ;

         map_size=$(shell cat u-boot.map |

             awk '/_image_copy_start/ {start = $$1} /_image_binary_end/ {end = $$1} END {if (start != "" && end != "") print "ibase=16; " toupper(end) " - " toupper(start)}'

             | sed 's/0X//g'  

             | bc);          

         if [ "" != "$$map_size" ]; then

             if test $$map_size -ne $$file_size; then

                 echo "u-boot.map shows a binary size of $$map_size" >&2 ;                                                                                                                              

                 echo "  but u-boot.bin shows $$file_size" >&2 ;

                 exit 1;      

             fi

         fi                    

     
    由此大概可以看出,他们都首先需要u-boot这个elf文件。
    而u-boot的依赖关系:

    u-boot:$(u-boot-init) $(u-boot-main) u-boot.lds

        $(call if_changed,u-boot__)

     
    (1)u-boot-init定义为:

    u-boot-init := $(head-y)

    head-y的定义为:  

    head-y := $(CPUDIR)/start.o

    (2)u-boot-main定义为:

    u-boot-main := $(libs-y)

    libs-y的定义为各种库和驱动,项目较多,在此只列出几个比较麻烦的引用:

    libs-y += lib/

    libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/

    libs-y += $(CPUDIR)/

    ifdef SOC

    libs-y += $(CPUDIR)/$(SOC)/

     

    endif

     

    libs-y += arch/$(ARCH)/lib/

     

    VENDOR CPUDIR SOC ARCH等的定义在顶层目录中的config.mk,因为顶层目录的config.mk已经被包含到Makefile中了:

    include$(srctree)/config.mk

    config.mk的内容在以后博客中分析(已更新《2014-10 u-boot 顶层config.mk分析》)。
    (3)u-boot.lds定义为:

    u-boot.lds: $(LDSCRIPT) prepare FORCE

        $(call if_changed_dep,cpp_lds)

    $(LDSCRIPT)定义为:

     514 # If there is no specified link script, we look in a number of places for it

     515 ifndef LDSCRIPT

     516     ifeq ($(wildcard $(LDSCRIPT)),)

     517         LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds

     518     endif

     519     ifeq ($(wildcard $(LDSCRIPT)),)

     520         LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot.lds

     521     endif

     522     ifeq ($(wildcard $(LDSCRIPT)),)

     523         LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot.lds

     524     endif

     525 endif

     
    LDSCRIPT优先使用这三者中后面的lds,因为:=符号的取值是由命令当前所处位置决定的。
     
    preparede定义:

    prepare:prepare0

    prepare0的定义:

    prepare0: archprepare FORCE

        $(Q)$(MAKE)$(build)=.

     
    archprepare的定义:

    archprepare: prepare1 scripts_basic

    scripts_basic展开为:
    @make -f scripts/Makefile.build obj=scripts/basic
    prepare1的定义:
    prepare1: prepare2 $(version_h) $(timestamp_h) include/config/auto.conf
    ------

    $(version_h): include/config/uboot.release FORCE

        $(call filechk,version.h)

    version_h:= include/generated/version_autogenerated.h

    $(timestamp_h): $(srctree)/Makefile FORCE

        $(call filechk,timestamp.h)

    timestamp_h := include/generated/timestamp_autogenerated.h

    ------
    prepare2的定义:

    prepare2: prepare3 outputmakefile

    outputmakefile并不执行,原因还是请见另一篇blog《u-boot make xxx_defconfig 过程分析》
    prepare3的定义:

    prepare3: include/config/uboot.release

     
    include/config/auto.conf的定义和生成:

    include/config/%.conf:$(KCONFIG_CONFIG) include/config/auto.conf.cmd

     

        $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig

     
    $(KCONFIG_CONFIG) 就是 .config 这个配置文件。
    那么  include/config/auto.conf.cmd 这个文件应该在什么时候生成?

    现在仍然假设 auto.conf 和 auto.conf.cmd 还没有生成,那么由上面的 $(KCONFIG_CONFIG) include/config/auto.conf.cmd: ; 这条语句知道,该语句中的目标没有依赖,也没有生成它的规则命令,所以可想 GNU Make 本身无法生成 auto.conf.cmd 的。然后该条语句后面的一个分号表明,这两个目标被强制是最新的,所以下面这条命令得以执行:
    $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig

    这里我们看到要生成一个目标 silentoldconfig ,这个目标定义在 scripts/kconfig/Makefile 中。因为这里使用 -f 选项重新指定了顶层 Makefile,而目标又是 silentoldconfig ,所以该命令最终会在顶层 Makefile 的 462-464 这里执行:
    1
    2
    3
    %config: scripts_basic outputmakefile FORCE
            $(Q)mkdir -p include/linux include/config
            $(Q)$(MAKE) $(build)=scripts/kconfig $@


    这时,我们来到 scripts/kconfig/Makefile 文件里。在该文件的 32-34 行看到:
    1
    2
    3
    silentoldconfig: $(obj)/conf
            $(Q)mkdir -p include/generated
            $< -s $(Kconfig)

    从上面看到,silentoldconfig 目标需要依赖 conf 这个程序,该程序也在 scripts/kconfig 目录下生成。
    $< -s $(Kconfig) 该条命令相当于 conf -s $(Kconfig) ,这里 $(Kconfig) 是位于不同平台目录下的 Kconfig 文件,比如在 x86 平台就是 arch/x86/Kconfig 。

    conf 程序的源代码的主函数在同目录的 conf.c 文件中,在 main() 函数中看到:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    while ((opt = getopt(ac, av, "osdD:nmyrh")) != -1) {
            switch (opt) {
            case 'o':
                input_mode = ask_silent;
                break;
            case 's':
                input_mode = ask_silent;
                sync_kconfig = 1;
                break;
    ... ...

    所以,在使用 s 参数时,sync_kconfig 这个变量会为 1 。同样在 main() 函数还看到:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        if (sync_kconfig) {
            name = conf_get_configname();
            if (stat(name, &tmpstat)) {
                fprintf(stderr, _("*** "
                    "*** You have not yet configured your kernel! "
                    "*** (missing kernel config file "%s") "
                    "*** "
                    "*** Please run some configurator (e.g. "make oldconfig" or "
                    "*** "make menuconfig" or "make xconfig"). "
                    "*** "), name);
                exit(1);
            }
        }

    上面代码中,如果我们从未配置过内核,那么就会打印出错误信息,然后退出。这里假设已经配置过内核,并生成了 .config 文件,那么在 main() 函数中会来到:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
        switch (input_mode) {
        case set_default:
            if (!defconfig_file)
                defconfig_file = conf_get_default_confname();
            if (conf_read(defconfig_file)) {
                printf(_("*** "
                    "*** Can't find default configuration "%s"! "
                    "*** "), defconfig_file);
                exit(1);
            }
            break;
        case ask_silent:
        case ask_all:
        case ask_new:
            conf_read(NULL);
            break;
    ... ...

    由于使用 s 选项,则 input_mode 为 ask_silent,所以这里会执行 conf_read(NULL); 函数。
    conf_read(NULL); 函数用来读取 .config 文件。读取的各种相关内容主要存放在一个 struct symbol 结构链表里,而各个结构的相关指针则放在一个 symbol_hash[] 的数组中,对于数组中元素的寻找通过 fnv32 哈希算法进行定位。

    最后会来到 conf.c 中的底部:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        if (sync_kconfig) {
            /* silentoldconfig is used during the build so we shall update autoconf.
             * All other commands are only used to generate a config.
             */
            if (conf_get_changed() && conf_write(NULL)) {
                fprintf(stderr, _(" *** Error during writing of the kernel configuration. "));
                exit(1);
            }
            if (conf_write_autoconf()) {
                fprintf(stderr, _(" *** Error during update of the kernel configuration. "));
                return 1;
            }
        } else {
            if (conf_write(NULL)) {
                fprintf(stderr, _(" *** Error during writing of the kernel configuration. "));
                exit(1);
            }
        }
     
    实际上也只有当处理 silentoldconfig 目标是 sync_kconfig 变量才会为 1 。上面代码中的 conf_write_autoconf() 函数就用来生成 auto.conf, auto.conf.cmd 以及 autoconf.h 这 3 个文件。

    在 if (conf_get_changed() && conf_write(NULL)) 这个判断里,conf_get_changed() 函数判断 .config 文件是否做过变动,如果是,那么会调用 conf_write(NULL) 来重新写 .config 文件。实际上,对于 defconfig, oldconfig, menuconfig 等目标来说,conf 程序最终也是调用 conf_write() 函数将配置结果写入 .config 文件中(最后那个 else 里的内容便是)。

    确保了 .config 已经最新后,那么调用 conf_write_autoconf() 生成 auto.conf,auto.conf.cmd 以及 autoconf.h 这 3 个文件。

    来到 conf_write_autoconf() 函数:j

    在 conf_write_autoconf() 里,调用 file_write_dep("include/config/auto.conf.cmd"); 函数将相关内容写入 auto.conf.cmd 文件。在生成的 auto.conf.cmd 文件中可以看到:
     
    1
    2
    include/config/auto.conf:
            $(deps_config)

    可以看到 auto.conf 文件中的内容依赖于 $(deps_config) 变量里定义的东西,这些东西基本上是各个目录下的 Kconfig 以及其它一些相关文件。

    auto.config 和 .config 的差别是在 auto.config 里去掉了 .config 中的注释项目以及空格行,其它的都一样。

    仍然在 conf_write_autoconf() 里,分别建立了 .tmpconfig,.tmpconfig_tristate 和 .tmpconfig.h 这 3 个临时文件:
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    out = fopen(".tmpconfig", "w");
        if (!out)
            return 1;
     
        tristate = fopen(".tmpconfig_tristate", "w");
        if (!tristate) {
            fclose(out);
            return 1;
        }
     
        out_h = fopen(".tmpconfig.h", "w");
        if (!out_h) {
            fclose(out);
            fclose(tristate);
            return 1;
        }

    然后将文件头的注释部分分别写入到这几个临时文件中:
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
        sym = sym_lookup("KERNELVERSION", 0);
        sym_calc_value(sym);
        time(&now);
        fprintf(out, "# "
                 "# Automatically generated make config: don't edit "
                 "# Linux kernel version: %s "
                 "# %s"
                 "# ",
                 sym_get_string_value(sym), ctime(&now));
        fprintf(tristate, "# "
                  "# Automatically generated - do not edit "
                  " ");
        fprintf(out_h, "/* "
                   " * Automatically generated C config: don't edit "
                   " * Linux kernel version: %s "
                   " * %s"
                   " */ "
                   "#define AUTOCONF_INCLUDED ",
                   sym_get_string_value(sym), ctime(&now));


    接着在 for_all_symbols(i, sym) 这个循环里(是一个宏)里将相关内容分别写入到这几个文件中。

    在最后一段代码中,将这几个临时文件进行改名:
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    name = getenv("KCONFIG_AUTOHEADER");
        if (!name)
            name = "include/generated/autoconf.h";
        if (rename(".tmpconfig.h", name))
            return 1;
        name = getenv("KCONFIG_TRISTATE");
        if (!name)
            name = "include/config/tristate.conf";
        if (rename(".tmpconfig_tristate", name))
            return 1;
        name = conf_get_autoconfig_name();
        /*
         * This must be the last step, kbuild has a dependency on auto.conf
         * and this marks the successful completion of the previous steps.
         */
        if (rename(".tmpconfig", name))
            return 1;

    上面代码中的 conf_get_autoconfig_name() 实现为:
     
    1
    2
    3
    4
    5
    6
    const char *conf_get_autoconfig_name(void)
    {
        char *name = getenv("KCONFIG_AUTOCONFIG");
     
        return name ? name : "include/config/auto.conf";
    }

    从上面可以看到,分别生成了以下几个文件:
    引用
    include/generated/autoconf.h
    include/config/tristate.conf
    include/config/auto.conf


    其中 include/generated/autoconf.h 头文件由内核本身使用,主要用来预处理 C 代码。比如在 .config 或 auto.conf 中定义要编译为模块的项,如:
    CONFIG_DEBUG_NX_TEST=m
    在 autoconf.h 中会被定义为:
    #define CONFIG_DEBUG_NX_TEST_MODULE 1

    在 .config 或 auto.conf 后接字符串值的项,如:
    CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config"
    在 autoconfig.h 中会被定义为:
    #define CONFIG_DEFCONFIG_LIST "/lib/modules/$UNAME_RELEASE/.config"

    同样对应于 int 型的项如 CONFIG_HZ=1000 在 autoconf.h 中被定义为 #define CONFIG_HZ 1000 。
     
    后半段引用于:http://blog.csdn.net/lcw_202
  • 相关阅读:
    darknet版本yolov3训练与测试
    Package opencv was not found in the pkg-config search path.
    ubuntu18.4下安装Anaconda及conda命令
    ubuntu18.4编译opencv4.1
    ubuntu18.4下cuda卸载
    抽象基类、访问控制与继承和继承中的类作用域
    C++ Pirmer : 第十五章 : 面向对象程序设计之基类和派生的定义、类型转换与继承与虚函数
    C++ Primer : : 第十四章 : 重载运算符与类型转换之类型转换运算符和重载匹配
    C++ Pirmer : 第十四章 : 重载运算符与类型转换之函数调用运算符与标准库的定义的函数对象
    C++ Primer : 第十四章 : 重载运算与类型转换之重载运算符
  • 原文地址:https://www.cnblogs.com/humaoxiao/p/4189411.html
Copyright © 2011-2022 走看看