跳转至

Linux Kernel Building Process*

Kernel Exploring - 老司机带你探索内核编译系统

内核编译的背后

Tldr

按照 nested kernel 原论文的实现思路,由 nested kernel 进行系统初始化,创建页表并设置为只读后,再执行 outer kernel。而对于 linux kernel,能否采用这种实现方式,需要了解其编译构建过程。

内核编译过很多次,通过几篇内核编译系统相关的文章,了解以下几个方面的内容:

  • 内核的构造,如何从各种复杂的子系统生成最终的单个二进制文件;
  • 如何生成启动镜像 bzImage;
  • 编译链接与内核启动过程的联系。

Kernel Make Tips*

内核编译流程无非就是设置好 .config 之后执行 make -j$(nproc),然后 make install 或使用 make bindeb-pkg 直接生成可安装的 deb 包。

内核 Makefile 中的默认目标 all 在 x86 平台主要包含 vmlinux、modules 和 bzImage。vmlinux 就是源码根目录下的那个,modules 就是各种内核模块,而 bzImage,简单理解就是 elf 加载器以及压缩后的 vmlinux。

make 指定路径可以只编译小部分代码:

$ make M=/path/to/module
$ make /path/to/object.o
$ make /path/to/preprocess.i  # 指定文件预处理后的代码
$ make /path/to/asm.s         # 指定文件的汇编代码

make FDARGS=<kernel-params> FDINITRD=<path/to/initrd> isoimage 可以生成包含内核的可启动 iso 镜像。

内核文件 vmlinux 的生成过程*

首先在 Makefile 中找到 vmlinux 目标的定义:

# Kernel 5.6.0 Makefile

# Final link of vmlinux with optional arch pass after final link
cmd_link-vmlinux =                                                 \
    $(CONFIG_SHELL) $< $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_vmlinux) ;    \
    $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)

vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
    +$(call if_changed,link-vmlinux)

# scripts/Kbuild.include

cmd = @set -e; $(echo-cmd) $(cmd_$(1))

if_changed = $(if $(newer-prereqs)$(cmd-check),                              \
    $(cmd);                                                              \
    printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)

生成 vmlinux 的过程就是将目标文件链接到一起的过程,调用 scripts/Kbuild.include 中定义的 if_changed,满足条件就调用拼接成的 cmd_link-vmlinux,将第一个依赖 scripts/link-vmlinux.sh 传给 shell 执行。

vmlinux 之后的两个依赖,其中 autoksyms_recursive 主要是做一些编译前的准备工作,定义如下:

PHONY += autoksyms_recursive
ifdef CONFIG_TRIM_UNUSED_KSYMS
autoksyms_recursive: descend modules.order
    $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \
      "$(MAKE) -f $(srctree)/Makefile vmlinux"
endif

一般来说 CONFIG_TRIM_UNUSED_KSYMS 是未定义的,所以也不会执行这里的命令。依赖中的 descend 与后面其实是重复的,不太懂为什么要这样写,而 modules.order 目标主要是生成用来记录编译的外部模块的 Modules.order 文件,供 modprobe 命令加载和卸载模块使用。

而另一个依赖 $(vmlinux-deps) 是负责源码编译依赖,定义如下:

# Makefile

include arch/$(SRCARCH)/Makefile

# Externally visible symbols (used by link-vmlinux.sh)
export KBUILD_VMLINUX_OBJS := $(head-y) $(init-y) $(core-y) $(libs-y2) \
                  $(drivers-y) $(net-y) $(virt-y)
export KBUILD_VMLINUX_LIBS := $(libs-y1)
export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds

vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS)

可以看到,vmlinux-deps 其实就是链接文件、目标文件和库文件的集合。$(KBUILD_VMLINUX_OBJS) 就是所有内核子系统的根目标,具体展开如下:

# arch/x86/Makefile

# Kernel objects

