Linux Kernel Makefile解析(一)

时间:2022-04-14 18:19:41

闲置了这么久的博客,也要拾起来了(为自己的懒惰感到羞愧)。最近一段时间一直和linux的核心代码打交道,也积累了一些经验,现在想把自己对linux的理解写下来,也欢迎朋友指正。

我认为对linux代码的阅读首先要读懂她的Makefile,Makefile是代码的外部框架,熟悉她会让我们对Linux的代码层次有一定的了解,这对我们深读代码很有帮助。工作过程中接手一个新项目的维护工作时,我也会首先读她的Makefile,因为这在一定程序上代表了项目开发者的架构思路,这也是我要学习的地方。

好了,现在我们开始我们的代码之旅。。。

编译内核首先要执行make menuconfig,那我们就从这条命令开始。(P.S. 内核版本 3.8.0)

1. Makefile变量的初始化

KBUILD_EXTMOD 为空,因为命令行中没有”M= xxx“,KBUILD_SRC为空,因为没有“O = xxx“。


no-dot-config-targets := clean mrproper distclean \
             cscope gtags TAGS tags help %docs check% coccicheck \
             $(version_h) headers_% archheaders archscripts \
            no-dot-config-targets kernelversion %src-pkg

config-targets := 0
mixed-targets  := 0
dot-config     := 1

ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
    ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
        # MAKECMDGOALS 中仅包含no-dot-config-targets提供的命令
        dot-config := 0
    endif
endif

ifeq ($(KBUILD_EXTMOD),)
        ifneq ($(filter config %config,$(MAKECMDGOALS)),)
        # MAKECMDGOALS 包含config
                config-targets := 1
                ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)
                # MAKECMDGOALS 还包含其他命令
                        mixed-targets := 1
                endif

endif

由上面代码可以得出如下几个变量的值:
config-targets = 1
mixed-targets = 0
dot-config = 1

2. 确定执行规则

根据前面得到的几个变量,那么就可以确定执行Makefile line487~505的代码了。

include $(srctree)/arch/$(SRCARCH)/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG

config: scripts_basic outputmakefile FORCE
    $(Q)mkdir -p include/linux include/config
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

%config: scripts_basic outputmakefile FORCE
    $(Q)mkdir -p include/linux include/config
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

我们要先执行依赖规则 scripts_basic 和 outputmakefile,而后执行target。查找代码可以确认依赖规则如下:

# Makefile line 418
scripts_basic:
    $(Q)$(MAKE) $(build)=scripts/basic
    $(Q)rm -f .tmp_quiet_recordmcount

# Makefile line 429
outputmakefile:
ifneq ($(KBUILD_SRC),) # 不执行
    $(Q)ln -fsn $(srctree) source
    $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
        $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

3. 执行依赖规则

  • scripts_basic
    $(Q)$(MAKE) $(build)=scripts/basic
    build定义在scripts/Kbuild.include中,含义如下build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
    那么这行代码展开即是make -f scripts/Makefile.build obj=scripts/basic,跳到scripts/Makefile.build中执行,条件是obj=scripts/basic

    那我们把目光转向scripts/Makefile.build

    src := $(obj) 
    
    kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
    kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
    include $(kbuild-file)

    查找scripts/basic目录下所有的Kbuild或者Makefile,include 它们,然后编译其中的小模块,在这里编译出可执行程序fixdep.

  • outputmakefile
    无执行代码。

4. 执行目标规则

  • $(Q)mkdir -p include/linux include/config # 创建目录
    $(Q)$(MAKE) $(build)=scripts/kconfig $@
    命令展开为
    make -f scripts/Makefile.build obj=scripts/kconfig menuconfig

  • 跳到scripts/Makefile.build中执行,条件是obj=scripts/kconfig,目标是menuconfig。
    继续解析,执行执行scripts/kconfig中的Makefile,目标规则menuconfig。
    menuconfig: $(obj)/mconf
    $< $(Kconfig)

  • 其中$(obj)/mconf的创建是Kernel Makefile中最基础也是非常精彩的部分,linux中大部分子模块均是按照这样的方式组织的。
    在scripts/kconfig/Makefile中有如下定义:

    mconf-objs := mconf.o zconf.tab.o $(lxdialog)
    lxdialog := lxdialog/checklist.o lxdialog/util.o lxdialog/inputbox.o
    lxdialog += lxdialog/textbox.o lxdialog/yesno.o lxdialog/menubox.o
    hostprogs-y += mconf

    注意这里的mconf-objs和hostprogs-y
    进入scripts/Makefile.host

    __hostprogs := $(sort $(hostprogs-y) $(hostprogs-m))
    host-cmulti := $(foreach m,$(__hostprogs),\
    $(if $($(m)-cxxobjs),,$(if $($(m)-objs),$(m))))
    host-cobjs := $(sort $(foreach m,$(__hostprogs),$($(m)-objs)))
    __hostprogs := $(addprefix $(obj)/,$(__hostprogs))
    host-cmulti := $(addprefix $(obj)/,$(host-cmulti))
    host-cobjs := $(addprefix $(obj)/,$(host-cobjs))

    这里
    __hostprogs := scripts/kconfig/mconf
    host-cmulti := scripts/kconfig/mconf
    host-cobjs := mconf.o zconf.tab.o lxdialog/checklist.o lxdialog/util.o lxdialog/inputbox.o lxdialog/textbox.o lxdialog/yesno.o lxdialog/menubox.o(不用关心这些.o文件是什么,我们只关心makefile的执行流程,这里在每个前面要加上前缀$(obj),即”scripts/kconfig/”)

  • 通过如下代码编译成可执行文件

    cmd_host-cmulti = $(HOSTCC) $(HOSTLDFLAGS) -o $@ \
    $(addprefix $(obj)/,$($(@F)-objs)) \
    $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F))
    $(host-cmulti): $(obj)/%: $(host-cobjs) $(host-cshlib) FORCE
    $(call if_changed,host-cmulti)

    上面第二个规则匹配目标mconf,而$(host-cobjs)的匹配规则如下

    cmd_host-cobjs = $(HOSTCC) $(hostc_flags) -c -o $@ $<
    $(host-cobjs): $(obj)/%.o: $(src)/%.c FORCE
    $(call if_changed_dep,host-cobjs)

    实质上是将.c 文件编译成 .o文件,而后调用cmd_host-cobjs生成$(host-cobjs)各个目标文件。返回$(host-cmulti)规则,调用cmd_host-cmulti生成最终的可执行文件mconf,叹为观止啊。

  • 最后返回最初的menuconfig规则,现在只剩下最后一条命令:$< $(Kconfig),展开为scripts/Kconfig/mconf Kconfig,这是一条shell命令,使用刚编出来的可执行文件mconf来完成make menuconfig的最后步骤,即我们看到的图形化内核配置菜单,至此全部完成。

后面介绍Linux Makefile的其他编译目标,上文中如有不正确的部分欢迎指正。