,目标 all 依赖于根 makefile 后面声明的 vmlinux:
vmlinux 是 linux 内核的静态链接可执行文件格式。脚本 scripts/link-vmlinux.sh 把不同的编译好的子模块链接到一起形成了 vmlinux。
第二个目标是 vmlinux-deps,它的定义如下:
它是由内核代码下的每个顶级目录的 built-in.o 组成的。之后我们还会检查内核所有的目录,kbuild 会编译各个目录下所有的对应 $(obj-y) 的源文件。接着调用 $(LD) -r 把这些文件合并到一个 build-in.o 文件里。此时我们还没有vmlinux-deps,所以目标 vmlinux 现在还不会被构建。对我而言 vmlinux-deps 包含下面的文件:
下一个可以被执行的目标如下:
就像我们看到的,vmlinux-dir 依赖于两部分:prepare 和 scripts。第一个 prepare 定义在内核的根 makefile 中,准备工作分成三个阶段:
第一个 prepare0 展开到 archprepare ,后者又展开到 archheader 和 archscripts,这两个变量定义在 x86_64 相关的 Makefile。让我们看看这个文件。x86_64 特定的 makefile 从变量定义开始,这些变量都是和特定架构的配置文件 (defconfig,等等)有关联。在定义了编译 16-bit 代码的编译选项之后,根据变量 BITS 的值,如果是 32, 汇编代码、链接器、以及其它很多东西(全部的定义都可以在arch/x86/Makefile找到)对应的参数就是 i386,而 64 就对应的是 x86_84。
第一个目标是 makefile 生成的系统调用列表(syscall table)中的 archheaders :
第二个目标是 makefile 里的 archscripts:
我们可以看到 archscripts 是依赖于根 Makefile里的scripts_basic 。首先我们可以看出 scripts_basic 是按照 scripts/basic 的 makefile 执行 make 的:
scripts/basic/Makefile 包含了编译两个主机程序 fixdep 和 bin2 的目标:
第一个工具是 fixdep:用来优化 gcc 生成的依赖列表,然后在重新编译源文件的时候告诉make。第二个工具是 bin2c,它依赖于内核配置选项 CONFIG_BUILD_BIN2C,并且它是一个用来将标准输入接口(LCTT 译注:即 stdin)收到的二进制流通过标准输出接口(即:stdout)转换成 C 头文件的非常小的 C 程序。你可能注意到这里有些奇怪的标志,如 hostprogs-y 等。这个标志用于所有的 kbuild 文件,更多的信息你可以从documentation 获得。在我们这里, hostprogs-y 告诉 kbuild 这里有个名为 fixed 的程序,这个程序会通过和 Makefile 相同目录的 fixdep.c 编译而来。
执行 make 之后,终端的第一个输出就是 kbuild 的结果:
当目标 script_basic 被执行,目标 archscripts 就会 make arch/x86/tools 下的 makefile 和目标 relocs:
包含了重定位 的信息的代码 relocs_32.c 和 relocs_64.c 将会被编译,这可以在make 的输出中看到:
在编译完 relocs.c 之后会检查 version.h:
我们可以在输出看到它:
以及在内核的根 Makefiel 使用 arch/x86/include/generated/asm 的目标 asm-generic 来构建 generic 汇编头文件。在目标 asm-generic 之后,archprepare 就完成了,所以目标 prepare0 会接着被执行,如我上面所写:
注意 build,它是定义在文件 scripts/Kbuild.include,内容是这样的:
或者在我们的例子中,它就是当前源码目录路径:.:
脚本 scripts/Makefile.build 通过参数 obj 给定的目录找到 Kbuild 文件,然后引入 kbuild 文件:
并根据这个构建目标。我们这里 . 包含了生成 kernel/bounds.s 和 arch/x86/kernel/asm-offsets.s 的 Kbuild 文件。在此之后,目标 prepare 就完成了它的工作。 vmlinux-dirs 也依赖于第二个目标 scripts ,它会编译接下来的几个程序:filealias,mk_elfconfig,modpost 等等。之后,scripts/host-programs 就可以开始编译我们的目标 vmlinux-dirs 了。
首先,我们先来理解一下 vmlinux-dirs 都包含了那些东西。在我们的例子中它包含了下列内核目录的路径:
我们可以在内核的根 Makefile 里找到 vmlinux-dirs 的定义:
这里我们借助函数 patsubst 和 filter去掉了每个目录路径里的符号 /,并且把结果放到 vmlinux-dirs 里。所以我们就有了 vmlinux-dirs 里的目录列表,以及下面的代码:
符号 $@ 在这里代表了 vmlinux-dirs,这就表明程序会递归遍历从 vmlinux-dirs 以及它内部的全部目录(依赖于配置),并且在对应的目录下执行 make 命令。我们可以在输出看到结果:
每个目录下的源代码将会被编译并且链接到 built-io.o 里:
好了,所有的 built-in.o 都构建完了,现在我们回到目标 vmlinux 上。你应该还记得,目标 vmlinux 是在内核的根makefile 里。在链接 vmlinux 之前,系统会构建 samples, Documentation 等等,但是如上文所述,我不会在本文描述这些。
你可以看到,调用脚本 scripts/link-vmlinux.sh 的主要目的是把所有的 built-in.o 链接成一个静态可执行文件,和生成 System.map。 最后我们来看看下面的输出:
vmlinux 和System.map 生成在内核源码树根目录下。
这就是全部了,vmlinux 构建好了,下一步就是创建 bzImage.
?
bzImage 就是压缩了的 linux 内核镜像。我们可以在构建了 vmlinux 之后通过执行 make bzImage 获得bzImage。同时我们可以仅仅执行 make 而不带任何参数也可以生成 bzImage ,因为它是在 arch/x86/kernel/Makefile 里预定义的、默认生成的镜像:
让我们看看这个目标,它能帮助我们理解这个镜像是怎么构建的。我已经说过了 bzImage 是被定义在 arch/x86/kernel/Makefile,定义如下:
在这里我们可以看到第一次为 boot 目录执行 make,在我们的例子里是这样的:
现在的主要目标是编译目录 arch/x86/boot 和 arch/x86/boot/compressed 的代码,构建 setup.bin 和 vmlinux.bin,最后用这两个文件生成 bzImage。第一个目标是定义在 arch/x86/boot/Makefile 的 $(obj)/setup.elf:
我们已经在目录 arch/x86/boot 有了链接脚本 setup.ld,和扩展到 boot 目录下全部源代码的变量 SETUP_OBJS 。我们可以看看第一个输出:
下一个源码文件是 arch/x86/boot/header.S,但是我