head-y := arch/x86/kernel/head_$(BITS).o
head-y += arch/x86/kernel/head$(BITS).o
head-y += arch/x86/kernel/ebda.o
head-y += arch/x86/kernel/platform-quirks.o

libs-y  += arch/x86/lib/

# See arch/x86/Kbuild for content of core part of the kernel
core-y += arch/x86/

# drivers-y are linked after core-y
drivers-$(CONFIG_MATH_EMULATION) += arch/x86/math-emu/
drivers-$(CONFIG_PCI)            += arch/x86/pci/

# must be linked after kernel/
drivers-$(CONFIG_OPROFILE) += arch/x86/oprofile/

# suspend and hibernation support
drivers-$(CONFIG_PM) += arch/x86/power/

drivers-$(CONFIG_FB) += arch/x86/video/

# Makefile 

ifeq ($(KBUILD_EXTMOD),)
# Objects we will link into vmlinux / subdirs we need to visit
init-y      := init/
drivers-y   := drivers/ sound/
drivers-$(CONFIG_SAMPLES) += samples/
net-y       := net/
libs-y      := lib/
core-y      := usr/
virt-y      := virt/
endif # KBUILD_EXTMOD

ifeq ($(KBUILD_EXTMOD),)
core-y      += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/

init-y      := $(patsubst %/, %/built-in.a, $(init-y))
core-y      := $(patsubst %/, %/built-in.a, $(core-y))
drivers-y   := $(patsubst %/, %/built-in.a, $(drivers-y))
net-y       := $(patsubst %/, %/built-in.a, $(net-y))
libs-y1     := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2     := $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))
virt-y      := $(patsubst %/, %/built-in.a, $(virt-y))

vmlinux-deps 中的变量,都依赖于 descend,最终为 vmlinux-dirs 中的值:

# Makefile

# The actual objects are generated when descending,
# make sure no implicit rule kicks in
$(sort $(vmlinux-deps)): descend ;

vmlinux-dirs    := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
             $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
             $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))

build-dirs  := $(vmlinux-dirs)

descend: $(build-dirs)
$(build-dirs): prepare
    $(Q)$(MAKE) $(build)=$@ \
    single-build=$(if $(filter-out $@/, $(filter $@/%, $(single-no-ko))),1) \
    need-builtin=1 need-modorder=1

$(build-dirs) 目标的规则最终执行的命令是 make -f scripts/Makefile.build obj=$@\ 也就是到各个目录中执行编译。

而在所有子系统的根目标都编译完成后,就要把它们链接起来生成 vmlinux,由 scripts/link-vmlinux.sh 完成。

启动镜像 bzImage 的生成过程*

编译完内核后,下一步就是要用 make install 安装内核,而 intall 目标的规则是在 arch/x86/Makefile 中定义的:

# arch/x86/Makefile

boot := arch/x86/boot
install:
    $(Q)$(MAKE) $(build)=$(boot) $@

这里执行的命令是 make -f scripts/Makefile.build obj=arch/x86/boot install,最终执行 arch/x86/boot/Makefile 中的 install 目标:

# arch/x86/boot/Makefile

install:
    sh $(srctree)/$(src)/install.sh $(KERNELRELEASE) $(obj)/bzImage \
        System.map "$(INSTALL_PATH)"

而在安装脚本 arch/x86/boot/install.sh 中,执行 cat $2 > $4/vmlinuz 命令,将 arch/x86/boot/bzImage 复制到安装路径。

bzImage 目标的生成规则定义在 arch/x86/Makefile 中:

# arch/x86/Makefile

# Default kernel to build
all: bzImage

# KBUILD_IMAGE specify target image being built
KBUILD_IMAGE := $(boot)/bzImage

bzImage: vmlinux
ifeq ($(CONFIG_X86_DECODER_SELFTEST),y)
    $(Q)$(MAKE) $(build)=arch/x86/tools posttest
