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后,在源码顶层目录生成.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,$<,[email protected],$(CONFIG_SYS_TEXT_BASE))

$(BOARD_SIZE_CHECK)

3、System.map

System.map: u-boot

@$(call SYSTEM_MAP,$<) > [email protected]

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的内容在以后博客中分析()。

(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

原因请见另一篇blog《u-boot make xxx_defconfig 过程分析》

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 [email protected]

这时,我们来到 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, _("***\n"

                "*** You have not yet configured your kernel!\n"

                "*** (missing kernel config file \"%s\")\n"

                "***\n"

                "*** Please run some configurator (e.g. \"make oldconfig\" or\n"

                "*** \"make menuconfig\" or \"make xconfig\").\n"

                "***\n"), 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(_("***\n"

                "*** Can‘t find default configuration \"%s\"!\n"

                "***\n"), 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, _("\n*** Error during writing of the kernel configuration.\n\n"));

            exit(1);

        }

        if (conf_write_autoconf()) {

            fprintf(stderr, _("\n*** Error during update of the kernel configuration.\n\n"));

            return 1;

        }

    } else {

        if (conf_write(NULL)) {

            fprintf(stderr, _("\n*** Error during writing of the kernel configuration.\n\n"));

            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, "#\n"

             "# Automatically generated make config: don‘t edit\n"

             "# Linux kernel version: %s\n"

             "# %s"

             "#\n",

             sym_get_string_value(sym), ctime(&now));

    fprintf(tristate, "#\n"

              "# Automatically generated - do not edit\n"

              "\n");

    fprintf(out_h, "/*\n"

               " * Automatically generated C config: don‘t edit\n"

               " * Linux kernel version: %s\n"

               " * %s"

               " */\n"

               "#define AUTOCONF_INCLUDED\n",

               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

时间: 2024-10-26 02:20:07

u-boot make过程分析的相关文章

spring boot environment加载过程分析

environment是在printBanner之前就初始化好了, 更在context创建之前, 已经加载application-xxxx.properties, System.properties, System.environment ... 也可以自己监听应用启动 SpringApplicationRunListener事件, 完成自己的独特的配置加载方案 启动后调用listener.finished() 打印一些启动后的信息 prepareEnvironment()源码如下 1 priv

国内某公有云 linux云主机开机初始化过程分析和他的镜像制作过程

最近学习了国内某公有云的linux云主机启动之后,在镜像内部的初始化过程,分享出来,仅供参看. 一.开机过程 可以看到开机时候按照数字顺序执行了一连串的脚本,其中也提示的该公有云厂商的名字的ucloud,最后一条显示做了清理工作.进系统一看 果然找不到这些脚本了. 二.进单用户模式找出这些脚本 想让开机的时候不让最后一步 999-clwanup.sh执行的办法很多,我采取的的办法是单用户模式,简单上个图,具体方法大家谷歌下. 成功进入单用户模式,并复制他的初始化脚本 三 初始化过程分析 (一)

ActivityManagerService启动过程分析

ActivityManagerService启动过程 一 从Systemserver到AMS zygote-> systemserver:java入层口: /** * The main entry point from zygote. */ public static void main(String[] args) { new SystemServer().run(); } 接下来继续看SystemServer run函数执行过程: private void run() { // 准备Syst

OpenWrt启动过程分析+添加自启动脚本【转】

一.OpenWrt启动过程分析 转自: http://www.eehello.com/?post=107 总结一下OpenWrt的启动流程:1.CFE->2.linux->3./etc/preinit->4./sbin/init ->5./etc/inittab ->6./etc/init.d/rcS->7./etc/rc.d/S* ->8. OpenWrt是一个开放的linux平台,主要用于带wifi的无线路由上. 类似于Ubuntu.Red Hat.之类的li

Android运行时ART加载OAT文件的过程分析

在前面一文中,我们介绍了Android运行时ART,它的核心是OAT文件.OAT文件是一种Android私有ELF文件格式,它不仅包含有从DEX文件翻译而来的本地机器指令,还包含有原来的DEX文件内容.这使得我们无需重新编译原有的APK就可以让它正常地在ART里面运行,也就是我们不需要改变原来的APK编程接口.本文我们通过OAT文件的加载过程分析OAT文件的结构,为后面分析ART的工作原理打基础. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! OAT文件

Android系统Recovery工作原理之使用update.zip升级过程分析(一)

通过分析update.zip包在具体Android系统升级的过程,来理解Android系统中Recovery模式服务的工作原理.我们先从update.zip包的制作开始,然后是Android系统的启动模式分析,Recovery工作原理,如何从我们上层开始选择system update到重启到Recovery服务,以及在Recovery服务中具体怎样处理update.zip包升级的,我们的安装脚本updater-script怎样被解析并执行的等一系列问题.分析过程中所用的Android源码是gin

Linux驱动的两种加载方式过程分析

一.概念简述 在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载. 静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用.静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译和下载内核,效率较低.若采用静态加载的驱动较多,会导致内核容量很大,浪费存储空间. 动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行

Android执行时ART载入OAT文件的过程分析

在前面一文中,我们介绍了Android执行时ART,它的核心是OAT文件.OAT文件是一种Android私有ELF文件格式,它不仅包括有从DEX文件翻译而来的本地机器指令.还包括有原来的DEX文件内容.这使得我们无需又一次编译原有的APK就能够让它正常地在ART里面执行.也就是我们不须要改变原来的APK编程接口. 本文我们通过OAT文件的载入过程分析OAT文件的结构,为后面分析ART的工作原理打基础. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! <An

Android系统Recovery工作原理之使用update.zip升级过程分析(六)---Recovery服务流程细节【转】

本文转载自:http://blog.csdn.net/mu0206mu/article/details/7465439  Android系统Recovery工作原理之使用update.zip升级过程分析(六)---Recovery服务流程细节            Recovery服务毫无疑问是Recovery启动模式中最核心的部分.它完成Recovery模式所有的工作.Recovery程序对应的源码文件位于:/gingerbread0919/bootable/recovery/recovery

Linux内核的启动过程分析

秦鼎涛 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验目的及要求: 使用gdb跟踪调试内核从start_kernel到init进程启动 详细分析从start_kernel到init进程启动的过程并结合实验截图撰写一篇署名博客,并在博客文章中注明“真实姓名(与最后申请证书的姓名务必一致) + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.1