Linux内核编译流程

Process of the Linux kernel building(Linux内核编译流程)

Introduction(简介)

I won’t tell you how to build and install a custom Linux kernel on your machine. If you need help with this, you can find many resources that will help you do it. Instead, we will learn what occurs when you execute make in the root directory of the Linux kernel source code.

在本文中,我不会告诉你如何在你的机器上编译和安装你自定义的Linux内核。如果你需要这么做,你可以找多许多这样的资源来帮助你完成这样的工作。在这里,我们会学到当你在Linux内核代码根目录里面执行make 命令时会发生什么。

When I started to study the source code of the Linux kernel, the makefile was the first file that I opened. And it was scary :). The makefile contained 1591 lines of code when I wrote this part and the kernel was the 4.2.0-rc3 release.

当我开始学习Linux内核源码的时候, makefile 是我第一个打开的文件,而且它真的很恐怖。当我写这篇文档的时候,Linux内核是4.2.0-rc3版本,而那个 含有1591 行代码。

This makefile is the top makefile in the Linux kernel source code and the kernel building starts here. Yes, it is big, but moreover, if you’ve read the source code of the Linux kernel you may have noted that all directories containing source code has its own makefile. Of course it is not possible to describe how each source file is compiled and linked, so we will only study the standard compilation case. You will not find here building of the kernel’s documentation, cleaning of the kernel source code, tags generation, cross-compilation related stuff, etc… We will start from the make execution with the standard kernel configuration file and will finish with the building of the bzImage.

这里说的makefile指的是Linux内核源码顶层的makefile,内核编译就是从这个makefile开始的。是的,它很庞大,但是,如果你读过内核源码就会发现每个目录下都有自己的makefile。当然描述每个文件的编译链接过程是不现实的,因此我们仅仅研究一个标准的编译例子。在这篇文档中你不会找到如何生成Linux内核文档,清理内核源码,ctags生成,交叉编译(等这些内容。我们会从以标准配置文件执行make 命令开始,到生成bzImage结束。

It would be better if you’re already familiar with the make util, but I will try to describe every piece of code in this part anyway.

如果你熟悉make util更好,但我也会尝试详细描述代码的的方方面面。

So let’s start.
那么,让我们开始吧。

Preparation before the kernel compilation(编译前准备)

There are many things to prepare before the kernel compilation can be started. The main point here is to find and configure
the type of compilation, to parse command line arguments that are passed to make, etc… So let’s dive into the top Makefile of Linux kernel.

在开始编译前,有很多准备工作要做。这些准备工作主要是寻找和配置编译类型、解析传递给make命令的命令行参数等。所以让我们深入研究Linux内核的顶层Makefile

The top Makefile of Linux kernel is responsible for building two major products: vmlinux (the resident kernel image) and the modules (any module files). The Makefile of the Linux kernel starts with the definition of following variables:

Linux 内核顶层的Makefile负责编译两类主要的目标:vmlinux和modules。Makefile从定义下面这些变量开始:

VERSION = 4
PATCHLEVEL = 2
SUBLEVEL = 0
EXTRAVERSION = -rc3
NAME = Hurr durr I'ma sheep

These variables determine the current version of Linux kernel and are used in different places, for example in the forming of the KERNELVERSION variable in the same Makefile:

这些变量定义了当前Linux内核的版本,它们被用在许多不同的地方,例如组成同一Makefile中的变量KERNELVERSION

KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)

After this we can see a couple of ifeq conditions that check some of the parameters passed to make. The Linux kernel makefiles provides a special make help target that prints all available targets and some of the command line arguments that can be passed to make. For example : make V=1 => verbose build. The first ifeq checks whether the V=n option is passed to make:

在这之后,我们可以看到一些ifeq 语句对用于检查传递给make命令的参数。Linux内核的makefiles提供了一个特殊的编译目标make help用语打印出所有可能的编译目标和一些传递给make命令的命令行参数。例如,make V=1 => verbose build. 第一个ifeq 检查是否 V=n 参数被传递给命令 make

ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif

ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif

export quiet Q KBUILD_VERBOSE

If this option is passed to make, we set the KBUILD_VERBOSE variable to the value of V option. Otherwise we set the KBUILD_VERBOSE variable to zero. After this we check the value of KBUILD_VERBOSE variable and set values of the quiet and Q variables depending on the value of KBUILD_VERBOSE variable. The @ symbols suppress the output of command. And if it is present before a command the output will be something like this: CC scripts/mod/empty.o instead of Compiling .... scripts/mod/empty.o. In the end we just export all of these variables.

如果该参数被传递给make,我们设置KBUILD_VERBOSE 变量的值为参数V 的值,否则我们设置变量KBUILD_VERBOSE 的值为0。然后,根据KBUILD_VERBOSE 的值设置变量 quietQ 的值。符号@ 阻止了命令的输出。如果该符号出现在命令的前面,那么该命令的输出将会是这样CC scripts/mod/empty.o 而不是 Compiling .... scripts/mod/empty.o。最后,我们将export出这些变量。

The next ifeq statement checks that O=/dir option was passed to the make. This option allows to locate all output files in the given dir:

接下来的 ifeq 检查是否 O=/dir 选项被传递给 make 命令。 这个选项使得编译所得的文件输出到指定的目录dir 中:

ifeq ($(KBUILD_SRC),)

ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif

ifneq ($(KBUILD_OUTPUT),)
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
                                && /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
     $(error failed to create output directory "$(saved-output)"))

sub-make: FORCE
    $(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \
    -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))

skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

We check the KBUILD_SRC that represents the top directory of the kernel source code and whether it is empty (it is empty when the makefile is executed for the first time). We then set the KBUILD_OUTPUT variable to the value passed with the O option (if this option was passed). In the next step we check this KBUILD_OUTPUT variable and if it is set, we do following things:
* Store the value of KBUILD_OUTPUT in the temporary saved-output variable;
* Try to create the given output directory;
* Check that directory created, in other way print error message;
* If the custom output directory was created successfully, execute make again with the new directory (see the -C option).

我们检查代表Linux内核源码根目录的变量KBUILD_SRC 的值是否为空(当第一次执行makefile的时候该值为空)。接着我们设置变量KBUILD_OUTPUT 的值为通过选项O 传递进来的值。接下来我们判断KBUILD_OUTPUT 值,如果已设置,我们执行下面的操作:
* 将变量 KBUILD_OUTPUT 的值存储在临时变量 saved-output 中;
* 尝试创建指定的输出目录;
* 判断制定的输出目录是否已创建,否者输出错误信息;
* 如果指定的输出目录已创建,使用新的目录再次执行make命令,(参考-C 选项)

The next ifeq statements check that the C or M options passed to make:

下一个ifeq 语句检查是否选项C 或者 M 被传递给命令 make

ifeq ("$(origin C)", "command line")
  KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
  KBUILD_CHECKSRC = 0
endif

ifeq ("$(origin M)", "command line")
  KBUILD_EXTMOD := $(M)
endif

The C option tells the makefile that we need to check all c source code with a tool provided by the $CHECK environment variable, by default it is sparse. The second M option provides build for the external modules (will not see this case in this part). We also check whether the KBUILD_SRC variable is set, and if it isn’t, we set the srctree variable to .:

选项 C 告诉 makefile 我们需要使用环境变量$CHECK来检查所有的 c 代码, 默认的工具时 sparse. 第二个 M 选项 提供了对外部模块的编译 (本例中不会涉及到). 我们也会检查变量 KBUILD_SRC 的值是否已设置, 如果没有, 我们设置变量 srctree 为代表当前目录的 .:

ifeq ($(KBUILD_SRC),)
        srctree := .
endif

objtree := .
src     := $(srctree)
obj     := $(objtree)

export srctree objtree VPATH

That tells Makefile that the kernel source tree will be in the current directory where make was executed. We then set objtree and other variables to this directory and export them. The next step is to get value for the SUBARCH variable that represents what the underlying architecture is:

这告诉 Makefilemake命令执行时,内核代码树就在当前目录下。 然后我们设置 objtree 和其它变量的值为这个目录,并export它们。下一步是获取代表目标平台架构的变量 SUBARCH :

SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \
                  -e s/sun4u/sparc64/ \
                  -e s/arm.*/arm/ -e s/sa110/arm/ \
                  -e s/s390x/s390/ -e s/parisc64/parisc/ \
                  -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
                  -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ )

As you can see, it executes the uname util that prints information about machine, operating system and architecture. As it gets the output of uname, it parses the ouput and assigns the result to the SUBARCH variable. Now that we have SUBARCH, we set the SRCARCH variable that provides the directory of the certain architecture and hfr-arch that provides the directory for the header files:

如你所见,通过执行命令 uname打印出机器、操作系统和架构的信息。获取uname命令的输出后,解析其输出,并赋值给变量SUBARCH。有了SUBARCH之后,我们设置提供对应架构目录的变量SRCARCH和提供头文件目录的变量hfr-arch:

ifeq ($(ARCH),i386)
        SRCARCH := x86
endif
ifeq ($(ARCH),x86_64)
        SRCARCH := x86
endif

hdr-arch  := $(SRCARCH)

Note ARCH is an alias for SUBARCH. In the next step we set the KCONFIG_CONFIG variable that represents path to the kernel configuration file and if it was not set before, it is set to .config by default:

变量ARCH是变量SUBARCH的别名。在下一步中,我们设置变量KCONFIG_CONFIG表示内核配置文件的路径。该变量如果在此之前没有设置,则设置为默认值.config:

KCONFIG_CONFIG  ?= .config
export KCONFIG_CONFIG

and the shell that will be used during kernel compilation:

并设置在内核编译过程中所用到的 shell:

CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
      else if [ -x /bin/bash ]; then echo /bin/bash; \
      else echo sh; fi ; fi)

The next set of variables are related to the compilers used during Linux kernel compilation. We set the host compilers for the c and c++ and the flags to be used with them:

下面要设置的变量与Linux内核编译过程中的编译器相关。我们设置cc++ 源码的主机端(host)编译器和编译flag:

HOSTCC       = gcc
HOSTCXX      = g++
HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
HOSTCXXFLAGS = -O2

Next we get to the CC variable that represents compiler too, so why do we need the HOST* variables? CC is the target compiler that will be used during kernel compilation, but HOSTCC will be used during compilation of the set of the host programs (we will see it soon). After this we can see the definition of KBUILD_MODULES and KBUILD_BUILTIN variables that are used to determine what to compile (kernel, modules or both):

我们接下来遇到的变量CC也和编译器相关,那么我们为啥需要HOST*变量呢?CC 是内核编译过程中的目标编译器,而HOSTCC在编译过程中会编译一些主机程序(后面我们会看到它们)。在这之后,我们会看到变量KBUILD_MODULESKBUILD_BUILTIN 的定义,这些变量决定了需要编译那些目标,内核、模块还是两则都是。

KBUILD_MODULES :=
KBUILD_BUILTIN := 1

ifeq ($(MAKECMDGOALS),modules)
  KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif

Here we can see definition of these variables and the value of KBUILD_BUILTIN variable will depend on the CONFIG_MODVERSIONS kernel configuration parameter if we pass only modules to make. The next step is to include the kbuild file.

这里我们看到,如果我们仅仅传递modulesmake命令,这些变量的定义和KBUILD_BUILTIN 的值依赖于CONFIG_MODVERSIONS 内核配置参数。下一步是包含那些kbuild文件.

include scripts/Kbuild.include

The Kbuild or Kernel Build System is the special infrastructure to manage the build of the kernel and its modules. The kbuild files has the same syntax that makefiles do. The scripts/Kbuild.include file provides some generic definitions for the kbuild system. As we included this kbuild files we can see definition of the variables that are related to the different tools that will be used during kernel and modules compilation (like linker, compilers, utils from the binutils, etc…):

Kbuild或者Kernel Build System 是用来控制内核或者模块编译的特殊框架。kbuild 文件和普通makefile有相同的语法。文件scripts/Kbuild.include提供了kbuild 系统的一些公共定义。我们包含了该文件后,可以看到与内核和模块编译过程中所使用的各种工具相关的各种变量定义(如链接器linker、编译器compiler、 binutils中的各种工具等):

AS      = $(CROSS_COMPILE)as
LD      = $(CROSS_COMPILE)ld
CC      = $(CROSS_COMPILE)gcc
CPP     = $(CC) -E
AR      = $(CROSS_COMPILE)ar
NM      = $(CROSS_COMPILE)nm
STRIP       = $(CROSS_COMPILE)strip
OBJCOPY     = $(CROSS_COMPILE)objcopy
OBJDUMP     = $(CROSS_COMPILE)objdump
AWK     = awk
...
...
...

We then define two other variables: USERINCLUDE and LINUXINCLUDE. They contain the paths of the directories with headersc z (public for users in the first case and for kernel in the second case):

接下来我们定义两个变量USERINCLUDELINUXINCLUDE 。他们包含了头文件目录(第一个是用户公用头文件,第二个是内核使用的头文件)。

USERINCLUDE    := \
        -I$(srctree)/arch/$(hdr-arch)/include/uapi \
        -Iarch/$(hdr-arch)/include/generated/uapi \
        -I$(srctree)/include/uapi \
        -Iinclude/generated/uapi \
        -include $(srctree)/include/linux/kconfig.h

LINUXINCLUDE    := \
        -I$(srctree)/arch/$(hdr-arch)/include \
        ...

And the standard flags for the C compiler:
以及标准的C编译器编译flag:

KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
           -fno-strict-aliasing -fno-common \
           -Werror-implicit-function-declaration \
           -Wno-format-security \
           -std=gnu89

It is the not last compiler flags, they can be updated by the other makefiles (for example kbuilds from arch/). After all of these, all variables will be exported to be available in the other makefiles. The following two the RCS_FIND_IGNORE and the RCS_TAR_IGNORE variables will contain files that will be ignored in the version control system:
这还不是最终的编译flags,它们可能在编译过程中被其它的makefiles修改(例如,arch/目录下的kbuilds)。在这之后,所有的变量都被导出。接下来的两个变量RCS_FIND_IGNORERCS_TAR_IGNORE包含了那些会被版本控制系统忽略的文件:

export RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o    \
              -name CVS -o -name .pc -o -name .hg -o -name .git \) \
              -prune -o
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \
             --exclude CVS --exclude .pc --exclude .hg --exclude .git

That’s all. We have finished with the all preparations, next point is the building of vmlinux.
就这样,我们完成了所有的准备工作,下面进入vmlinux的编译。

Directly to the kernel build(进入内核编译)

We have now finished all the preparations, and next step in the main makefile is related to the kernel build. Before this moment, nothing has been printed to the terminal by make. But now the first steps of the compilation are started. We need to go to line 598 of the Linux kernel top makefile and we will find the vmlinux target there:

我们已经完成全部的准备工作,下面进入到与内核编译相关的步骤。在此之前,终端上不会打印出make命令的任何输出。但从现在开始内核编译的第一步。我们跳转到内核源码顶层Makefile的第598行,我们会看到编译目标vmlinux的定义:

all: vmlinux
    include arch/$(SRCARCH)/Makefile

Don’t worry that we have missed many lines in Makefile that are between export RCS_FIND_IGNORE..... and all: vmlinux...... This part of the makefile is responsible for the make *.config targets and as I wrote in the beginning of this part we will see only building of the kernel in a general way.

别担心,我们跳过了从export RCS_FIND_IGNORE.....all: vmlinux.....之间的这么多行。Makefile这部分内容与make *.config相关,就像我在文件开头说的那样,我们仅研究一个通用的编译过程。

The all: target is the default when no target is given on the command line. You can see here that we include architecture specific makefile there (in our case it will be arch/x86/Makefile). From this moment we will continue from this makefile. As we can see all target depends on the vmlinux target that defined a little lower in the top makefile:

all目标是在没有通过命令行传递任何编译目标时的默认编译目标。你可以看到,此时,我们包含了跟架构相关的makefile(在本例中是指arch/x86/Makefile)。现在我们继续研究makefile。我们看到目标all依赖于目标vmlinux, 目标vmlinux定义在顶层makefile下面一点:

vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE

The vmlinux is the Linux kernel in a statically linked executable file format. The scripts/link-vmlinux.sh script links and combines different compiled subsystems into vmlinux. The second target is the vmlinux-deps that defined as:

vmlinux是Linux内核的一种静态链接可执行文件格式。脚本scripts/link-vmlinux.sh链接并将编译后得各子系统打包进vmlinux。
第二个目标vmlinux-deps的定义如下:

vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

and consists from the set of the built-in.o from each top directory of the Linux kernel. Later, when we will go through all directories in the Linux kernel, the Kbuild will compile all the $(obj-y) files. It then calls $(LD) -r to merge these files into one built-in.o file. For this moment we have no vmlinux-deps, so the vmlinux target will not be executed now. For me vmlinux-deps contains following files:

它由LInux内核源码中各顶层目录中的built-in.o组成。后面当我们遍历Linux内核的所有目录时,kbuild会编译$(obj-y)中的源码,然后调用$(LD) -r将这些文件打包成一个built-in.o。现在,我们还没有生成vmlinux-deps,所以vmlinux目标还不会进行编译。对于我来说,vmlinux-deps包含下面这些文件:

arch/x86/kernel/vmlinux.lds arch/x86/kernel/head_64.o
arch/x86/kernel/head64.o    arch/x86/kernel/head.o
init/built-in.o             usr/built-in.o
arch/x86/built-in.o         kernel/built-in.o
mm/built-in.o               fs/built-in.o
ipc/built-in.o              security/built-in.o
crypto/built-in.o           block/built-in.o
lib/lib.a                   arch/x86/lib/lib.a
lib/built-in.o              arch/x86/lib/built-in.o
drivers/built-in.o          sound/built-in.o
firmware/built-in.o         arch/x86/pci/built-in.o
arch/x86/power/built-in.o   arch/x86/video/built-in.o
net/built-in.o

The next target that can be executed is following:
下一个可以编译的目标是:

$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=$@

As we can see vmlinux-dirs depends on two targets: prepare and scripts. prepare is defined in the top Makefile of the Linux kernel and executes three stages of preparations:

我们看到vmlinux-dirs依赖于两个目标preparescriptsprepare定义在顶层的Makefile中,分为3各执行阶段:

prepare: prepare0
prepare0: archprepare FORCE
    $(Q)$(MAKE) $(build)=.
archprepare: archheaders archscripts prepare1 scripts_basic

prepare1: prepare2 $(version_h) include/generated/utsrelease.h \
                   include/config/auto.conf
    $(cmd_crmodverdir)
prepare2: prepare3 outputmakefile asm-generic

The first prepare0 expands to the archprepare that expands to the archheaders and archscripts that defined in the x86_64 specific Makefile.

第一个prepare0archprepare扩展得到。而archprepare又由定义在x86_64指定Makefile中的 archheaders and arch scripts扩展得。

Let’s look on it. The x86_64 specific makefile starts from the definition of the variables that are related to the architecture-specific configs (defconfig, etc…). After this it defines flags for the compiling of the 16-bit code, calculating of the BITS variable that can be 32 for i386 or 64 for the x86_64 flags for the assembly source code, flags for the linker and many many more (all definitions you can find in the arch/x86/Makefile).

让我们看下这个文件。x86_64指定的makefile,以定义一些架构相关变量开始,之后是定义一些编译16-bit代码的flag,计算BITS变量,可以是i386架构的32 或者x86_64架构的 64“,定义编译汇编代码的flags,以及链接用的flags等等。

The first target is archheaders in the makefile generates syscall table:
该makefile中的第一个目标archheaders生成系统调用表:

archheaders:
    $(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all

And the second target is archscripts in this makefile is:
makefile中的第二个目标archscripts定义如下:

archscripts: scripts_basic
    $(Q)$(MAKE) $(build)=arch/x86/tools relocs

We can see that it depends on the scripts_basic target from the top Makefile. At the first we can see the scripts_basic target that executes make for the scripts/basic makefile:
我们可以看到,它依赖于定义在根Makefile中的scripts_basic,在目录scripts/basic中执行make命令生成目标scripts_basic

scripts_basic:
    $(Q)$(MAKE) $(build)=scripts/basic

The scripts/basic/Makefile contains targets for compilation of the two host programs: fixdep and bin2:
scripts/basic/Makefile包含编译生成两个主机端程序fixdepbin2的一些目标:

hostprogs-y := fixdep
hostprogs-$(CONFIG_BUILD_BIN2C)     += bin2c
always      := $(hostprogs-y)

$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep

First program is fixdep - optimizes list of dependencies generated by gcc that tells make when to remake a source code file.
第一个程序fixdep,优化GCC生成的依赖关系表,可以告诉make何时需要重新对源代码文件进行make。

The second program is bin2c, which depends on the value of the CONFIG_BUILD_BIN2C kernel configuration option and is a very little C program that allows to convert a binary on stdin to a C include on stdout.
第二个程序bin2c,它依赖于内核配置选项中CONFIG_BUILD_BIN2C 的值,它是一个非常小得C程序,可以讲标准输入中的二进制转换成标准输出中的C头文件。

You can note here a strange notation: hostprogs-y, etc… This notation is used in the all kbuild files and you can read more about it in the documentation. In our case hostprogs-y tells kbuild that there is one host program named fixdep that will be built from fixdep.c that is located in the same directory where the Makefile is.

你可能会注意到这里有一个奇怪的符号hostprogs-y。这个符号被用在所有的 kbuild文件中,你可以在documentation中找到跟多的描述。在这里,hostprogs-y告诉kbuild这里将有一个主机端程序fixdep由跟Makefile同一目录的fixdep.c生成。

The first output after we execute make in our terminal will be result of this kbuild file:
我们执行make命令后的第一条输出就是由这个kbuild文件生成的:

$ make
  HOSTCC  scripts/basic/fixdep

As script_basic target was executed, the archscripts target will execute make for the arch/x86/tools makefile with the relocs target:
当目标script_basic执行完后,目标archscripts会在目录arch/x86/tools中执行make,编译目标relocs:

$(Q)$(MAKE) $(build)=arch/x86/tools relocs

The relocs_32.c and the relocs_64.c will be compiled that will contain relocation information and we will see it in the make output:
文件relocs_32.crelocs_64.c将会被编译,它们包含有重定向信息,我们可以看到下面的make输出:

  HOSTCC  arch/x86/tools/relocs_32.o
  HOSTCC  arch/x86/tools/relocs_64.o
  HOSTCC  arch/x86/tools/relocs_common.o
  HOSTLD  arch/x86/tools/relocs

There is checking of the version.h after compiling of the relocs.c:
在编译relocs.c后,会检查文件version.h

$(version_h): $(srctree)/Makefile FORCE
    $(call filechk,version.h)
    $(Q)rm -f $(old_version_h)

We can see it in the output:
我们会看到这样的输出:

CHK     include/config/kernel.release

and the building of the generic assembly headers with the asm-generic target from the arch/x86/include/generated/asm that generated in the top Makefile of the Linux kernel.
并执行目标asm-generic,编译目录arch/x86/include/generated/asm中的generic汇编文件。它们是由Linux内核的根Makefile生成的。

After the asm-generic target the archprepare will be done, so the prepare0 target will be executed. As I wrote above:
asm-generic目标之后,archprepare也将完成,目标prepare0将会被编译,像之前提到的那样:

prepare0: archprepare FORCE
    $(Q)$(MAKE) $(build)=.

Note on the build. It defined in the scripts/Kbuild.include and looks like this:
注意那个build,它定义在scripts/Kbuild.include文件中,看起来像这样:

build := -f $(srctree)/scripts/Makefile.build obj

Or in our case it is current source directory - .:
或者在本例中,它就是当前代码目录.

$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=.

The scripts/Makefile.build tries to find the Kbuild file by the given directory via the obj parameter, include this Kbuild files:

脚本scripts/Makefile.build 尝试寻找obj参数指定目录中的Kbuild文件,包含这些文件,并编译这些文件中的目标。

include $(kbuild-file)

and build targets from it.In our case . contains the Kbuild file that generates the kernel/bounds.s and the arch/x86/kernel/asm-offsets.s. After this the prepare target finished to work.

在本例中,当前目录.包含生成kernel/bounds.sarch/x86/kernel/asm-offsets.s的Kbuild文件。在此之后,目标prepare就编译完成了。

The vmlinux-dirs also depends on the second target - scripts that compiles following programs: file2alias, mk_elfconfig, modpost, etc….. After scripts/host-programs compilation our vmlinux-dirs target can be executed.

vmlinux-dirs也依赖与prepare的第二阶段,scripts编译生成了下面这些程序:file2alias, mk_elfconfig, modpost等。在scripts/host-programs编译完之后,目标vmlinux-dirs也可以开始执行了。

First of all let’s try to understand what does vmlinux-dirs contain. For my case it contains paths of the following kernel directories:
首先,让我们试着去了解vmlinux-dirs中包含哪些内容。在本例中,它包含下面这些内核目录的路径:

init usr arch/x86 kernel mm fs ipc security crypto block
drivers sound firmware arch/x86/pci arch/x86/power
arch/x86/video net lib arch/x86/lib

We can find definition of the vmlinux-dirs in the top Makefile of the Linux kernel:
在Linux内核源码根目录的Makefile中,我们可以找到vmlinux-dirs的定义:

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

init-y      := init/
drivers-y   := drivers/ sound/ firmware/
net-y       := net/
libs-y      := lib/
...
...
...

Here we remove the / symbol from the each directory with the help of the patsubst and filter functions and put it to the vmlinux-dirs. So we have list of directories in the vmlinux-dirs and the following code:

这里,我们通过函数patsubstfilter移除了每个目录名中的/,并将其赋值给了vmlinux-dirs,这样我们得到了包含文件夹列表的vmlinux-dirs变量和下面这段代码:

$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=$@

The $@ represents vmlinux-dirs here that means that it will go recursively over all directories from the vmlinux-dirs and its internal directories (depens on configuration) and will execute make in there. We can see it in the output:

符号$@ 代表vmlinux-dirs,这里的含义是,递归的进入vmlinux-dirs中的每个目录及其子目录,并在里面执行make 命令,我们可以看到这样的输出:

  CC      init/main.o
  CHK     include/generated/compile.h
  CC      init/version.o
  CC      init/do_mounts.o
  ...
  CC      arch/x86/crypto/glue_helper.o
  AS      arch/x86/crypto/aes-x86_64-asm_64.o
  CC      arch/x86/crypto/aes_glue.o
  ...
  AS      arch/x86/entry/entry_64.o
  AS      arch/x86/entry/thunk_64.o
  CC      arch/x86/entry/syscall_64.o

Source code in each directory will be compiled and linked to the built-in.o:
每个目录中的源码都会被编译和链接生成built-in.o:

$ find . -name built-in.o
./arch/x86/crypto/built-in.o
./arch/x86/crypto/sha-mb/built-in.o
./arch/x86/net/built-in.o
./init/built-in.o
./usr/built-in.o
...
...

Ok, all buint-in.o(s) built, now we can back to the vmlinux target. As you remember, the vmlinux target is in the top Makefile of the Linux kernel. Before the linking of the vmlinux it builds samples, Documentation, etc… but I will not describe it here as I wrote in the beginning of this part.

好了,当这些buint-in.o(s)都生成后,我们可以回到目标vmlinux上来。你还记得,目标vmlinux是定义在顶层Makefile中的。在链接vmlinux之前,它会编译生成 samples和Documentation这些,但像我之前提到过的,我不会描述它们的编译过程。

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

As you can see main purpose of it is a call of the scripts/link-vmlinux.sh script is linking of the all built-in.o(s) to the one statically linked executable and creation of the System.map. In the end we will see following output:

你可以看到,代码主要是调用脚本scripts/link-vmlinux.sh,将所有的built-in.o(s)静态链接成一个可执行的文件,并生成System.map。最后我们可以看到这样的输出以及Linux源码根目录中的vmlinuxSystem.map

  LINK    vmlinux
  LD      vmlinux.o
  MODPOST vmlinux.o
  GEN     .version
  CHK     include/generated/compile.h
  UPD     include/generated/compile.h
  CC      init/version.o
  LD      init/built-in.o
  KSYM    .tmp_kallsyms1.o
  KSYM    .tmp_kallsyms2.o
  LD      vmlinux
  SORTEX  vmlinux
  SYSMAP  System.map

and vmlinux and System.map in the root of the Linux kernel source tree:

$ ls vmlinux System.map
System.map  vmlinux

That’s all, vmlinux is ready. The next step is creation of the bzImage.

至此,vmlinux已经生成,下一步是生成bzImage

Building bzImage(编译bzImage)

The bzImage file is the compressed Linux kernel image. We can get it by executing make bzImage after vmlinux is built. That, or we can just execute make without any argument and we will get bzImage anyway because it is default image:

bzImage 是压缩过的内核镜像。在生成vmlinux后,可以通过命令make bzImage 获得bzImage。或者干脆直接执行make命令不带任何参数,我们也能得到bzImage,因为它是arch/x86/kernel/Makefile的默认的编译目标:

all: bzImage

in the arch/x86/kernel/Makefile. Let’s look on this target, it will help us to understand how this image builds. As I already said the bzImage target defined in the arch/x86/kernel/Makefile and looks like this:

让我们看看这个目标,这有助于我们理解这个镜像文件时如何生成的。之前我们提到,bzImage目标定义在文件arch/x86/kernel/Makefile中,看起来像这样:

bzImage: vmlinux
    $(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/$@

We can see here, that first of all called make for the boot directory, in our case it is:

到这里,我们看到第一步是在boot目录下执行make,在本例中,boot目录是:

boot := arch/x86/boot

The main goal now is to build the source code in the arch/x86/boot and arch/x86/boot/compressed directories, build setup.bin and vmlinux.bin, and build the bzImage from them in the end. First target in the arch/x86/boot/Makefile is the $(obj)/setup.elf:

现在的主要目标是,编译目录 arch/x86/bootarch/x86/boot/compressed 中的代码,生成 setup.binvmlinux.bin,并且最终通过它们生成bzImage
arch/x86/boot/Makefile中的第一个目标是$(obj)/setup.elf:

$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE
    $(call if_changed,ld)

We already have the setup.ld linker script in the arch/x86/boot directory and the SETUP_OBJS variable that expands to the all source files from the boot directory. We can see first output:

在目录arch/x86/boot中我们已经有了setup.ld,变量SETUP_OBJS展开后得到目录boot中的所有源文件。我们第一次看到这样输出:

  AS      arch/x86/boot/bioscall.o
  CC      arch/x86/boot/cmdline.o
  AS      arch/x86/boot/copy.o
  HOSTCC  arch/x86/boot/mkcpustr
  CPUSTR  arch/x86/boot/cpustr.h
  CC      arch/x86/boot/cpu.o
  CC      arch/x86/boot/cpuflags.o
  CC      arch/x86/boot/cpucheck.o
  CC      arch/x86/boot/early_serial_console.o
  CC      arch/x86/boot/edd.o

The next source file is arch/x86/boot/header.S, but we can’t build it now because this target depends on the following two header files:

下一个源文件是arch/x86/boot/header.S,但目前我们还不能编译它,因为它依赖于下面两个头文件:

$(obj)/header.o: $(obj)/voffset.h $(obj)/zoffset.h

The first is voffset.h generated by the sed script that gets two addresses from the vmlinux with the nm util:
第一个头文件voffset.hsed脚本通过nm工具获取的vmlinux的两个地址,它们是Linux内核的起始和结束地址:

#define VO__end 0xffffffff82ab0000
#define VO__text 0xffffffff81000000

They are start and end of the kernel. The second is zoffset.h depens on the vmlinux target from the arch/x86/boot/compressed/Makefile:

第二个头文件 zoffset.h依赖于arch/x86/boot/compressed/Makefile中的目标vmlinux

$(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE
    $(call if_changed,zoffset)

The $(obj)/compressed/vmlinux target depends on the vmlinux-objs-y that compiles source code files from the arch/x86/boot/compressed directory and generates vmlinux.bin, vmlinux.bin.bz2, and compiles programm - mkpiggy. We can see this in the output:

目标$(obj)/compressed/vmlinux依赖于vmlinux-objs-y,编译arch/x86/boot/compressed中的代码,生成vmlinux.bin, vmlinux.bin.bz2以及程序mkpiggy,我们可以看到下面这些输出:

  LDS     arch/x86/boot/compressed/vmlinux.lds
  AS      arch/x86/boot/compressed/head_64.o
  CC      arch/x86/boot/compressed/misc.o
  CC      arch/x86/boot/compressed/string.o
  CC      arch/x86/boot/compressed/cmdline.o
  OBJCOPY arch/x86/boot/compressed/vmlinux.bin
  BZIP2   arch/x86/boot/compressed/vmlinux.bin.bz2
  HOSTCC  arch/x86/boot/compressed/mkpiggy

Where vmlinux.bin is the vmlinux file with debuging information and comments stripped and the vmlinux.bin.bz2 compressed vmlinux.bin.all + u32 size of vmlinux.bin.all. The vmlinux.bin.all is vmlinux.bin + vmlinux.relocs, where vmlinux.relocs is the vmlinux that was handled by the relocs program (see above). As we got these files, the piggy.S assembly files will be generated with the mkpiggy program and compiled:

这里的vmlinux.binvmlinux文件去除调试信息和注释后生成的。通过程序relocs处理vmlinux后得到vmlinux.relocsvmlinux.bin.all = vmlinux.bin + vmlinux.relocsvmlinux.bin.bz2vmlinux.bin.all压缩后得到的。生成这些文件后,mkpiggy程序会生成汇编文件piggy.S并编译:

  MKPIGGY arch/x86/boot/compressed/piggy.S
  AS      arch/x86/boot/compressed/piggy.o

This assembly files will contain the computed offset from the compressed kernel. After this we can see that zoffset generated:
这个汇编文件中包含了计算得到的压缩后的内核偏移量。之后我们可以看到生成了目标zoffset

  ZOFFSET arch/x86/boot/zoffset.h

As the zoffset.h and the voffset.h are generated, compilation of the source code files from the arch/x86/boot can be continued:

当文件zoffset.hvoffset.h生成后,编译可以继续进行:

  AS      arch/x86/boot/header.o
  CC      arch/x86/boot/main.o
  CC      arch/x86/boot/mca.o
  CC      arch/x86/boot/memory.o
  CC      arch/x86/boot/pm.o
  AS      arch/x86/boot/pmjump.o
  CC      arch/x86/boot/printf.o
  CC      arch/x86/boot/regs.o
  CC      arch/x86/boot/string.o
  CC      arch/x86/boot/tty.o
  CC      arch/x86/boot/video.o
  CC      arch/x86/boot/video-mode.o
  CC      arch/x86/boot/video-vga.o
  CC      arch/x86/boot/video-vesa.o
  CC      arch/x86/boot/video-bios.o

As all source code files will be compiled, they will be linked to the setup.elf:

所有文件都被编译后,它们将会被链接到setup.elf文件中去:

  LD      arch/x86/boot/setup.elf

or(或者):

ld -m elf_x86_64   -T arch/x86/boot/setup.ld arch/x86/boot/a20.o arch/x86/boot/bioscall.o arch/x86/boot/cmdline.o arch/x86/boot/copy.o arch/x86/boot/cpu.o arch/x86/boot/cpuflags.o arch/x86/boot/cpucheck.o arch/x86/boot/early_serial_console.o arch/x86/boot/edd.o arch/x86/boot/header.o arch/x86/boot/main.o arch/x86/boot/mca.o arch/x86/boot/memory.o arch/x86/boot/pm.o arch/x86/boot/pmjump.o arch/x86/boot/printf.o arch/x86/boot/regs.o arch/x86/boot/string.o arch/x86/boot/tty.o arch/x86/boot/video.o arch/x86/boot/video-mode.o arch/x86/boot/version.o arch/x86/boot/video-vga.o arch/x86/boot/video-vesa.o arch/x86/boot/video-bios.o -o arch/x86/boot/setup.elf

The last two things is the creation of the setup.bin that will contain compiled code from the arch/x86/boot/* directory:

最后两项工作是生成包含arch/x86/boot/* 编译后代码的setup.bin

objcopy  -O binary arch/x86/boot/setup.elf arch/x86/boot/setup.bin

and the creation of the vmlinux.bin from the vmlinux:

和从vmlinux 生成 vmlinux.bin

objcopy  -O binary -R .note -R .comment -S arch/x86/boot/compressed/vmlinux arch/x86/boot/vmlinux.bin

In the end we compile host program: arch/x86/boot/tools/build.c that will create our bzImage from the setup.bin and the vmlinux.bin:

接着,我们会编译一个主机端程序arch/x86/boot/tools/build.c 它会通过setup.binvmlinux.bin生成bzImage

arch/x86/boot/tools/build arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin arch/x86/boot/zoffset.h arch/x86/boot/bzImage

Actually the bzImage is the concatenated setup.bin and the vmlinux.bin. In the end we will see the output which is familiar to all who once built the Linux kernel from source:

实际上,bzImagesetup.binvmlinux.bin 的结合体。最后,我们会看到很熟悉的输出,如果你曾从代码开始编译过内核。

Setup is 16268 bytes (padded to 16384 bytes).
System is 4704 kB
CRC 94a88f9a
Kernel: arch/x86/boot/bzImage is ready  (#5)

That’s all.
编译过程至此结束。

Conclusion(总结)

It is the end of this part and here we saw all steps from the execution of the make command to the generation of the bzImage. I know, the Linux kernel makefiles and process of the Linux kernel building may seem confusing at first glance, but it is not so hard. Hope this part will help you understand the process of building the Linux kernel.

至此,本文描述了从开始执行make命令到生成bzImage整个流程。我知道Linux内核makefile和编译步骤咋一看很令人困惑,但其实也没那么难。希望本文能够帮助你理解编译Linux内核的编译流程。

Links(链接)

  • 9
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值