endif
    $(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)
    $(Q)mkdir -p $(objtree)/arch/$(UTS_MACHINE)/boot
    $(Q)ln -fsn ../../x86/boot/bzImage $(objtree)/arch/$(UTS_MACHINE)/boot/$@

执行的命令为 make -f scripts/Makefile.build obj=arch/x86/boot arch/x86/boot/bzImage,即执行 arch/x86/boot/Makefile 中的 arch/x86/boot/bzImage 目标:

# arch/x86/boot/Makefile

quiet_cmd_image = BUILD   $@
silent_redirect_image = >/dev/null
cmd_image = $(obj)/tools/build $(obj)/setup.bin $(obj)/vmlinux.bin \
                   $(obj)/zoffset.h $@ $($(quiet)redirect_image)

$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
    $(call if_changed,image)
    @$(kecho) 'Kernel: $@ is ready' ' (#'`cat .version`')'

到这里就看懂了,就是用 $(obj)tools/build 工具生成 bzImage,依赖的文件是 setup.binvmlinux.bin,构建命令的格式为 build setup vmlinux zoffset.h bzImage

$(obj)tools/build 的代码在 arch/x86/boot/tools/build.c,关键部分如下:

  // open bzImage
  dest = fopen(argv[4], "w");

    // read setup.bin
    file = fopen(argv[1], "r");
    c = fread(buf, 1, sizeof(buf), file);

  // read vmlinux.bin
  fd = open(argv[2], O_RDONLY);
    kernel = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0);

  // write setup.bin to bzImage
  if (fwrite(buf, 1, i, dest) != i)
        die("Writing setup failed");

  // write vmlinux.bin to bzImage
  if (fwrite(kernel, 1, sz, dest) != sz)
        die("Writing kernel failed");

大概的流程就是打开 bzImage,然后将 setup.binvmlinux.bin (经过何种处理后)写入 bzImage

下面继续探究 bzImage 的两个依赖 setup.binvmlinux.bin 的生成过程。

setup.bin 的生成过程*

setup.bin 目标的规则定义在 arch/x86/boot/Makefile 中:

# arch/x86/boot/Makefile

OBJCOPYFLAGS_setup.bin  := -O binary
$(obj)/setup.bin: $(obj)/setup.elf FORCE
    $(call if_changed,objcopy)

if_changed 在前面遇到过,满足条件会调用拼接的 cmd_objcopy,定义在 scripts/Makefile.lib 中:

# scripts/Makefile.lib

# Objcopy
# ---------------------------------------------------------------------------

quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@

也就是说,setup.bin 是由 setup.elf 目标经 objcopy 得到的,而 setup.elf 目标的定义:

# arch/x86/boot/Makefile

setup-y     += a20.o bioscall.o cmdline.o copy.o cpu.o cpuflags.o cpucheck.o
setup-y     += early_serial_console.o edd.o header.o main.o memory.o
setup-y     += pm.o pmjump.o printf.o regs.o string.o tty.o video.o
setup-y     += video-mode.o version.o
setup-$(CONFIG_X86_APM_BOOT) += apm.o

# The link order of the video-*.o modules can matter.  In particular,
# video-vga.o *must* be listed first, followed by video-vesa.o.
# Hardware-specific drivers should follow in the order they should be
# probed, and video-bios.o should typically be last.
setup-y     += video-vga.o
setup-y     += video-vesa.o
setup-y     += video-bios.o

SETUP_OBJS = $(addprefix $(obj)/,$(setup-y))

LDFLAGS_setup.elf   := -m elf_i386 -T
$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE
    $(call if_changed,ld)

总结,setup.bin 是从各种目标文件链接成 setup.elf 再 objcopy 生成的。

vmlinux.bin 的生成过程*

类似地,vmlinux.bin 目标的规则定义:

# arch/x86/boot/Makefile

$(obj)/compressed/vmlinux: FORCE
    $(Q)$(MAKE) $(build)=$(obj)/compressed $@

OBJCOPYFLAGS_vmlinux.bin := -O binary -R .note -R .comment -S
$(obj)/vmlinux.bin: $(obj)/compressed/vmlinux FORCE
    $(call if_changed,objcopy)

同样,vmlinux.bin 也是 objcopy 得到的,只不过它依赖的是 $(obj)/compressed/vmlinux,执行 make -f scripts/Makefile.build obj=arch/x86/boot/compressed arch/x86/boot/compressed/vmlinux,目标的规则定义如下:

# arch/x86/boot/compressed/Makefile

vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/kernel_info.o $(obj)/head_$(BITS).o \
    $(obj)/misc.o $(obj)/string.o $(obj)/cmdline.o $(obj)/error.o \
    $(obj)/piggy.o $(obj)/cpuflags.o

$(obj)/vmlinux: $(vmlinux-objs-y) FORCE
    $(call if_changed,check-and-link-vmlinux)

也就是要将 vmlinux-objs-y 中的所有目标都链接到一起。乍一看并没有发现 vmlinux.bin 的依赖与根目录 vmlinux 有什么关系,那内核代码是如何加载到 bzImage 中?

逐个查看依赖,发现这个 $(obj)/piggy.o 似乎比较神奇,其对应的源码文件 arch/x86/boot/compressed/piggy.S 竟然是编译时才生成的?!!!:

# arch/x86/boot/compressed/Makefile

hostprogs   := mkpiggy

suffix-$(CONFIG_KERNEL_GZIP)    := gz
suffix-$(CONFIG_KERNEL_BZIP2)   := bz2
suffix-$(CONFIG_KERNEL_LZMA)    := lzma
suffix-$(CONFIG_KERNEL_XZ)  := xz
suffix-$(CONFIG_KERNEL_LZO)     := lzo
suffix-$(CONFIG_KERNEL_LZ4)     := lz4

quiet_cmd_mkpiggy = MKPIGGY $@
      cmd_mkpiggy = $(obj)/mkpiggy $< > $@

targets += piggy.S
$(obj)/piggy.S: $(obj)/vmlinux.bin.$(suffix-y) $(obj)/mkpiggy FORCE
    $(call if_changed,mkpiggy)

$(obj)/piggy.S 目标的依赖有 vmlinux.bin.gz(此处以 gz 为例,实际后缀会根据 .config 中指定的内核压缩算法取值)和 $(obj)/mkpiggy。而 mkpiggy 目标被添加到 hostprogs 中。

hostprogs 是内核提供的机制,支持构建在编译阶段使用的可执行文件,将目标程序添加到 hostprogs 中,并添加对目标程序的显式依赖。

因此,在此处,生成 piggy.S 目标时,就会编译 mkpiggy.c,执行 mkpiggy vmlinux.bin.gz > piggy.S 生成 piggy.S 文件,其内容为:

.section ".rodata..compressed","a",@progbits
.globl z_input_len
z_input_len = 11843278
.globl z_output_len
z_output_len = 53909412
.globl input_data, input_data_end
input_data:
.incbin "arch/x86/boot/compressed/vmlinux.bin.gz"
input_data_end:

而其中的 incbin 的作用,就是在汇编中直接包含一个文件,amazing!

那么下面再来看 vmlinux.bin.gz 目标的定义:

# arch/x86/boot/compressed/Makefile

OBJCOPYFLAGS_vmlinux.bin :=  -R .comment -S
$(obj)/vmlinux.bin: vmlinux FORCE
    $(call if_changed,objcopy)

vmlinux.bin.all-y := $(obj)/vmlinux.bin
vmlinux.bin.all-$(CONFIG_X86_NEED_RELOCS) += $(obj)/vmlinux.relocs

$(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE
    $(call if_changed,gzip)

# scripts/Makefile.lib
# Gzip
# ---------------------------------------------------------------------------

quiet_cmd_gzip = GZIP    $@
      cmd_gzip = cat $(real-prereqs) | gzip -n -f -9 > $@

可以看到,vmlinux.bin.gz 就是由根目录的 vmlinux 经 objcopy 得到的 vmlinux.bin 再压缩生成的。

总结,vmlinux.bin 是将压缩后的内核代码 vmlinux.bin.gz 包含在 piggy.S 中,生成目标 piggy.o,再与其他几个目标文件链接到一起,后经 objcopy 得到的。

内核编译架构*

内核源码树的每个层次目录中的 Makefile 中定义好相应的目标,如果有下一层,就“进入”下一层继续编译,主要与 scripts/Makefile.build 文件相关。

参考上面的 $(Q)$(MAKE) $(build)=,其中 build 在 scripts/Kbuild.include 中定义为 build := -f $(srctree)/scripts/Makefile.build obj

要注意的是,在内核编译过程中,当前目录始终是源码根目录,对于不同路径下目标的编译是通过 $(obj) 变量实现的。

重要的规则*

if_changed*

这条规则用于判断依赖是否更新,非常常见,定义在 scripts/Kbuild.include 中:

cmd = @set -e; $(echo-cmd) $(cmd_$(1))

# Execute command if command has changed or prerequisite(s) are updated.
if_changed = $(if $(newer-prereqs)$(cmd-check),                              \
    $(cmd);                                                              \
    printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)

调用 if 函数,条件为真执行第一行,为假执行第二行。

判断条件为 $(newer-prereqs)$(cmd-check),这两个的定义为:

newer-prereqs = $(filter-out $(PHONY),$?)

ifneq ($(KBUILD_NOCMDDEP),1)
# Check if both commands are the same including their order. Result is empty
# string if equal. User may override this check using make KBUILD_NOCMDDEP=1
cmd-check = $(filter-out $(subst $(space),$(space_escape),$(strip $(cmd_$@))), \
                         $(subst $(space),$(space_escape),$(strip $(cmd_$1))))
else
cmd-check = $(if $(strip $(cmd_$@)),,1)
endif

第一个好理解,就是判断是否有比目标更新的依赖。第二个似乎是要比较两个字符串,$1 应该是函数的第一个参数,$@ 是目标,以 $(obj)/bzImage 的规则为例:

$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
    $(call if_changed,image)
    @$(kecho) 'Kernel: $@ is ready' ' (#'`cat .version`')'

也就是要比较 cmd_imagecmd_arch/x86/boot/bzImage 的值,而后者的值,定义在编译生成的 arch/x86/boot/.bzImage.cmd 中(稍后就知道它是怎么生成的了)。

因此,if_changed 在判断如果有依赖更新或命令变化后,会执行后面的命令:

    @set -e; $(echo-cmd) $(cmd_$(1));                                                              \
    printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)

# Name of target with a '.' as filename prefix. foo/bar.o => foo/.bar.o
dot-target = $(dir $@).$(notdir $@)

echo-cmd 就是输出内容,cmd_$1 就是真正要执行的命令,而 $(dot-target).cmd 就是刚才看的保存 cmd_$@ 命令的文件。

而刚生成的 .cmd 文件是如何包含进来的,在 Makefile 中有:

# read saved command lines for existing targets
existing-targets := $(wildcard $(sort $(targets)))

-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd)

include 了所有的目标对应的 .cmd

特殊的目标和变量*

FORCE 目标*

很多目标的依赖中都包含 FORCE,内核使用它来强制执行规则。FORCE 目标定义在 Makefile 中:

PHONY += FORCE
FORCE:

# Declare the contents of the PHONY variable as phony.  We keep that
# information in a variable so we can use it in if_changed and friends.
.PHONY: $(PHONY)

看起来 FORCE 就是一个没有依赖也没有命令的规则。如果将它放在某个目标的依赖中,则这个目标一定会被重新生成。

还是用 $(obj)/bzImage 目标举例:

$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
    $(call if_changed,image)
    @$(kecho) 'Kernel: $@ is ready' ' (#'`cat .version`')'

