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
原因請見另一篇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 $@


這時,我們來到 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM