u-boot编译流程简要分析

0、说明

本文基于U-Boot 2022.01-v2.07版本进行分析。

1、u-boot的编译过程

  • 1.1、参考资料
    u-boot的编译系统介绍可以参考:U-Boot 完全分析与移植,这篇文章。

  • 1.2、编译入口
    在执行make编译u-boot时,由于没有指定目标,因此执行的是顶层Makefile中的第一个目标。在顶层Makefile中,从文件开头往下查找第一个编译目标,会找到如下部分:

    # That's our default target when none is given on the command line
    PHONY := _all
    _all:
    

    会发现这里的_all没有做任何操作,也没有任何依赖,继续往下找,会发现_all会重载:

    # Use make M=dir to specify directory of external module to build
    # Old syntax make ... SUBDIRS=$PWD is still supported
    # Setting the environment variable KBUILD_EXTMOD take precedence
    ifdef SUBDIRS
    KBUILD_EXTMOD ?= $(SUBDIRS)
    endif
    
    ifeq ("$(origin M)", "command line")
    KBUILD_EXTMOD := $(M)
    endif
    
    # If building an external module we do not care about the all: rule
    # but instead _all depend on modules
    PHONY += all
    ifeq ($(KBUILD_EXTMOD),)
    _all: all
    else
    _all: modules
    endif
    

    由于执行make是我们没有指定M=dir,因此这里的KBUILD_EXTMOD就为空,ifeq ($(KBUILD_EXTMOD),)就为真,最终_all:被重载为_all: all,如下:

    # 找到的第一个目标
    PHONY := _all
    _all:
    
    # KBUILD_EXTMOD为空,进行了重载
    PHONY += all
    _all: all
    

    这里_all依赖于all,需要确定all做了哪些事情,继续往下找:

    all: .binman_stamp inputs
    ifeq ($(CONFIG_BINMAN),y)
    	$(call if_changed,binman)
    endif
    

    这里会发现,all依赖于.binman_stampinputs,重点在inputs:

    PHONY += inputs
    inputs: $(INPUTS-y)
    

    继续看INPUTS-y的赋值:

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

    INPUTS-y有多个值,重点关注u-boot.bin,其取决于uboot的配置项。在我的配置项中,其取值如下:

    u-boot.bin: u-boot-dtb.bin FORCE
    	$(call if_changed,copy)
    

    继续看u-boot-dtb.bin,就在它上面:

    u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
    	$(call if_changed,cat)
    

    dts/dt.dtb是来编译设备树的,重点看u-boot-nodtb.bin:

    u-boot-nodtb.bin: u-boot FORCE
    	$(call if_changed,objcopy_uboot)
    	$(BOARD_SIZE_CHECK)
    

    继续看u-boot

    u-boot:	$(u-boot-init) $(u-boot-main) $(u-boot-keep-syms-lto) u-boot.lds FORCE
    	+$(call if_changed,u-boot__)
    ifeq ($(CONFIG_KALLSYMS),y)
    	$(call cmd,smap)
    	$(call cmd,u-boot__) common/system_map.o
    endif
    

    u-boot-keep-syms-lto不用管,u-boot.lds是用于生成链接脚本的,暂且不管。重点看u-boot-initu-boot-main:

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

    head-y的定义在arch\arm\Makefile文件中,用于编译对应目录中的start.s文件:

    head-y := arch/arm/cpu/$(CPU)/start.o
    

    libs-y还在顶层Makefile中,主要是指定需要编译哪些目录,部分内容如下:

    libs-$(CONFIG_API) += api/
    libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
    libs-y += boot/
    libs-y += cmd/
    libs-y += common/
    libs-$(CONFIG_OF_EMBED) += dts/
    libs-y += env/
    libs-y += lib/
    libs-y += fs/
    libs-y += net/
    libs-y += disk/
    libs-y += drivers/
    libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/
    libs-$(CONFIG_SYS_FSL_MMDC) += drivers/ddr/fsl/
    libs-$(CONFIG_$(SPL_)ALTERA_SDRAM) += drivers/ddr/altera/
    libs-y += drivers/usb/cdns3/
    libs-y += drivers/usb/dwc3/
    libs-y += drivers/usb/common/
    libs-y += drivers/usb/emul/
    libs-y += drivers/usb/eth/
    libs-$(CONFIG_USB_DEVICE) += drivers/usb/gadget/
    libs-$(CONFIG_USB_GADGET) += drivers/usb/gadget/
    libs-$(CONFIG_USB_GADGET) += drivers/usb/gadget/udc/
    libs-y += drivers/usb/host/
    libs-y += drivers/usb/mtu3/
    libs-y += drivers/usb/musb/
    libs-y += drivers/usb/musb-new/
    libs-y += drivers/usb/phy/
    libs-y += drivers/usb/ulpi/
    ifdef CONFIG_POST
    libs-y += post/
    endif
    libs-$(CONFIG_UNIT_TEST) += test/
    libs-$(CONFIG_UT_ENV) += test/env/
    libs-$(CONFIG_UT_OPTEE) += test/optee/
    libs-$(CONFIG_UT_OVERLAY) += test/overlay/
    libs-$(CONFIG_CMD_HTTPD) += httpd/
    
    libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)
    
    libs-y := $(sort $(libs-y))
    
    u-boot-dirs	:= $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples
    
    u-boot-alldirs	:= $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-))))
    
    libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
    

    至此,需要编译哪些文件/目录已经清楚了,接下来需要看如何编译了。

  • 1.3、编译过程
    需要编译的文件已经准备好了,如何编译需要从此处开始:

    $(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) ;
    

    这里首先会对u-boot-initu-boot-main进行排序,然后它们的每一个值都依赖于u-boot-dirs:

    PHONY += $(u-boot-dirs)
    $(u-boot-dirs): prepare scripts
    	$(Q)$(MAKE) $(build)=$@
    

    $(Q)$(MAKE) $(build)=$@就是具体的编译动作了,其中build定义在scripts\Kbuild.include中,这个文件顶层Makefile有包含,build如下:

    ###
    # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
    # Usage:
    # $(Q)$(MAKE) $(build)=dir
    build := -f $(srctree)/scripts/Makefile.build obj
    

    最终变成了:

    PHONY += $(u-boot-dirs)
    $(u-boot-dirs): prepare scripts
    	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=$@
    

    具体的编译是由scripts/Makefile.build里完成的。

  • 1.3、scripts/Makefile.build编译分析
    scripts/Makefile.build中的第一个目标为:

    PHONY := __build
    __build:
    
    __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) $(subdir-ym) $(always)
    	@:
    

    builtin-target的值:

    ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)
    builtin-target := $(obj)/built-in.o
    endif
    

    lib-target的值:

    ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),)
    lib-target := $(obj)/lib.a
    endif
    

    重点看subdir-ym这个依赖:
    subdir-ym的值定义在scripts\Makefile.lib中:

    __subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
    subdir-y	+= $(__subdir-y)
    __subdir-m	:= $(patsubst %/,%,$(filter %/, $(obj-m)))
    subdir-m	+= $(__subdir-m)
    
    subdir-ym	:= $(sort $(subdir-y) $(subdir-m))
    

    这里可以看出subdir-ym的值是取出子目录的Makefile中定义的obj-y指定的.o文件。
    scripts/Makefile.buildsubdir-ym的命令如下:

    PHONY += $(subdir-ym)
    $(subdir-ym):
    	$(Q)$(MAKE) $(build)=$@
    

    也即:

    PHONY += $(subdir-ym)
    $(subdir-ym):
    	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=$@
    

    也就是递归编译子目录中的文件。
    这里需要注意子目录的Makefile文件是需要包含的,它的包含在scripts\Makefile.build中定义:

    # 得到子目录路径
    prefix := tpl
    src := $(patsubst $(prefix)/%,%,$(obj))
    ifeq ($(obj),$(src))
    prefix := spl
    src := $(patsubst $(prefix)/%,%,$(obj))
    ifeq ($(obj),$(src))
    prefix := .
    endif
    endif
    
    # 包含子目录Makefile
    kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
    kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
    include $(kbuild-file)
    

    至此,回到本小节的最开始处,__build的依赖就是由一堆.o.a文件组成。

    那么.c转为.o.a的规则呢?
    scripts\Makefile.build中有定义:

    $(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
    	$(call cmd,force_checksrc)
    	$(call if_changed_rule,cc_o_c)
    

    最终会执行rule_cc_o_c,定义如下:

    define rule_cc_o_c
    $(call echo-cmd,checksrc) $(cmd_checksrc)			  \
    $(call echo-cmd,cc_o_c) $(cmd_cc_o_c);				  \
    $(cmd_modversions)						  \
    $(call echo-cmd,record_mcount)					  \
    $(cmd_record_mcount)						  \
    scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' >    \
                                                  $(dot-target).tmp;  \
    rm -f $(depfile);						  \
    mv -f $(dot-target).tmp $(dot-target).cmd
    endef
    

    然后rule_cc_o_c会执行cmd_cc_o_c:

    cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
    
  • 1.4、总结

    # 默认目标:
    PHONY := _all
    _all:
    
    # _all重载
    PHONY += all
    _all: all
    
    # 依赖于all
    all: .binman_stamp inputs
    ifeq ($(CONFIG_BINMAN),y)
    	$(call if_changed,binman)
    endif
    
    # 第一步:
    u-boot:	$(u-boot-init) $(u-boot-main) $(u-boot-keep-syms-lto) u-boot.lds FORCE
    	+$(call if_changed,u-boot__)
    ifeq ($(CONFIG_KALLSYMS),y)
    	$(call cmd,smap)
    	$(call cmd,u-boot__) common/system_map.o
    endif
    
    # 第二步:
    $(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) ;
    
    # 第三步:
    PHONY += $(u-boot-dirs)
    $(u-boot-dirs): prepare scripts
    	$(Q)$(MAKE) $(build)=$@
    
    # 第四步
    # build定义在scripts\Kbuild.include中,开始递归目录编译
    build := -f $(srctree)/scripts/Makefile.build obj
    

2、u-boot启动流程简要分析

3、u-boot增加自定义命令

4、u-boot的DM驱动模型

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值