正常情况下,make bzImage,这条规则会被执行,如果所有的依赖都没有更新,只会输出 Kernel ready。而如果将 FORCE 从依赖中删除,当其他依赖没有更新时,就不会输出提示。

existing_targets*

Makefilescripts/Makefile.build 中都有定义:

# Read all saved command lines and dependencies for the $(targets) we
# may be building above, using $(if_changed{,_dep}). As an
# optimization, we don't need to read them if the target does not
# exist, we will rebuild anyway in that case.

existing-targets := $(wildcard $(sort $(targets)))

-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd)

重要的文件和关系*

Makefile.build*

scripts/Makefile.build 是整个编译中,具体执行动作的核心文件,上面已经多次提到,在 scripts/Kbuild.include 中定义了 build

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

下面就简单看一下 scripts/Makefile.build 文件的内容。

首先是初始化各种变量:

# Init all relevant variables used in kbuild files so
# 1) they have correct type
# 2) they do not inherit any value from the environment
obj-y :=
obj-m :=
...

然后引用相关文件,包括 scripts/Kbuild.include 和目标目录下的 kbuild 和 makefile。引用 scripts/Makefile.lib,包含对 obj-yobj-m 变量的处理,一些编译链接选项变量定义和一些规则。

include scripts/Kbuild.include

# The filename Kbuild has precedence over 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)

include scripts/Makefile.lib

而在 scripts/Makefile.lib 中有关于子目录的相关处理:

# scripts/Makefile.lib

# Handle objects in subdirs
# ---------------------------------------------------------------------------
# o if we encounter foo/ in $(obj-y), replace it by foo/built-in.a
#   and add the directory to the list of dirs to descend into: $(subdir-y)
# o if we encounter foo/ in $(obj-m), remove it from $(obj-m)
#   and add the directory to the list of dirs to descend into: $(subdir-m)
__subdir-y  := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y    += $(__subdir-y)
__subdir-m  := $(patsubst %/,%,$(filter %/, $(obj-m)))
subdir-m    += $(__subdir-m)
ifdef need-builtin
obj-y       := $(patsubst %/, %/built-in.a, $(obj-y))
else
obj-y       := $(filter-out %/, $(obj-y))
endif
obj-m       := $(filter-out %/, $(obj-m))

# Subdirectories we need to descend into
subdir-ym   := $(sort $(subdir-y) $(subdir-m))

scripts/Makefile.bulid 中,会进入到下一层目录编译:

# Descending
# ---------------------------------------------------------------------------

PHONY += $(subdir-ym)
$(subdir-ym):
    $(Q)$(MAKE) $(build)=$@ \
    $(if $(filter $@/, $(KBUILD_SINGLE_TARGETS)),single-build=) \
    need-builtin=$(if $(filter $@/built-in.a, $(subdir-obj-y)),1) \
    need-modorder=$(if $(need-modorder),$(if $(filter $@/modules.order, $(modorder)),1))

回到 scripts/Makefile.build,定义了默认目标 __build,将 make 要做的工作都放在依赖里:

PHONY := __build
__build:


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

后面就是一些编译规则,比如如何编译出一个 .o 文件:

# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
    $(call cmd,force_checksrc)
    $(call if_changed_rule,cc_o_c)

再比如将多个 .o 文件编译成 built-in.a 文件:

#
# Rule to compile a set of .o files into one .a file (without symbol table)
#
ifdef builtin-target

quiet_cmd_ar_builtin = AR      $@
      cmd_ar_builtin = rm -f $@; $(AR) cDPrST $@ $(real-prereqs)

$(builtin-target): $(real-obj-y) FORCE
    $(call if_changed,ar_builtin)

targets += $(builtin-target)
endif # builtin-target

鉴于 scripts/Makefile.build 文件的重要性,如果想调试编译某个目标的过程,可以修改规则,指向一个自定义的 Makefile.build 在其中修改以添加调试输出。


最后更新: March 18, 2023