【Linux】内核番外篇(一)——内核编译和链接

万丈高楼平地起,内核的映像再复杂,也是由一个一个的文件编译、组装而成的,只不过其中的门道要比作业和Lab里的技术复杂太多了。本篇将对内核编译作一个简单的探索。

接下来的车速很快,阅读过程中感到不适请先移步老司机带你探索内核编译系统中的相应部分,作者以生动有趣的口吻讲解了下面几乎所有的知识和技巧。本文因此不重复造轮子,侧重于探索得到的结果。

1. 单个文件编译

在命令行输入:

make net/ipv4/af_inet.o

我们将探寻该命令背后的机制。这里面的target不是常量字符串,所以Makefile中一定会有相应的匹配机制。我们找找看%.o

single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.s %.symtypes %/

虽然没找到对应的target,但有了一个变量。继续往下找:

# We cannot build single targets and the others at the same time
ifneq ($(filter $(single-targets), $(MAKECMDGOALS)),)
	single-build := 1
	ifneq ($(filter-out $(single-targets), $(MAKECMDGOALS)),)
		mixed-build := 1
	endif
endif

可以想到,如果传入了编译单个文件的指令,single-build变量会置1。

# Single targets
# ---------------------------------------------------------------------------
# To build individual files in subdirectories, you can do like this:
#
#   make foo/bar/baz.s
#
# The supported suffixes for single-target are listed in 'single-targets'
#
# To build only under specific subdirectories, you can do like this:
#
#   make foo/bar/baz/

ifdef single-build

# .ko is special because modpost is needed
single-ko := $(sort $(filter %.ko, $(MAKECMDGOALS)))
single-no-ko := $(sort $(patsubst %.ko,%.mod, $(MAKECMDGOALS)))

$(single-ko): single_modpost
	@:
$(single-no-ko): descend
	@:

ifeq ($(KBUILD_EXTMOD),)
# For the single build of in-tree modules, use a temporary file to avoid
# the situation of modules_install installing an invalid modules.order.
MODORDER := .modules.tmp
endif

似乎发现华点了,这次我们找到了目标$(single-no-ko),它的依赖是descend

# Handle descending into subdirectories listed in $(build-dirs)
# Preset locale variables to speed up the build process. Limit locale
# tweaks to this spot to avoid wrong language settings when running
# make menuconfig etc.
# Error messages still appears in the original language
PHONY += descend $(build-dirs)
descend: $(build-dirs)
$(build-dirs): prepare
	$(Q)$(MAKE) $(build)=$@ \
	single-build=$(if $(filter-out $@/, $(filter $@/%, $(KBUILD_SINGLE_TARGETS))),1) \
	need-builtin=1 need-modorder=1

下面的语句似乎在做真正的编译,但它太怪了,看不出来到底要干啥。我们在make时从命令行传入KBUILD_VERBOSE=1,发现它对应这条命令:

make -f ./scripts/Makefile.build obj=net single-build=1 need-builtin=1 need-modorder=1

接下来深入scripts/Makefile.build。第一句就是开门见山:

src := $(obj)

既然是Makefile,那它也有待执行的目标。随缘找一下:

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

我们大概知道第一句代码是我们要找的、能把.c变成.o的代码。找找看if_changed_rule函数:

if_changed_rule = $(if $(newer-prereqs)$(cmd-check),$(rule_$(1)),@:)

大概是在满足某某条件之后,求rule_$(1)的值,此例中即为rule_cc_o_c

define rule_cc_o_c
	$(call cmd_and_fixdep,cc_o_c)
	$(call cmd,gen_ksymdeps)
	$(call cmd,checksrc)
	$(call cmd,checkdoc)
	$(call cmd,objtool)
	$(call cmd,modversions_c)
	$(call cmd,record_mcount)
endef

我们按相似的方式展开第一条命令:

cmd_and_fixdep =                                                             \
	$(cmd);                                                              \
	scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\
	rm -f $(depfile)


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

所以实际上调用了cmd_cc_o_cquiet_cmd_cc_o_c

# C (.c) files
# The C file is compiled and updated dependency information is generated.
# (See cmd_cc_o_c + relevant part of rule_cc_o_c)

quiet_cmd_cc_o_c = CC $(quiet_modtag)  $@
      cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<

于是我们就知道为什么编译时会有这么一行输出了:

CC      net/ipv4/af_inet.o

2. vmlinux编译

2.1. 产生目标

先找到vmlinux

# 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)

使用if_changed判断是否改变,有改变则调用回调函数cmd_link-vmlinux执行链接。$<表示第一个依赖,此处是脚本scripts/link-vmlinux.sh

vmlinux-depsvmlinux的一个重要依赖:

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

我们来逐个查看该变量的各个部分:

export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds

KBUILD_LDS是链接脚本的路径。

KBUILD_VMLINUX_OBJS := $(head-y) $(patsubst %/,%/built-in.a, $(core-y))
KBUILD_VMLINUX_OBJS += $(addsuffix built-in.a, $(filter %/, $(libs-y)))
ifdef CONFIG_MODULES
KBUILD_VMLINUX_OBJS += $(patsubst %/, %/lib.a, $(filter %/, $(libs-y)))


endif
KBUILD_VMLINUX_OBJS += $(patsubst %/,%/built-in.a, $(drivers-y))

export KBUILD_VMLINUX_OBJS

KBUILD_VMLINUX_OBJS代表了一些内核object的集合。构成它的很多变量已在主Makefile中定义:

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

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

这样就把体系结构无关的源码全都包含上了。然后,在体系结构相关的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/

最后,它们追加一个后缀,并添加到KBUILD_VMLINUX_OBJS。以arch/x86为例,它表现为arch/x86/built-in.a


ifdef CONFIG_MODULES
KBUILD_VMLINUX_LIBS := $(filter-out %/, $(libs-y))
else
KBUILD_VMLINUX_LIBS := $(patsubst %/,%/lib.a, $(libs-y))
endif

export KBUILD_VMLINUX_LIBS

KBUILD_VMLINUX_LIBS简单点,如果允许加载外部模块,则取值为空。

所以说白了,vmlinux-deps就是定义了许多依赖目标。这些目标由下面的代码实际生成:

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

是不是看到了前一节讲过的目标?没错,之后的步骤就是调用Makefile.build,然后深入各个文件夹找到真正的源代码进行编译!

2.2. 执行链接

深入到scripts/link-vmlinux.sh,这是链接的关键语句:

		objects="--whole-archive			\
			${KBUILD_VMLINUX_OBJS}			\
			--no-whole-archive			\
			--start-group				\
			${KBUILD_VMLINUX_LIBS}			\
			--end-group				\
			${@}"

		${LD} ${KBUILD_LDFLAGS} ${LDFLAGS_vmlinux}	\
			${strip_debug#-Wl,}			\
			-o ${output}				\
			-T ${lds} ${objects}

把刚才的KBUILD_VMLINUX_OBJSKBUILD_VMLINUX_LIBS都链接进去了。怎么样,是不是豁然开朗?

发表评论

您的电子邮箱地址不会被公开。