Linux 内核:Kconfig/kbuild 内部的过程( 二 )


kbuild 指向到不同类型的 makefile:

  • Makefile 位于源代码根目录的顶级 makefile 。
  • .config 是内核配置文件 。
  • arch/$(ARCH)/Makefile 是架构的 makefile,它用于补充顶级 makefile 。
  • scripts/Makefile.* 描述所有的 kbuild makefile 的通用规则 。
  • 最后,大约有 500 个 kbuild makefile 。
顶级 makefile 会将架构 makefile 包含进去,读取 .config 文件,下到子目录,在 scripts/ Makefile.* 中定义的例程的帮助下,在每个组件的 makefile 上调用 make,构建每个中间对象,并将所有的中间对象链接为 vmlinux 。内核文档 Documentation/kbuild/makefiles.txt 描述了这些 makefile 的方方面面 。
作为一个例子,让我们看看如何在 x86-64 上生成 vmlinux:
 
Linux 内核:Kconfig/kbuild 内部的过程

文章插图
vmlinux overview
 
(此插图基于 Richard Y. Steven 的 博客。有过更新,并在作者允许的情况下使用 。)
进入 vmlinux 的所有 .o 文件首先进入它们自己的 built-in.a,它通过变量KBUILD_VMLINUX_INIT、KBUILD_VMLINUX_MAIN、KBUILD_VMLINUX_LIBS 表示,然后被收集到 vmlinux 文件中 。
在下面这个简化的 makefile 代码的帮助下,了解如何在 Linux 内核中实现递归 make:
# In top Makefilevmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) +$(call if_changed,link-vmlinux)# Variable assignmentsvmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)export KBUILD_VMLINUX_LIBS := $(libs-y1)export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.ldsinit-y := init/drivers-y := drivers/ sound/ firmware/net-y := net/libs-y := lib/core-y := usr/virt-y := virt/# Transform to corresponding built-in.ainit-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))# Setup the dependency. vmlinux-deps are all intermediate objects, vmlinux-dirs# are phony targets, so every time comes to this rule, the recipe of vmlinux-dirs# will be executed. Refer "4.6 Phony Targets" of `info make`$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;# Variable vmlinux-dirs is the directory part of each built-in.avmlinux-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)))# The entry of recursive make$(vmlinux-dirs): $(Q)$(MAKE) $(build)=$@ need-builtin=1递归 make 的 配方(recipe)被扩展开是这样的:
make -f scripts/Makefile.build obj=init need-builtin=1这意味着 make 将进入 scripts/Makefile.build 以继续构建每个 built-in.a 。在scripts/link-vmlinux.sh 的帮助下,vmlinux 文件最终位于源根目录下 。
vmlinux 与 bzImage 对比许多 Linux 内核开发人员可能不清楚 vmlinux 和 bzImage 之间的关系 。例如,这是他们在 x86-64 中的关系:
 
Linux 内核:Kconfig/kbuild 内部的过程

文章插图
 
 
源代码根目录下的 vmlinux 被剥离、压缩后,放入 piggy.S,然后与其他对等对象链接到 arch/x86/boot/compressed/vmlinux 。同时,在 arch/x86/boot 下生成一个名为 setup.bin 的文件 。可能有一个可选的第三个文件,它带有重定位信息,具体取决于 CONFIG_X86_NEED_RELOCS 的配置 。
由内核提供的称为 build 的宿主程序将这两个(或三个)部分构建到最终的 bzImage 文件中 。
依赖跟踪kbuild 跟踪三种依赖关系:
  1. 所有必备文件(*.c 和 *.h)
  2. 所有必备文件中使用的 CONFIG_ 选项
  3. 用于编译该目标的命令行依赖项
第一个很容易理解,但第二个和第三个呢? 内核开发人员经常会看到如下代码:
#ifdef CONFIG_SMP__boot_cpu_id = cpu;#endif当 CONFIG_SMP 改变时,这段代码应该重新编译 。编译源文件的命令行也很重要,因为不同的命令行可能会导致不同的目标文件 。
当 .c 文件通过 #include 指令使用头文件时,你需要编写如下规则:
main.o: defs.h recipe...管理大型项目时,需要大量的这些规则;把它们全部写下来会很乏味无聊 。幸运的是,大多数现代 C 编译器都可以通过查看源文件中的 #include 行来为你编写这些规则 。对于 GNU 编译器集合(GCC),只需添加一个命令行参数:-MD depfile
# In scripts/Makefile.libc_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)-include $(srctree)/include/linux/compiler_types.h$(__c_flags) $(modkern_cflags)$(basename_flags) $(modname_flags)


推荐阅读