TranslateProject/sources/tech/20150728 Process of the Linux kernel building.md
2015-08-25 09:34:30 +08:00

44 KiB
Raw Blame History

Translating by Ezio

如何构建Linux 内核

介绍

我不会告诉你怎么在自己的电脑上去构建、安装一个定制化的Linux 内核,这样的资料 太多了,它们会对你有帮助。本文会告诉你当你在内核源码路径里敲下make 时会发生什么。当我刚刚开始学习内核代码时,Makefile 是我打开的第一个文件,这个文件看起来真令人害怕 :)。那时候这个Makefile 还只包含了1591 行代码,当我开始写本文是,这个Makefile 已经是第三个候选版本了。

这个makefile 是Linux 内核代码的根makefile 内核构建就始于此处。是的它的内容很多但是如果你已经读过内核源代码你就会发现每个包含代码的目录都有一个自己的makefile。当然了我们不会去描述每个代码文件是怎么编译链接的。所以我们将只会挑选一些通用的例子来说明问题而你不会在这里找到构建内核的文档、如何整洁内核代码、tags 的生成和交叉编译 相关的说明,等等。我们将从make 开始,使用标准的内核配置文件,到生成了内核镜像bzImage 结束。

如果你已经很了解make 工具那是最好,但是我也会描述本文出现的相关代码。

让我们开始吧

编译内核前的准备

在开始编译前要进行很多准备工作。最主要的就是找到并配置好配置文件,make 命令要使用到的参数都需要从这些配置文件获取。现在就让我们深入内核的根makefile

内核的根Makefile 负责构建两个主要的文件:vmlinux (内核镜像可执行文件)和模块文件。内核的 Makefile 从此处开始:

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

这些变量决定了当前内核的版本,并且被使用在很多不同的地方,比如KERNELVERSION

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

接下来我们会看到很多ifeq 条件判断语句,它们负责检查传给make 的参数。内核的Makefile 提供了一个特殊的编译选项make help ,这个选项可以生成所有的可用目标和一些能传给make 的有效的命令行参数。举个例子,make V=1 会在构建过程中输出详细的编译信息,第一个ifeq 就是检查传递给make的V=n 选项。

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

如果V=n 这个选项传给了make ,系统就会给变量KBUILD_VERBOSE 选项附上V 的值,否则的话KBUILD_VERBOSE 就会为0。然后系统会检查KBUILD_VERBOSE 的值,以此来决定quietQ 的值。符号@ 控制命令的输出,如果它被放在一个命令之前,这条命令的执行将会是CC scripts/mod/empty.o,而不是Compiling .... scripts/mod/empty.oCC 在makefile 中一般都是编译命令)。最后系统仅仅导出所有的变量。下一个ifeq 语句检查的是传递给make 的选项O=/dir,这个选项允许在指定的目录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),)

系统会检查变量KBUILD_SRC如果他是空的第一次执行makefile 时总是空的),并且变量KBUILD_OUTPUT 被设成了选项O 的值(如果这个选项被传进来了),那么这个值就会用来代表内核源码的顶层目录。下一步会检查变量KBUILD_OUTPUT ,如果之前设置过这个变量,那么接下来会做以下几件事:

  • 将变量KBUILD_OUTPUT 的值保存到临时变量saved-output
  • 尝试创建输出目录;
  • 检查创建的输出目录,如果失败了就打印错误;
  • 如果成功创建了输出目录,那么就在新目录重新执行make 命令(参见选项-C)。

下一个ifeq 语句会检查传递给make 的选项CM

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

第一个选项C 会告诉makefile 需要使用环境变量$CHECK 提供的工具来检查全部c 代码,默认情况下会使用sparse。第二个选项M 会用来编译外部模块(本文不做讨论)。因为设置了这两个变量,系统还会检查变量KBUILD_SRC,如果KBUILD_SRC 没有被设置,系统会设置变量srctree.

ifeq ($(KBUILD_SRC),)
        srctree := .
endif
		
objtree	:= .
src		:= $(srctree)
obj		:= $(objtree)

export srctree objtree VPATH

这将会告诉Makefile 内核的源码树就在执行make 命令的目录。然后要设置objtree 和其他变量为执行make 命令的目录,并且将这些变量导出。接着就是要获取SUBARCH 的值这个变量代表了当前的系统架构一般都指CPU 架构):

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

如你所见,系统执行uname 得到机器、操作系统和架构的信息。因为我们得到的是uname 的输出,所以我们需要做一些处理在赋给变量SUBARCH 。获得SUBARCH 之后就要设置SRCARCHhfr-archSRCARCH提供了硬件架构相关代码的目录,hfr-arch 提供了相关头文件的目录:

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

hdr-arch  := $(SRCARCH)

注意:ARCHSUBARCH 的别名。如果没有设置过代表内核配置文件路径的变量KCONFIG_CONFIG,下一步系统会设置它,默认情况下就是.config

KCONFIG_CONFIG	?= .config
export KCONFIG_CONFIG

以及编译内核过程中要用到的shell

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

接下来就要设置一组和编译内核的编译器相关的变量。我们会设置主机的CC++ 的编译器及相关配置项:

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

下一步会去适配代表编译器的变量CC,那为什么还要HOST* 这些选项呢?这是因为CC 是编译内核过程中要使用的目标架构的编译器,但是HOSTCC 是要被用来编译一组host 程序的(下面我们就会看到)。然后我们就看看变量KBUILD_MODULESKBUILD_BUILTIN 的定义,这两个变量决定了我们要编译什么东西(内核、模块还是其他):

KBUILD_MODULES :=
KBUILD_BUILTIN := 1

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

在这我们可以看到这些变量的定义,并且,如果们仅仅传递了modulesmake,变量KBUILD_BUILTIN 会依赖于内核配置选项CONFIG_MODVERSIONS。下一步操作是引入下面的文件:

include scripts/Kbuild.include

文件kbuild ,Kbuild 或者又叫做 Kernel Build System是一个用来管理构建内核和模块的特殊框架。kbuild 文件的语法与makefile 一样。文件scripts/Kbuild.includekbuild 系统同提供了一些原生的定义。因为我们包含了这个kbuild 文件,我们可以看到和不同工具关联的这些变量的定义,这些工具会在内核和模块编译过程中被使用(比如链接器、编译器、二进制工具包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
...
...
...

After definition of these variables we define two variables: USERINCLUDE and LINUXINCLUDE. They will contain paths of the directories with headers (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 编译器标志:

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: 这并不是最终确定的编译器标志他们还可以在其他makefile 里面更新(比如arch/ 里面的kbuild。经过所有这些全部变量会被导出这样其他makefile 就可以直接使用了。下面的两个变量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 直面构建内核

As we have finished all preparations, next step in the root makefile is related to the kernel build. Before this moment we will not see in the our terminal after the execution of the make command. But now first steps of the compilation are started. In this moment we need to go on the 598 line of the Linux kernel top makefile and we will see vmlinux target there:

现在我们已经完成了所有的准备工作根makefile内核根目录下的makefile的下一步工作就是和编译内核相关的了。在我们执行make 命令之前我们不会在终端看到任何东西。但是现在编译的第一步开始了这里我们需要从内核根makefile的的598 行开始,这里可以看到目标vmlinux

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

Don't worry that we have missed many lines in Makefile that are placed after export RCS_FIND_IGNORE..... and before 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 代码,他们只是负责根据各种配置文件生成不同目标内核的,因为之前我就说了这一部分我们只讨论构建内核的通用途径。

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 依赖于根makefile 后面声明的vmlinux

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

The vmlinux is is the Linux kernel in an statically linked executable file format. The scripts/link-vmlinux.sh script links 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 the 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:

它是由内核代码下的每个顶级目录的built-in.o 组成的。之后我们还会检查内核所有的目录,kbuild 会编译各个目录下所有的对应$obj-y 的源文件。接着调用$(LD) -r 把这些文件合并到一个build-in.o 文件里。此时我们还没有vmloinux-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 the vmlinux-dirs depends on the two targets: prepare and scripts. The first prepare defined in the top Makefile of the Linux kernel and executes three stages of preparations:

就像我们看到的,vmlinux-dir 依赖于两部分:preparescripts。第一个prepare 定义在内核的根makefile ,准备工作分成三个阶段:

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 exapnds to the archheaders and archscripts that defined in the x86_64 specific Makefile. Let's look on it. The x86_64 specific makefile starts from the definition of the variables that are related to the archicteture-specific configs (defconfig and 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). The first target is archheaders in the makefile generates syscall table:

第一个prepare0 展开到archprepare ,后者又展开到archheaderarchscripts,这两个变量定义在x86_64 相关的Makefile。让我们看看这个文件。x86_64 特定的makefile从变量定义开始这些变量都是和特定架构的配置文件 (defconfig,等等)有关联。变量定义之后这个makefile 定义了编译16-bit代码的编译选项,根据变量BITS 的值,如果是32 汇编代码、链接器、以及其它很多东西(全部的定义都可以在arch/x86/Makefile找到)对应的参数就是i386,而64就对应的是x86_84。生成的系统调用列表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:

我们可以看到archscripts 是依赖于根Makefile里的scripts_basic 。首先我们可以看出scripts_basic 是根据scripts/basic 的mekefile 执行的:

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 the gcc that tells make when to remake a source code file. The second program is bin2c depends on the value of the CONFIG_BUILD_BIN2C kernel configuration option and very little C program that allows to convert a binary on stdin to a C include on stdout. You can note here strange notation: hostprogs-y and etc. This notation is used in the all kbuild files and more about it you can read in the documentation. In our case the hostprogs-y tells to the kbuild that there is one host program named fixdep that will be built from the will be built from fixdep.c that located in the same directory that Makefile. The first output after we will execute make command in our terminal will be result of this kbuild file:

第一个工具是fixdep:用来优化gcc 生成的依赖列表然后在重新编译源文件的时候告诉make。第二个工具是bin2c,他依赖于内核配置选项CONFIG_BUILD_BIN2C,并且它是一个用来将标准输入接口即stdin收到的二进制流通过标准输出接口stdout转换成C 头文件的非常小的C 程序。你可以注意到这里有些奇怪的标志,如hostprogs-y等。这些标志使用在所有的kbuild 文件,更多的信息你可以从documentation 获得。在我们的用例hostprogs-y 中,他告诉kbuild 这里有个名为fixed 的程序,这个程序会通过和Makefile 相同目录的fixdep.c 编译而来。执行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 就会make arch/x86/tools 下的makefile 和目标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. After the asm-generic target the archprepare will be done, so the prepare0 target will be executed. As I wrote above:

以及在内核根Makefiel 使用arch/x86/include/generated/asm的目标asm-generic 来构建generic 汇编头文件。在目标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:

参数obj 会告诉脚本scripts/Makefile.build 那些目录包含kbuild 文件,脚本以此来寻找各个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. The vmlinux-dirs also depends on the second target - scripts that compiles following programs: file2alias, mk_elfconfig, modpost and etc... After scripts/host-programs compilation our vmlinux-dirs target can be executed. First of all let's try to understand what does vmlinux-dirs contain. For my case it contains paths of the following kernel directories:

然后根据这个构建目标。我们这里. 包含了Kbuild,就用这个文件来生成kernel/bounds.sarch/x86/kernel/asm-offsets.s。这样目标prepare 就完成了它的工作。vmlinux-dirs 也依赖于第二个目标——scripts scripts会编译接下来的几个程序:filealiasmk_elfconfig,modpost等等。scripts/host-programs 编译完之后,我们的目标vmlinux-dirs 就可以开始编译了。第一步,我们先来理解一下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:

我们可以在内核的根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-io.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 and etc., but I will not describe it in this part as I wrote in the beginning of this part.

好了,所有的built-in.o 都构建完了,现在我们回到目标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:

你可以看到,vmlinux 的调用脚本scripts/link-vmlinux.sh 的主要目的是把所有的built-in.o 链接成一个静态可执行文件、生成System.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: 还有内核源码树根目录下的vmlinuxSystem.map

$ 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 is the compressed Linux kernel image. We can get it with the execution of the make bzImage after the vmlinux built. In other way we can just execute make without arguments and will get bzImage anyway because it is default image:

bzImage 就是压缩了的linux 内核镜像。我们可以在构建了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 := arch/x86/boot

The main goal now to build source code in the arch/x86/boot and arch/x86/boot/compressed directories, build setup.bin and vmlinux.bin, and build the bzImage from they 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 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 code file is the 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.h 是使用sed 脚本生成的,包含用nm 工具从vmlinux 获取的两个地址:

#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.harch/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 the vmlinux.bin is the vmlinux with striped debuging information and comments 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.bin 是去掉了调试信息和注释的vmlinux 二进制文件,加上了占用了u32 即4-Byte的长度信息的vmlinux.bin.all 压缩后就是vmlinux.bin.bz2。其中vmlinux.bin.all 包含了vmlinux.binvmlinux.relocsvmlinux 的重定位信息),其中vmlinux.relocsvmlinux 经过程序relocs 处理之后的vmlinux 镜像(见上文所述)。我们现在已经获取到了这些文件,汇编文件piggy.S 将会被mkpiggy 生成、然后编译:

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

This assembly files will contain computed offset from a 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 已经生成了,arch/x86/boot 里的源文件可以继续编译:

  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 familiar to all who once build the Linux kernel from source:

实际上bzImage 就是把setup.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 to understand process of the Linux kernel building.

这就是本文的最后一节。本文我们了解了编译内核的全部步骤:从执行make 命令开始,到最后生成bzImage。我知道linux 内核的makefiles 和构建linux 的过程第一眼看起来可能比较迷惑但是这并不是很难。希望本文可以帮助你理解构建linux 内核的整个流程。

Links 链接


via: https://github.com/0xAX/linux-insides/blob/master/Misc/how_kernel_compiled.md

译者:译者ID 校对:校对者ID

本文由 LCTT 原创翻译,Linux中国 荣誉推出