介绍
我不会告诉你怎么在自己的电脑上去构建、安装一个定制化的 Linux 内核,这样的资料太多了,它们会对你有帮助。本文会告诉你当你在内核源码路径里敲下make
时会发生什么。
当我刚刚开始学习内核代码时,Makefile 是我打开的第一个文件,这个文件看起来真令人害怕 :)。那时候这个 Makefile 还只包含了1591
行代码,当我开始写本文时,内核已经是4.2.0的第三个候选版本 了。
这个 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
这些变量决定了当前内核的版本,并且被使用在很多不同的地方,比如同一个 Makefile
中的 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
(LCTT 译注: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
和其他变量为这个目录,并且将这些变量导出。接着就是要获取 SUBARCH
的值,这个变量代表了当前的系统架构(LCTT 译注:一般都指CPU 架构):
SUBARCH :=