2014-10 u-boot make过程分析
2014-12-27 23:34
344 查看
/**
******************************************************************************
* @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
原因请见另一篇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 这里执行:
这时,我们来到 scripts/kconfig/Makefile 文件里。在该文件的 32-34 行看到:
从上面看到,silentoldconfig 目标需要依赖 conf 这个程序,该程序也在 scripts/kconfig 目录下生成。
$<-s$(Kconfig) 该条命令相当于 conf -s $(Kconfig) ,这里 $(Kconfig) 是位于不同平台目录下的 Kconfig 文件,比如在 x86 平台就是 arch/x86/Kconfig 。
conf 程序的源代码的主函数在同目录的 conf.c 文件中,在 main() 函数中看到:
所以,在使用 s 参数时,sync_kconfig 这个变量会为 1 。同样在 main() 函数还看到:
上面代码中,如果我们从未配置过内核,那么就会打印出错误信息,然后退出。这里假设已经配置过内核,并生成了 .config 文件,那么在 main() 函数中会来到:
由于使用 s 选项,则 input_mode 为 ask_silent,所以这里会执行 conf_read(NULL);函数。
conf_read(NULL);函数用来读取 .config 文件。读取的各种相关内容主要存放在一个 struct symbol 结构链表里,而各个结构的相关指针则放在一个 symbol_hash[] 的数组中,对于数组中元素的寻找通过 fnv32 哈希算法进行定位。
最后会来到 conf.c 中的底部:
实际上也只有当处理 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 文件中可以看到:
可以看到 auto.conf 文件中的内容依赖于 $(deps_config) 变量里定义的东西,这些东西基本上是各个目录下的 Kconfig 以及其它一些相关文件。
auto.config 和 .config 的差别是在 auto.config 里去掉了 .config 中的注释项目以及空格行,其它的都一样。
仍然在 conf_write_autoconf() 里,分别建立了 .tmpconfig,.tmpconfig_tristate 和 .tmpconfig.h 这 3 个临时文件:
然后将文件头的注释部分分别写入到这几个临时文件中:
接着在 for_all_symbols(i,sym) 这个循环里(是一个宏)里将相关内容分别写入到这几个文件中。
在最后一段代码中,将这几个临时文件进行改名:
上面代码中的 conf_get_autoconfig_name() 实现为:
从上面可以看到,分别生成了以下几个文件:
引用
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
******************************************************************************
* @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
原因请见另一篇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 $@ |
1 2 3 | silentoldconfig: $(obj) /conf $(Q) mkdir -p include /generated $<-s $(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 ; ... ... |
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); } } |
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 ; ... ... |
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.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)); |
在最后一段代码中,将这几个临时文件进行改名:
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; |
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
相关文章推荐
- 2014-10 u-boot make xxx_defconfig 过程分析
- u-boot 2014-10 Makefile 配置过程分析
- 第二人生的源码分析(10)登录授权的实现过程
- nginx源码分析(10)-启动过程分析
- 第二人生的源码分析(10)登录授权的实现过程
- Liferay 启动过程分析10-初始化站点设置
- OpenStack建立实例完整过程源码详细分析(10)
- 黑马程序员--10.网络编程--08.【C_S常见的“双卡”现象和解决--TCP复制文本文件示例II】【阻塞式循环的分析过程 ---总结】
- c语言学习笔记(10)编译器编译过程分析
- 蔡军生先生第二人生的源码分析(10)登录授权的实现过程
- nginx源码分析(10)-启动过程分析
- 2014-10 u-boot 顶层config.mk分析
- 10 uboot的配置及编译过程分析
- 二、uboot的配置过程分析 (2011-03-10 19:41)
- 第二人生的源码分析(10)登录授权的实现过程
- Android系统的开机画面显示过程分析(10)
- hbase 原代码分析 (10) region 创建过程
- 关于嵌入式Linux系统的启动(很好的嵌入式linux启动过程分析)
- OPENH323中呼叫建立过程的跟踪与分析(六)----快速隧道方式之被叫端
- OPENH323中呼叫建立过程的跟踪与分析(三)----快速启动方式之主叫端