zoukankan      html  css  js  c++  java
  • 嵌入式Linux驱动学习之路(四)u-boot编译分析

    u-boot编译分析

    在配置完成后,执行make开始编译。这里打开Makefile。

    首先在目标all前有一句话首先检查是否有include/config.mk文件来判断是否成功配置过。

    ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))

    下面分析“make”命令正常执行的过程。

    include/autoconf.mk生成过程

      首先包含头文件autoconf.mk.dep、autoconf.mk。这是与开发板相关的一些宏定义。在Makefile执行过程中,需要根据某些宏定义来确定执行哪些操作。

    all:
    sinclude $(obj)include/autoconf.mk.dep
    sinclude $(obj)include/autoconf.mk
    
    # load ARCH, BOARD, and CPU configuration
    include $(obj)include/config.mk        //配置时生成的
    export	ARCH CPU BOARD VENDOR SOC
    

       (在配置时会删除autoconf.mk.dep和autoconf.mk这两个文件,在执行make后创建它。这两个文件该如何更新?我在GUN make使用手册中发现这句话。

       make 在读入所有 makefile 文件之后,首先将所读取的每个 makefile 作为一个目标,寻找更新它们的规则。如果存在一个更新某一个 makefile 文件明确规则或者隐含规则,就去更新对应的 makefile 文件。完成对所有的 makefile 文件的更新之后,如果之前所读取任何一个 makefile 文件被更新,那么 make 就清除本次执行的状态重新读取一遍所有的 makefile 文件(此过程中,同样在读取完成以后也会去试图更新所有的已经读取的 makefile 文件,但是一般这些文件不会再次被重建,因为它们在时间戳上 已经是最新的)。读取完成以后再开始解析已经读取的 makefile 文件并开始执行必要 的动作。

    故在包含头文件后,会把头文件作为目标在Makefile寻找相应的规则,执行后会再次包含更新后的规则。)  

    $(obj)include/autoconf.mk.dep: $(obj)include/config.h include/common.h
        @$(XECHO) Generating $@ ; 
        set -e ; 
        : Generate the dependancies ; 
        $(CC) -x c -DDO_DEPS_ONLY -M $(HOSTCFLAGS) $(CPPFLAGS) 
            -MQ $(obj)include/autoconf.mk include/common.h > $@
    
    $(obj)include/autoconf.mk: $(obj)include/config.h
        @$(XECHO) Generating $@ ; 
        set -e ; 
        : Extract the config macros ; 
        $(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | 
            sed -n -f tools/scripts/define2mk.sed > $@.tmp && 
        mv $@.tmp $@

       编译选项-dM的作用是输出 include/common.h中定义的所有宏。然后输出给 tools/scripts/define2mk.sed。而该脚本主要完成了在common.h中查找和处理以CONFIG_开头的宏定义。

    然后包含该include/config.mk 配置 ARCH CPU BOARD VENDOR SOC

    之后判断主机架构和开发板是否相同,若相同则用主机的架构。

    然后包含顶层目录中的config.mk。

    # load other configuration

    include $(TOPDIR)/config.mk

    config.mk文件执行过程。

    首先设置src和obj为空。

     cc-option保存编译选项

    使用FLAGS +$=(call cc-option ,option1, option2)来调用。

    然后是指定交叉工具链。

    CROSS_COMPILE在 arch/$(ARCH)/config.mk 中指定

    包含开发板的相关配置文件。

    指定隐含的编译规则。

    u-boot镜像生成过程

      在顶层目录下的Makefile中有两个all:

      第一个all:没有依赖,也没有命令。后面接的是两条

      sinclude sinclude $(obj)include/autoconf.mk.dep
      sinclude $(obj)include/autoconf.mk  

      第二个all:

      all: $(ALL)

      这个all才是我们真正需要执行的

    要理解这里的all 空命令的作用。必须了解下面几个点

    1. 当makefile中有两个相同目标的时候,会执行后一个目标。因为后一个目标会重载前一个。比如:

      all:

      echo all1

      all:

      echo all2

    执行make 会输出后一个all2

        

        2. 伪目标

        一个使用地方是 当不需要产生一个实际的目标文件的时候(比如只要执行指令)。

        clean:

        rm *.o

        这样是不产生文件。只需要执行指令。如果当前目录没有一个文件叫做clean。执行make clean是正常的 。如果目录下有一个clean文件。那么由于这个clean文件是最新的,因此就不会执行rm *.o操作。因此需要定义一个伪目标。不管目录下是否存在这个目标。都会执行clean的操作rm *.o

        .PHONY clean

        clean:

        rm *.o

        3. 空命令

        可以存在依赖文件,但是没有命令行。比如

        all:

        或者

        all: $(obj)

        空命令的唯一作用是防止make在执行时,试图为重建这个目标去查找隐含命令

        这句话暂时不好理解。先看后面的再说。


        4. makefile的执行规则和include 这个是关键点

        makefile首先被解析。

        遇到include。先去解析include的文件(假设为inc.mk),解析完毕以后再回头来解析原makefile。

        关键点是如果这个inc.mk不存在。make不会退出。而是先提出一个警告,继续处理原makefile的内容。

        等到原makefile的内容处理完毕以后。会尝试使用规则来重建这个inc.mk文件,也就是尝试将这个inc.mk文件当做一个目标来执行。如果不能重建出这个inc.mk。那么报告一个错误,make退出。如果这个inc.mk重建成功了。那么将原makefile的状态重置,重新开始执行这个makefile。

        参考:http://blog.csdn.net/groundhappy/article/details/52708792  

     可以这样理解。第一个all是为了避免将include中包含的Makefile的第一个目标作为终极目标而设置的。主要是第二个all:,这个才是make中执行的

    ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)
    all:        $(ALL)

      下面分析u-boot.bin文件的生成过程。

    $(obj)u-boot.bin:	$(obj)u-boot
    $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
    $(BOARD_SIZE_CHECK)
    

       而u-boot.bin依赖于u-boot。这里的uboot就是ELF格式的u-boot文件

    $(obj)u-boot:    depend 
    $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
    $(GEN_UBOOT)

      下面分析u-boot的各个依赖。

        1)depend依赖。依次进入$(SUBDIRS) $(CPUDIR) $(dir $(LDSCRIPT))表示的子目录中,并执行make _depend命令,生成各个子目录的.depend文件,在.depend文件中列出每个目标文件的依赖文件。

    depend dep:	$(TIMESTAMP_FILE) $(VERSION_FILE) 
    $(obj)include/autoconf.mk 
    $(obj)include/generated/generic-asm-offsets.h
      for dir in $(SUBDIRS) $(CPUDIR) $(dir $(LDSCRIPT)) ; do 
    	$(MAKE) -C $$dir _depend ; done
    

        2)$(SUBDIRS)依赖。

    SUBDIRS	= tools 
    	  examples/standalone 
    	  examples/api
    

     其依赖的规则如下。这表示生成它的方式是执行tool、example/standalone、examples/api目录下的Makefile。   

    $(SUBDIRS):	depend
    		$(MAKE) -C $@ all
    

     3)$(OBJS)依赖,其变量值是$(CPUDIR) $(dir $(LDSCRIPT) 

    $(OBJS):	depend
    		$(MAKE) -C $(CPUDIR) $(if $(REMOTE_BUILD),$@,$(notdir $@))
    

         以上规则表明,$(OBJS)表示的目标文件,都是通过进入$(CPUDIR)目录执行make命令编译得到的。

        4) $(LIBBOARD) 、$(LIBS) 、$(LDSCRIPT) 同上。

    5) $(obj)u-boot.lds依赖。LDSCRIPT定义在arch/arm/config.mk中。board/samsung/$(BOARD)/u-boot.lds   

    $(obj)u-boot.lds: $(LDSCRIPT)
    		$(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@
    

     u-boot.lds内容如下:  

    OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")    //指定输出可执行文件是32位ARM指令,小端模式的ELF格式。
    OUTPUT_ARCH(arm)                                    //指定输出可执行文件的平台为ARM
    ENTRY(_start)                                      //指定程序的入口为_start
    SECTIONS                                        
    {
    	. = 0x00000000;                               //指明目标代码的起始地址从0x0位置开始, ‘.’代表当前位置
    
    	. = ALIGN(4);                                //表示此处4字节对齐
    	.text	:
    	{
    		arch/arm/cpu/armv7/start.o	(.text)            //表示start.o是代码段的第一个.o文件
    		board/samsung/tiny4412/libtiny4412.o (.text)         //这是代码段的其他部分
    		arch/arm/cpu/armv7/exynos/libexynos.o	(.text)
    		*(.text)
    	}
    
    	. = ALIGN(4);
    	.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }     //表示这是只读数据段
    
    	. = ALIGN(4);
    	.data : {                                  //这是数据段
    		*(.data)
    	}
    
    	. = ALIGN(4);
    
    	. = .;
    	__u_boot_cmd_start = .;
    	.u_boot_cmd : { *(.u_boot_cmd) }
    	__u_boot_cmd_end = .;
    
    	. = ALIGN(4);
    
    	.rel.dyn : {
    		__rel_dyn_start = .;
    		*(.rel*)
    		__rel_dyn_end = .;
    	}
    
    	.dynsym : {
    		__dynsym_start = .;
    		*(.dynsym)
    	}
    
    	.bss __rel_dyn_start (OVERLAY) : {
    		__bss_start = .;                            //表示_bss_start标号指向bss段的开始位置
    		*(.bss)                                 //这是bss段
    		 . = ALIGN(4);
    		_end = .;
    	}
    
    	/DISCARD/ : { *(.dynstr*) }
    	/DISCARD/ : { *(.dynamic*) }
    	/DISCARD/ : { *(.plt*) }
    	/DISCARD/ : { *(.interp*) }
    	/DISCARD/ : { *(.gnu*) }
    }
    

    .

  • 相关阅读:
    1.2c#变量和运算符及注释
    1.1c#初识
    约数个数定理
    莫比乌斯反演
    欧几里得/拓展欧几里得
    中国剩余定理【数论】
    欧拉定理/欧拉函数【数论】
    费马小定理【数论】
    同余定理【数论】
    Java class 和public class 区别
  • 原文地址:https://www.cnblogs.com/ynxf/p/5954532.html
Copyright © 2011-2022 走看看