参考
一次实验引发的故事 – kernel build system探索—vmlinux是如何炼成的– kernel makefile深度探索Linux操作系统:系统构建和原理解析.pdf
问题
在前面的博文中,我们先是为自己的Ubuntu安装了一套内核源码树,然后为了方便进行嵌入式交叉编译,我们又为arm板子构建了一套源码树。
那么现在我们已经知道如何自己的电脑上去构建、安装一个定制化的Linux内核,但是我们还是要在唠叨一些。
当你在内核源码路径里敲下make时究竟发生什么
当我们刚刚开始接触内核代码时,毫无头绪,这时候Makefile是往往是我们打开的第一个文件,这个makefile是Linux内核代码的根makefile,内核构建就始于此处。是的,它的内容很多,但是如果你已经读过内核源代码,你就会发现每个包含代码的目录都有一个自己的Makefile。当然了,我们不会去描述每个代码文件是怎么编译链接的,所以我们将只会挑选一些通用的例子来说明问题。而你不会在这里找到构建内核的文档、如何整洁内核代码、tags的生成和交叉编译相关的说明,等等。
我们仅仅将从make开始,使用标准的内核配置文件,一直到生成了内核镜像bzImage或者zImage结束。
当然在着之前我们需要了解,我们make究竟是要构建一个什么样的目标,我想这个
构建的目标vmlinux,vmlinuz,bzImage,zImage
对于Linux内核,编译可以生成不同格式的映像文件,例如:
make zImag
make uImage
zImage是ARM Linux常用的一种压缩映像文件,uImage是U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的“头”,说明这个映像文件的类型、加载位置、生成时间、大小等信息。换句话说,如果直接从uImage的0x40位置开始执行,zImage和uImage没有任何区别。另外,Linux2.4内核不支持uImage,Linux2.6内核加入了很多对嵌入式系统的支持,但是uImage的生成也需要设置。
几种linux内核文件的区别:
1、vmlinux 编译出来的最原始的内核文件,未压缩。
2、zImage 是vmlinux经过gzip压缩后的文件。适用于小内核
3、bzImage bz表示“big zImage”,不是用bzip2压缩的。两者的不同之处在于,zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么采用zImage或bzImage都行,如果比较大应该用bzImage。适用于大内核
4、uImage U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的tag。
5、vmlinuz 是bzImage/zImage文件的拷贝或指向bzImage/zImage的链接。
6、initrd 是“initial ramdisk”的简写。一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态。
vmlinux
vmlinux是未压缩的内核,是make工作编译出的原始内核,vmlinuz是vmlinux的压缩文件。
vmlinux 是ELF文件,即编译出来的最原始的文件。
zImage, bzImage和vmlinuz
vmlinuz是可引导的、压缩的内核。“vm”代表“Virtual Memory”。Linux 支持虚拟内存,不像老的操作系统比如DOS有640KB内存的限制。Linux能够使用硬盘空间作为虚拟内存,因此得名“vm”。vmlinuz是可执行的Linux内核,它位于/boot/vmlinuz,它一般是一个软链接,是bzImage/zImage文件的拷贝或指向bzImage/zImage的链接。
vmlinuz的建立有两种方式。
一是编译内核时通过“make zImage”创建,然后通过:“cp /usr/src/linux-2.4/arch/i386/linux/boot/zImage /boot/vmlinuz”产生。zImage适用于小内核的情况,它的存在是为了向后的兼容性。
二是内核编译时通过命令make bzImage创建,然后通过:“cp /usr/src/linux-2.4/arch/i386/linux/boot/bzImage /boot/vmlinuz”产生。bzImage是压缩的内核映像,需要注意,bzImage不是用bzip2压缩的,bzImage中的bz容易引起误解,bz表示“big zImage”。 bzImage中的b是“big”意思。
zImage(vmlinuz)和bzImage(vmlinuz)都是用gzip压缩的。它们不仅是一个压缩文件,而且在这两个文件的开头部分内嵌有gzip解压缩代码。所以你不能用gunzip 或 gzip –dc解包vmlinuz。
内核文件中包含一个微型的gzip用于解压缩内核并引导它。两者的不同之处在于,老的zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么可以采用zImage 或bzImage之一,两种方式引导的系统运行时是相同的。大的内核采用bzImage,不能采用zImage。
但是注意通常情况下是不能用vmlinuz解压缩得到vmlinux的
initrd-x.x.x.img
initrd是“initial ramdisk”的简写。initrd一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态。
initrd 映象文件是使用mkinitrd创建的。mkinitrd实用程序能够创建initrd映象文件。这个命令是RedHat专有的。其它Linux发行版或许有相应的命令。这是个很方便的实用程序。具体情况请看帮助:man mkinitrd下面的命令创建initrd映象文件。
最后生成的内核镜象有两种 zImage 以及 uImage 。其中 zImage 下载到目标板中后,可以直接用 uboot 的命令 go 来进行直接跳转。这时候内核直接解压启动。但是无法挂载文件系统,因为 go 命令没有将内核需要的相关的启动参数传递给内核。传递启动参数我们必须使用命令 bootm 来进行跳转。 Bootm 命令跳转只处理 uImage 的镜象。
uboot 源代码的 tools/ 目录下有 mkimage 工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。
mkimage 在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个 0x40 字节的头,记录参数所指定的信息,这样 uboot 才能识别这个映象是针对哪个 CPU 体系结构的,哪个 OS 的,哪种类型,加载内存中的哪个位置, 入口点在内存的那个位置以及映象名是什么
uImage文件
vmlinux是内核文件,zImage是一般情况下默认的压缩内核映像文件,压缩vmlinux,加上一段解压启动代码得到。而uImage 则是使用工具mkimage对普通的压缩内核映像文件(zImage)加工而得。它是uboot专用的映像文件,它是在zImage之前加上一个长度为 64字节的“头”,说明这个内核的版本、加载位置、生成时间、大小等信息;其0x40之后与zImage没区别。
其实就是一个自动跟手动的区别,有了uImage头部的描述,u-boot就知道对应Image的信息,如果没有头部则需要自己手动去搞那些参数。
如何生成 uImage文件?首先在uboot的/tools目录下寻找mkimage文件,把其copy到系统/usr/local/bin目录下,这样就完成制 作工具。然后在内核目录下运行make uImage,如果成功,便可以在arch/arm/boot/目录下发现uImage文件,其大小比 zImage多64个字节。
此外,平时调试用uImage,不用去管调整了哪些东西;zImage则是一切OK后直接烧0X0。开机就运行
编译内核前的准备
在开始编译前要进行很多准备工作。最主要的就是找到并配置好配置文件,make命令要使用到的参数都需要从这些配置文件获取。现在就让我们深入内核的根makefile吧
内核版本设定
内核的根Makefile负责构建两个主要的文件:vmlinux(内核镜像可执行文件)和模块文件moudles。
我们先看看内核的Makefile开始的几行head -n 6 Makefile
VERSION = 4
PATCHLEVEL = 2
SUBLEVEL = 3
EXTRAVERSION =
NAME = Hurr durr I'ma sheep
Makefile在开始的时候定义了几个变量,这些变量决定了当前内核的版本,并且被使用在很多不同的地方,比如同一个Makefile中的KERNELVERSION
关于版本号
Linux内核有三个不同的命名方案。
参见 https://zh.wikipedia.org/wiki/Linux%E5%86%85%E6%A0%B8
早期版本
第一种方式用于1.0版本之前(包括1.0)。第一个版本的内核是0.01。其次是0.02,0.03,0.10,0.11,0.12(第一GPL版本),0.95,0.96,0.97,0.98,0.99及1.0。从0.95版有许多的补丁发布于主要版本版本之间。旧计划
第二种方式用于1.0之后到2.6,版本的格式为A.B.C,其中A,B,C代表:A大幅度转变的内核。这是很少发生变化,只有当发生重大变化的代码和核心发生才会发生。在历史上曾改变两次的内核:1994年的1.0及1996年的2.0。
B是指一些重大修改的内核。内核使用了传统的奇数次要版本号码的软件号码系统(用偶数的次要版本号码来表示稳定版本)。
C是指轻微修订的内核。这个数字当有安全补丁,bug修复,新的功能或驱动程序,内核便会有变化。
这样稳定版本来源于上一个测试版升级版本号,而一个稳定版本发展到完全成熟后就不再发展。自2.6.0(2003年12月)发布后,人们认识到,更短的发布周期将是有益的。自那时起,版本的格式为A.B.C.D,其中A,B,C,D代表:
A和B是无关紧要的
C是内核的版本新计划
自3.0(2011年7月)发布后,版本的格式为3.A.B,其中A,B代表:A是内核的版本
B是安全补丁使用一种“time-based”的方式。3.0版本之前,是一种“A.B.C.D”的格式。七年里,前两个数字A.B即“2.6”保持不变,C随着新版本的发布而增加,D代表一些bug修复,安全更新,添加新特性和驱动的次数。
3.0版本之后是“A.B.C”格式,B随着新版本的发布而增加, C代表一些bug修复,安全更新,新特性和驱动的次数。第三种方式中不再使用偶数代表稳定版,奇数代表开发版这样的命名方式。举个例子:3.7.0代表的不是开发版,而是稳定版!而4.0(2015年4月)发布后,则延续3.A.B的命名格式,只是将主版号变更为4。
make参数传递
接下来我们会看到很多ifeq条件判断语句,它们负责检查传递给make的参数。内核的Makefile提供了一个特殊的编译选项makehelp,这个选项可以生成所有的可用目标和一些能传给make的有效的命令行参数。
举个例子,首先出现的就是-V
,那么make V=1
会在构建过程中输出详细的编译信息,第一个ifeq就是检查传递给make的V=n选项。
参数-v在控制构建过程中输出编译信息
使用cat -n Makefile | head -n 83 | tail -n +23
查看
# Avoid interference with shell env settings
unexport GREP_OPTIONS
# We are using a recursive build, so we need to do a little thinking
# to get the ordering right.
#
# Most importantly: sub-Makefiles should only ever modify files in # their own directory. If in some directory we have a dependency on # a file in another dir (which doesn't happen often, but it's often # unavoidable when linking the built-in.o targets which finally # turn into vmlinux), we will call a sub make in that other dir, and # after that we are sure that everything which is in that other dir # is now up to date. # # The only cases where we need to modify files which have global # effects are thus separated out and done before the recursive # descending is started. They are now explicitly listed as the # prepare rule. # Beautify output # --------------------------------------------------------------------------- # # Normally, we echo the whole command before executing it. By making # that echo $($(quiet)$(cmd)), we now have the possibility to set # $(quiet) to choose other forms of output instead, e.g. # # quiet_cmd_cc_o_c = Compiling $(RELDIR)/$@ # cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< # # If $(quiet) is empty, the whole command will be printed. # If it is set to "quiet_", only the short version will be printed. # If it is set to "silent_", nothing will be printed at all, since # the variable $(silent_cmd_cc_o_c) doesn't exist. # # A simple variant is to prefix commands with $(Q) - that's useful # for commands that shall be hidden in non-verbose mode. # # $(Q)ln $@ :< # # If KBUILD_VERBOSE equals 0 then the above command will be hidden. # If KBUILD_VERBOSE equals 1 then the above command is displayed. # # To put more focus on warnings, be less verbose as default # Use 'make V=1' to see the full commands 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
如果V=n这个选项传给了make,系统就会给变量KBUILD_VERBOSE选项附上V的值,否则的话KBUILD_VERBOSE就会为0。然后系统会检查KBUILD_VERBOSE的值,以此来决定quiet和Q的值。符号@控制命令的输出,如果它被放在一个命令之前,这条命令的输出将会是CCscripts/mod/empty.o,而不是Compiling….scripts/mod/empty.o(LCTT译注:CC在makefile中一般都是编译命令)。在这段最后,系统导出了所有的变量。
参数-s
然后是-s
的控制
# If the user is running make -s (silent mode), suppress echoing of
# commands
ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4 ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),) quiet=silent_ endif else # make-3.8x ifneq ($(filter s% -s%,$(MAKEFLAGS)),) quiet=silent_ endif endif export quiet Q KBUILD_VERBOSE
参数-O
下一个ifeq语句检查的是传递给make的选项O=/dir,这个选项允许在指定的目录dir输出所有的结果文件
使用 cat -n Makefile | head -n 153 | tail -n +127
查看
# Cancel implicit rules on top Makefile
$(CURDIR)/Makefile Makefile: ;
ifneq ($(KBUILD_OUTPUT),)
# Invoke a second make in the output directory, passing relevant variables
# check that the output directory actually exists
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)")) PHONY += $(MAKECMDGOALS) sub-make $(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make @: sub-make: FORCE $(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \ -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS)) # Leave processing to above invocation of make 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)。
选项C
下一个ifeq语句会检查传递给make的选项C
使用 cat -n Makefile | head -n 178 | tail -n +153
查看
# We process the rest of the Makefile if this is the final invocation of make
ifeq ($(skip-makefile),)
# Do not print "Entering directory ...",
# but we want to display it when entering to the output directory # so that IDEs/editors are able to understand relative filenames. MAKEFLAGS += --no-print-directory # Call a source code checker (by default, "sparse") as part of the # C compilation. # # Use 'make C=1' to enable checking of only re-compiled files. # Use 'make C=2' to enable checking of *all* source files, regardless # of whether they are re-compiled or not. # # See the file "Documentation/sparse.txt" for more details, including # where to get the "sparse" utility. ifeq ("$(origin C)", "command line") KBUILD_CHECKSRC = $(C) endif ifndef KBUILD_CHECKSRC KBUILD_CHECKSRC = 0 endif
选项C会告诉makefile需要使用环境变量$CHECK提供的工具来检查全部c代码,默认情况下会使用sparse。
我们可以看到之前先检查了skip-makefile
,这个变量在选项O的时候被定义为1
选项M
选项M会用来编译外部模块
使用cat -n Makefile | head -n 198 | tail -n +178
查看
# Use make M=dir to specify directory of external module to build
# Old syntax make ... SUBDIRS=$PWD is still supported # Setting the environment variable KBUILD_EXTMOD take precedence ifdef SUBDIRS KBUILD_EXTMOD ?= $(SUBDIRS) endif ifeq ("$(origin M)", "command line") KBUILD_EXTMOD := $(M) endif # If building an external module we do not care about the all: rule # but instead _all depend on modules PHONY += all ifeq ($(KBUILD_EXTMOD),) _all: all else _all: modules endif
设置objtree
紧接着系统检查了变量KBUILD_SRC
,如果KBUILD_SRC
没有被设置,系统会设置变量srctree为当前目录./
, 使用cat -n Makefile | head -n 215 | tail -n +198
进行查看
ifeq ($(KBUILD_SRC),)
# building in the source tree
srctree := .
else
ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR))) # building in a subdirectory of the source tree srctree := .. else srctree := $(KBUILD_SRC) endif endif objtree := . src := $(srctree) obj := $(objtree) VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD)) export srctree objtree VPATH
这将会告诉Makefile内核的源码树就在执行make命令的目录,然后要设置objtree和其他变量为这个目录,并且将这些变量导出。
SUBARCH获取系统架构
接着就是要获取SUBARCH的值,这个变量代表了当前的系统架构(LCTT译注:一般都指CPU架构):
使用cat Makefile | head -n 230 | tail -n +217
查看
# SUBARCH tells the usermode build what the underlying arch is. That is set
# first, and if a usermode build is happening, the "ARCH=um" on the command
# line overrides the setting of ARCH below. If a native build is happening,
# then ARCH is assigned, getting whatever value it gets normally, and
# SUBARCH is subsequently ignored. 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 -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/
我的机子是Ubuntu-Gnome14.04 LTS x86的(即x86架构)运行一下
得到如下信息
如你所见,系统执行uname得到机器、操作系统和架构的信息。因为我们得到的是uname的输出,所以我们需要做一些处理再赋给变量SUBARCH。
依据SUBARCH设置SRCARCH和hfr-arch
获得SUBARCH
之后就要设置SRCARCH
和hfr-arch
SRCARCH提供了硬件架构相关代码的目录
hfr-arch提供了相关头文件的目录
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%) # Architecture as present in compile.h UTS_MACHINE := $(ARCH) SRCARCH := $(ARCH) # Additional ARCH settings for x86 ifeq ($(ARCH),i386) SRCARCH := x86 endif ifeq ($(ARCH),x86_64) SRCARCH := x86 endif # Additional ARCH settings for sparc ifeq ($(ARCH),sparc32) SRCARCH := sparc endif ifeq ($(ARCH),sparc64) SRCARCH := sparc endif # Additional ARCH settings for sh ifeq ($(ARCH),sh64) SRCARCH := sh endif # Additional ARCH settings for tile ifeq ($(ARCH),tilepro) SRCARCH := tile endif ifeq ($(ARCH),tilegx) SRCARCH := tile endif # Where to locate arch specific headers hdr-arch := $(SRCARCH)
注意:ARCH是SUBARCH的别名。
设置KCONFIG_CONFIG
如果没有设置过代表内核配置文件路径的变量KCONFIG_CONFIG
,下一步系统会设置它,默认情况下就是.config,这个文件是不是很熟悉,它就是我们make menuconfig后的那个.config配置文件,里面写入我们内核编译的所有信息
使用cat -n Makefile | head -n 292 | tail -n +289
查看
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
CONFIG_SHELL编译内核过程中要用到的shell
使用cat -n Makefile | head -n 297 | tail -n +292
查看
# SHELL used by kbuild
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++的编译器及相关配置项
使用cat -n Makefile | head -n 307 | tail -n +297
查看
HOSTCC = gcc
HOSTCXX = g++
HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89 HOSTCXXFLAGS = -O2 ifeq ($(shell $(HOSTCC) -v 2>&1 | grep -c "clang version"), 1) HOSTCFLAGS += -Wno-unused-value -Wno-unused-parameter \ -Wno-missing-field-initializers -fno-delete-null-pointer-checks endif
我们可以看到Makefile在这里开始适配代表C/C++编译器的变量CC和CXX
那为什么还要HOST*这些变量呢?这是因为CC是编译内核过程中要使用的目标架构的编译器,但是HOSTCC是要被用来编译一组host程序的(下面我们就会看到)。
KBUILD_编译的目标
然后我们就看到变量KBUILD_MODULES和KBUILD_BUILTIN的定义,这两个变量决定了我们要编译什么东西(内核、模块或者两者都有):
使用cat -n Makefile | head -n 337 | tail -n +307
查看
# Decide whether to build built-in, modular, or both.
# Normally, just do built-in.
KBUILD_MODULES :=
KBUILD_BUILTIN := 1
# If we have only "make modules", don't compile built-in objects. # When we're building modules with modversions, we need to consider # the built-in objects during the descend as well, in order to # make sure the checksums are up to date before we record them. ifeq ($(MAKECMDGOALS),modules) KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1) endif # If we have "make modules", compile modules # in addition to whatever we do anyway. # Just "make" or "make all" shall build modules as well ifneq ($(filter all _all modules,$(MAKECMDGOALS)),) KBUILD_MODULES := 1 endif ifeq ($(MAKECMDGOALS),) KBUILD_MODULES := 1 endif export KBUILD_MODULES KBUILD_BUILTIN export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD
在这我们可以看到这些变量的定义,并且,如果们仅仅传递了modules给make,变量KBUILD_BUILTIN会依赖于内核配置选项CONFIG_MODVERSIONS。
Kbuild
接着下一步操作是引入下面的文件:
使用查看 cat Makefile | head -n 341 | tail -n +337
# We need some generic definitions (do not try to remake the file).
scripts/Kbuild.include: ;
include scripts/Kbuild.include
文件Kbuild或者又叫做KernelBuildSystem是一个用来管理构建内核及其模块的特殊框架。kbuild文件的语法与makefile一样。文件scripts/Kbuild.include为kbuild系统提供了一些常规的定义。因为我们包含了这个kbuild文件,我们可以看到和不同工具关联的这些变量的定义,这些工具会在内核和模块编译过程中被使用(比如链接器、编译器、来自binutils的二进制工具包,等等):
# Make variables (CC, etc...)
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 GENKSYMS = scripts/genksyms/genksyms INSTALLKERNEL := installkernel DEPMOD = /sbin/depmod PERL = perl PYTHON = python CHECK = sparse CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ -Wbitwise -Wno-return-void $(CF) CFLAGS_MODULE = AFLAGS_MODULE = LDFLAGS_MODULE = CFLAGS_KERNEL = AFLAGS_KERNEL = CFLAGS_GCOV = -fprofile-arcs -ftest-coverage
在这些定义好的变量后面,我们又定义了两个变量:USERINCLUDE和LINUXINCLUDE。他们包含了头文件的路径(第一个是给用户用的,第二个是给内核用的),使用cat Makefile | head -n 387 | tail -n +369
查看
# Use USERINCLUDE when you must reference the UAPI directories only.
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 # Use LINUXINCLUDE when you must reference the include/ directory. # Needed to be compatible with the O= option LINUXINCLUDE := \ -I$(srctree)/arch/$(hdr-arch)/include \ -Iarch/$(hdr-arch)/include/generated/uapi \ -Iarch/$(hdr-arch)/include/generated \ $(if $(KBUILD_SRC), -I$(srctree)/include) \ -Iinclude \ $(USERINCLUDE)
以及给C编译器的标准标志,使用cat Makefile | head -n 419 | tail -n +387
查看
KBUILD_CPPFLAGS := -D__KERNEL__
KBUILD_CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
-fno-strict-aliasing -fno-common \
-Werror-implicit-function-declaration \
-Wno-format-security \
-std=gnu89
KBUILD_AFLAGS_KERNEL :=
KBUILD_CFLAGS_KERNEL :=
KBUILD_AFLAGS := -D__ASSEMBLY__ KBUILD_AFLAGS_MODULE := -DMODULE KBUILD_CFLAGS_MODULE := -DMODULE KBUILD_LDFLAGS_MODULE := -T $(srctree)/scripts/module-common.lds # Read KERNELRELEASE from include/config/kernel.release (if it exists) KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null) KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION) export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC export CPP AR NM STRIP OBJCOPY OBJDUMP export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL export KBUILD_ARFLAGS
这并不是最终确定的编译器标志,它们还可以在其他makefile里面更新(比如arch/里面的kbuild)。变量定义完之后,全部会被导出供其他makefile使用。
下面的两个变量RCS_FIND_IGNORE和RCS_TAR_IGNORE包含了被版本控制系统忽略的文件,使用cat Makefile | head -n 432 | tail -n +419
查看
# When compiling out-of-tree modules, put MODVERDIR in the module
# tree rather than in the kernel tree. The kernel tree might
# even be read-only.
export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions
# Files to ignore in find ... statements 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
然后后面的一大块内容负责根据各种配置文件(make*.config)生成不同目标内核的
可以使用cat Makefile | head -n 593 | tail -n +432
进行查看,内容较多,我们在这里就不一一列举了。
下面让我么直接进入make构建的过程。
内核编译过程
现在我们已经完成了所有的配置工作,根makefile的下一步工作就是和编译内核相关的了。
在这之前,我们不会在终端看到make命令输出的任何东西。
但是现在编译的第一步开始了,好吧,我们知道make后,最终的结果叫vmlinux,那我们就找找这个神奇的东西是怎么产生的吧。
终极目标vmlinux
这里我们需要从内核根makefile的594行开始,这里可以看到目标vmlinux的构建命令
使用 cat Makefile | head -n 606 | tail -n +594
查看
# The all: target is the default when no target is given on the
# command line.
# This allow a user to issue only 'make' to build a kernel including modules # Defaults to vmlinux, but the arch makefile usually adds further targets all: vmlinux # The arch Makefile can set ARCH_{CPP,A,C}FLAGS to override the default # values of the respective KBUILD_* variables ARCH_CPPFLAGS := ARCH_AFLAGS := ARCH_CFLAGS := include arch/$(SRCARCH)/Makefile
目标all:是在命令行如果不指定具体目标时默认使用的目标。
你可以看到这里包含了架构相关的makefile(在这里就指的是arch/x86/Makefile)。从这一时刻起,我们会从这个makefile继续进行下去。
如我们所见,目标all依赖于根makefile后面声明的vmlinux,我们可以使用cat Makefile | head -n 922 | tail -n +920
来查看
# Include targets which we want to
# execute if the rest of the kernel build went well.
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORC
vmlinux是linux内核的静态链接可执行文件格式。脚本scripts/link-vmlinux.sh把不同的编译好的子模块链接到一起形成了vmlinux。
vmlinux-deps
同时我们可以发现vlinux依赖于是vmlinux-deps,我们查找一下它cat -n Makefile | grep vmlinux-deps
发现它定义在914行,内容如下
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
它是由内核代码下的每个顶级目录的built-in.o组成的。
之后我们还会检查内核所有的目录,kbuild会编译各个目录下所有的对应$(obj-y)
的源文件。接着调用$(LD)-r
把这些文件合并到一个build-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
vmlinux-dirs
内核中有这么多目录,Makefile是怎么知道这些目录的呢,让我们继续往下看,使用cat -n Makefile | head -n 940 | tail -n +936
查看
# The actual objects are generated when descending,
# make sure no implicit rule kicks in
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
我们会发现vmlinux-deps
是基于vmlinux-dirs
继续往下,使用cat Makefile | head -n 950 | tail -n +940
查看
# Handle descending into subdirectories listed in $(vmlinux-dirs)
# Preset locale variables to speed up the build process. Limit locale
# tweaks to this spot to avoid wrong language settings when running
# make menuconfig etc.
# Error messages still appears in the original language PHONY += $(vmlinux-dirs) $(vmlinux-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@
就像我们看到的,vmlinux-dir依赖于两部分:prepare和scripts。
prepare
第一个prepare定义在内核的根makefile中,准备工作分成三个阶段。
我们继续往下看,使用 cat -n Makefile | head -n 996 | tail -n +959
# Things we need to do before we recursively start building the kernel
# or the modules are listed in "prepare".
# A multi level approach is used. prepareN is processed before prepareN-1.
# archprepare is used in arch Makefiles and when processed asm symlink,
# version.h and scripts_basic is processed / created. # Listed in dependency order PHONY += prepare archprepare prepare0 prepare1 prepare2 prepare3 # prepare3 is used to check if we are building in a separate output directory, # and if so do: # 1) Check that make has not been executed in the kernel src $(srctree) prepare3: include/config/kernel.release ifneq ($(KBUILD_SRC),) @$(kecho) ' Using $(srctree) as source for kernel' $(Q)if [ -f $(srctree)/.config -o -d $(srctree)/include/config ]; then \ echo >&2 " $(srctree) is not clean, please run 'make mrproper'"; \ echo >&2 " in the '$(srctree)' directory.";\ /bin/false; \ fi; endif # prepare2 creates a makefile if using a separate output directory prepare2: prepare3 outputmakefile asm-generic prepare1: prepare2 $(version_h) include/generated/utsrelease.h \ include/config/auto.conf $(cmd_crmodverdir) archprepare: archheaders archscripts prepare1 scripts_basic prepare0: archprepare FORCE $(Q)$(MAKE) $(build)=. # All the preparing.. prepare: prepare0
第一个prepare0展开到archprepare,后者又展开到archheader和archscripts,这两个变量定义在对应架构目录下的Makefile,x86架构就是arch/x86让我们看看这个文件。
x86特定的makefile从变量定义开始,这些变量都是和特定架构的配置文件(defconfig,等等)有关联。在定义了编译16-bit代码的编译选项之后,根据变量BITS的值,如果是32,汇编代码、链接器、以及其它很多东西(全部的定义都可以在arch/x86/Makefile找到)对应的参数就是i386,而64就对应的是x86_84。
archheaders
首先是archheaders
archheaders:
$(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all
archscripts
接着是archscripts
archscripts: scripts_basic
$(Q)$(MAKE) $(build)=arch/x86/tools relocs
scripts_basic
然后是scripts_basic
通过查找发现我们可以看到archscripts是依赖于根Makefile里的scripts_basic。
使用cat Makefile | head -n 441 | tail -n +432
查看
#====================================================
# Rules shared between *config targets and build targets
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic: $(Q)$(MAKE) $(build)=scripts/basic $(Q)rm -f .tmp_quiet_recordmcount
首先我们可以看出scripts_basic是按照scripts/basic的makefile执行make的
下面我们看看scripts/basic下的makefile都有什么
scripts/basic/Makefile包含了编译两个主机程序fixdep和bin2的目标
第一个工具是fixdep:
用来优化gcc生成的依赖列表,然后在重新编译源文件的时候告诉make。
第二个工具是bin2c,
它依赖于内核配置选项CONFIG_BUILD_BIN2C,并且它是一个用来将标准输入接口(LCTT译注:即stdin)收到的二进制流通过标准输出接口(即:stdout)转换成C头文件的非常小的C程序。你可能注意到这里有些奇怪的标志,如hostprogs-y等。这个标志用于所有的kbuild文件,更多的信息你可以从documentation获得。
在我们这里,hostprogs-y告诉kbuild这里有个名为fixed的程序,这个程序会通过和Makefile相同目录的fixdep.c编译而来。
我们make时执行make之后,终端的第一个输出就是kbuild的结果:
现在scripts_basic
的工作完成了,现在archscripts
开始工作了,重新回到archscripts
的地方,
$(Q)$(MAKE) $(build)=arch/x86/tools relocs
当目标script_basic被执行,目标archscripts
就会make arch/x86/tools
下的makefile
和目标relocs
包含了重定位的信息的代码relocs_32.c和relocs_64.c将会被编译,这可以在make的输出中看到,下面仍然是make的工作
下面我们继续接着进行make
,我们发现在编译完relocs.c之后会检查version.h
使用 cat Makefile | head -n 1021 | tail -n +1017
查看
$(version_h): $(srctree)/Makefile FORCE $(call filechk,version.h) $(Q)rm -f $(old_version_h)
以及在内核的根Makefiel使用arch/x86/include/generated/asm的目标asm-generic来构建generic汇编头文件。
在目标asm-generic之后,archprepare就完成了,所以目标prepare0会接着被执行,如我上面所写:
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.
注意build,它是定义在文件scripts/Kbuild.include,内容是这样的:
脚本scripts/Makefile.build通过参数obj给定的目录找到Kbuild文件,然后引入kbuild文件
include $(kbuild-file)
并根据这个构建目标。我们这里.包含了生成kernel/bounds.s和arch/x86/kernel/asm-offsets.s的Kbuild文件。在此之后,目标prepare就完成了它的工作。
scripts
vmlinux-dirs也依赖于第二个目标scripts,它会编译接下来的几个程序:filealias,mk_elfconfig,modpost等等。
与prepare类似,所以我们在这里就不细讲了。
开始编译vmlinux-dirs
之后,scripts/host-programs就可以开始编译我们的目标vmlinux-dirs了。
首先,我们先来理解一下vmlinux-dirs都包含了那些东西。在我们的例子中它包含了下列内核目录的路径
我们可以在内核的根Makefile里找到vmlinux-dirs的定义:
使用cat -n Makefile | head -n 905 | tail -n +891
查看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))) vmlinux-alldirs := $(sort $(vmlinux-dirs) $(patsubst %/,%,$(filter %/, \ $(init-) $(core-) $(drivers-) $(net-) $(libs-)))) init-y := $(patsubst %/, %/built-in.o, $(init-y)) core-y := $(patsubst %/, %/built-in.o, $(core-y)) drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) net-y := $(patsubst %/, %/built-in.o, $(net-y)) libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y)) libs-y := $(libs-y1) $(libs-y2)
前面我们已经知道vmlinux-dir会依赖与prepare和scripts
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
符号$@在这里代表了vmlinux-dirs,这就表明程序会递归遍历从vmlinux-dirs以及它内部的全部目录(依赖于配置),并且在对应的目录下执行make命令。我们可以在输出看到结果
在make的最后阶段,当所有的目录编译结束后,每个目录下的源代码将会被编译并且链接到built-io.o里。
神秘的built-in.o
在最后的链接过程中,我们可以看到,几乎所有的依赖条件中,都会生成一个built-in.o的文件。 那这个文件,是怎么生成的呢?
生成vmlinux
那么问题来了,makefile是怎么把内核目录中编译生成的build-in.o链接在一起生成vmlinux的呢?
现在我们回到目标vmlinux上。你应该还记得,目标vmlinux是在内核的根makefile里。在链接vmlinux之前,系统会构建samples,Documentation等等。
接着我们使用cat -n Makefile | head -n 936 | tail -n +922
查看
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check endif ifdef CONFIG_SAMPLES $(Q)$(MAKE) $(build)=samples endif ifdef CONFIG_BUILD_DOCSRC $(Q)$(MAKE) $(build)=Documentation endif ifdef CONFIG_GDB_SCRIPTS $(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.py endif +$(call if_changed,link-vmlinux)
我们可以看到vmlinux依赖于$(vmlinux-deps)
但是还需要一个shell脚本scripts/link-vmlinux.sh
这个脚本是用来干嘛的,不急我们慢慢来。
我们直接看最后使用+$(call if_changed,link-vmlinux)
,真相正在一步步浮出水面。
我们查看一下这个命令
这个命令是cmd_link-vmlinux
,就定义在主Makefile中第917行
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
(C
< 表示第一个以来目标,那么在vmlinux目标中,第一个目标是 scripts/link-vmlinux.sh
那么这个命令展开就成为
/bin/bash scripts/link-vmlinux.sh ld -m elf_i386 --emit-relocs --build-id
现在明晰了在这里调用脚本scripts/link-vmlinux.sh的,把所有的built-in.o链接成一个静态可执行文件vmlinux,和生成System.map。
那么link-vmlinux.sh是怎么做到得呢,使用该cat -n link-vmlinux.sh | head -n 239 | tail -n +229
查看这个脚本的信息
info LD vmlinux
vmlinux_link "${kallsymso}" vmlinux
if [ -n "${CONFIG_BUILDTIME_EXTABLE_SORT}" ]; then info SORTEX vmlinux sortextable vmlinux fi info SYSMAP System.map mksysmap vmlinux System.map
使用了脚本中vmlinux_link这个函数来生成vmlinux,使用mksysmap生成System.map
下面是vmlinux_link
函数的定义,cat -n link-vmlinux.sh | head -n 69 | tail -n +51
vmlinux_link()
{
local lds="${objtree}/${KBUILD_LDS}" if [ "${SRCARCH}" != "um" ]; then ${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \ -T ${lds} ${KBUILD_VMLINUX_INIT} \ --start-group ${KBUILD_VMLINUX_MAIN} --end-group ${1} else ${CC} ${CFLAGS_vmlinux} -o ${2} \ -Wl,-T,${lds} ${KBUILD_VMLINUX_INIT} \ -Wl,--start-group \ ${KBUILD_VMLINUX_MAIN} \ -Wl,--end-group \ -lutil ${1} rm -f linux fi }
然后是mksysmap,使用cat -n link-vmlinux.sh | head -n 107 | tail -n +103
查看
mksysmap()
{
${CONFIG_SHELL} "${srctree}/scripts/mksysmap" ${1} ${2} }
最后我们来看看下面的输出:
vmlinux和System.map生成在内核源码树根目录下。
这就是全部了,vmlinux构建好了,下一步就是创建bzImage.
构建bzImage
bzImage就是压缩了的linux内核镜像。我们可以在构建了vmlinux之后通过执行makebzImage获得bzImage。同时我们可以仅仅执行make而不带任何参数也可以生成bzImage,因为它是在arch/x86/kernel/Makefile里预定义的、默认生成的镜像。
我们在makefile中查找一下
我们可以看到bzImage是依赖于vmlinux生成的,
我们使用cat -n Makefile | head -n 237 | tail -n +215
查看其构建信息
####
# boot loader support. Several targets are kept for legacy purposes
boot := arch/x86/boot
BOOT_TARGETS = bzlilo bzdisk fdimage fdimage144 fdimage288 isoimage
PHONY += bzImage $(BOOT_TARGETS) # Default kernel to build all: bzImage # KBUILD_IMAGE specify target image being built KBUILD_IMAGE := $(boot)/bzImage bzImage: vmlinux ifeq ($(CONFIG_X86_DECODER_SELFTEST),y) $(Q)$(MAKE) $(build)=arch/x86/tools posttest endif $(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/$@
setup.bin
在这里我们可以看到第一次为$(boot)==arch/x86/boot
目录执行了make
操作
我们进入这个目录看看。这个makefile是如何工作生成bzImage的
我们会发现bzImage依赖于setup.bin和vmlinux.bin
使用cat -n Makefile | head -n 112 | tail -n +107
我们可以查看到
$(obj)/setup.bin: $(obj)/setup.elf FORCE
$(call if_changed,objcopy) $(obj)/compressed/vmlinux: FORCE $(Q)$(MAKE) $(build)=$(obj)/compressed $@
那么我们现在的主要目标是编译目录arch/x86/boot和arch/x86/boot/compressed的代码,构建setup.bin和vmlinux.bin,最后用这两个文件生成bzImage。
第一个目标是定义在arch/x86/boot/Makefile的$(obj)/setup.elf:
我们接着使用cat -n Makefile | head -n 105 | tail -n +103
查看如何生成setup.elf
$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE $(call if_changed,ld)
通过setup.ld来检列所有的setup_objs的目标文件来生成setup.elf
vmlinux.bin
下一个源码文件是arch/x86/boot/header.S
,这个是一个汇编文件
但是我们不能现在就编译它,因为这个目标依赖于下面两个头文件:
$(obj)/header.o: $(obj)/voffset.h $(obj)/zoffset.h
第一个头文件voffset.h是使用sed脚本生成的
包含用nm工具从vmlinux获取的两个地址:
#define VO__end 0xffffffff82ab0000
#define VO__text 0xffffffff81000000
这两个地址是内核的起始和结束地址。
第二个头文件zoffset.h在arch/x86/boot/compressed/Makefile可以看出是依赖于目标vmlinux的
$(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE
$(call if_changed,zoffset)
然后编译目录arch/x86/boot/compressed下的源代码,然后生成vmlinux.bin、vmlinux.bin.bz2,和编译工具mkpiggy。
vmlinux.bin是去掉了调试信息和注释的vmlinux二进制文件,加上了占用了u32(LCTT译注:即4-Byte)的长度信息的vmlinux.bin.all压缩后就是vmlinux.bin.bz2。其中vmlinux.bin.all包含了vmlinux.bin和vmlinux.relocs(LCTT译注:vmlinux的重定位信息),其中vmlinux.relocs是vmlinux经过程序relocs处理之后的vmlinux镜像(见上文所述)。
我们现在已经获取到了这些文件,汇编文件piggy.S将会被mkpiggy生成、然后编译:
MKPIGGY arch/x86/boot/compressed/piggy.S
AS arch/x86/boot/compressed/piggy.o
这个汇编文件会包含经过计算得来的、压缩内核的偏移信息。处理完这个汇编文件,我们就可以看到zoffset生成了:
ZOFFSET arch/x86/boot/zoffset.h
现在zoffset.h和voffset.h已经生成了,arch/x86/boot里的源文件可以继续编译,直到
所有的源代码会被编译,他们最终会被链接到setup.elf
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
最后的两件事是创建包含目录arch/x86/boot/*下的编译过的代码的setup.bin:
objcopy -O binary arch/x86/boot/setup.elf arch/x86/boot/setup.bin
以及从vmlinux生成vmlinux.bin:
objcopy -O binary -R .note -R .comment -S arch/x86/boot/compressed/vmlinux arch/x86/boot/vmlinux.bin
生成bzImage
最后,我们编译主机程序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
实际上bzImage就是把setup.bin和vmlinux.bin连接到一起。最终我们会看到输出结果,就和那些用源码编译过内核的同行的结果一样: