校对到200line
44 KiB
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
的值,以此来决定quiet
和Q
的值。符号@
控制命令的输出,如果它被放在一个命令之前,这条命令的执行将会是CC scripts/mod/empty.o
,而不是Compiling .... scripts/mod/empty.o
(注:CC 在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 的选项C
和M
:
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
之后就要设置SRCARCH
和hfr-arch
,SRCARCH
提供了硬件架构相关代码的目录,hfr-arch
提供了相关头文件的目录:
ifeq ($(ARCH),i386)
SRCARCH := x86
endif
ifeq ($(ARCH),x86_64)
SRCARCH := x86
endif
hdr-arch := $(SRCARCH)
注意:ARCH
是SUBARCH
的别名。如果没有设置过代表内核配置文件路径的变量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)
接下来就要设置一组和编译内核的编译器相关的变量。我们会设置主机的C
和C++
的编译器及相关配置项:
HOSTCC = gcc
HOSTCXX = g++
HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
HOSTCXXFLAGS = -O2
下一步会去适配代表编译器的变量CC
,那为什么还要HOST*
这些选项呢?这是因为CC
是编译内核过程中要使用的目标架构的编译器,但是HOSTCC
是要被用来编译一组host
程序的(下面我们就会看到)。然后我们就看看变量KBUILD_MODULES
和KBUILD_BUILTIN
的定义,这两个变量决定了我们要编译什么东西(内核、模块还是其他):
KBUILD_MODULES :=
KBUILD_BUILTIN := 1
ifeq ($(MAKECMDGOALS),modules)
KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif
在这我们可以看到这些变量的定义,并且,如果们仅仅传递了modules
给make
,变量KBUILD_BUILTIN
会依赖于内核配置选项CONFIG_MODVERSIONS
。下一步操作是引入下面的文件:
include scripts/Kbuild.include
文件kbuild
,Kbuild 或者又叫做 Kernel Build System
是一个用来管理构建内核和模块的特殊框架。kbuild
文件的语法与makefile 一样。文件scripts/Kbuild.include 为kbuild
系统同提供了一些原生的定义。因为我们包含了这个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):
在这些定义好的变量之后,我们又定义了两个变量:USERINCLUDE
和LINUXINCLUDE
。他们为包含头文件的路径(第一个是给用户用的,第二个是给内核用的):
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_IGNORE
和 RCS_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
依赖于两部分:prepare
和scripts
。第一个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
,后者又展开到archheader
和archscripts
,这两个变量定义在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
包含了编译两个主机程序fixdep
和bin2
的目标:
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.c
和relocs_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.s
和arch/x86/kernel/asm-offsets.s
。这样目标prepare
就完成了它的工作。vmlinux-dirs
也依赖于第二个目标——scripts
,scripts
会编译接下来的几个程序:filealias
,mk_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:
这里我们借助函数patsubst
和filter
去掉了每个目录路径里的符号/
,并且把结果放到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:
还有内核源码树根目录下的vmlinux
和System.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/boot
和arch/x86/boot/compressed
的代码,构建setup.bin
和vmlinux.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.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 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.bin
和vmlinux.relocs
(注:vmlinux 的重定位信息),其中vmlinux.relocs
是vmlinux
经过程序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.h
和voffset.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.bin
和vmlinux.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.bin
和vmlinux.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 链接
- GNU make util
- Linux kernel top Makefile
- cross-compilation
- Ctags
- sparse
- bzImage
- uname
- shell
- Kbuild
- binutils
- gcc
- Documentation
- System.map
- Relocation
via: https://github.com/0xAX/linux-insides/blob/master/Misc/how_kernel_compiled.md