参考
一次实验引起的故事 – kernel build system探索—vmlinux是如何炼成的– kernel makefilepython深度探索Linux操做系统:系统构建和原理解析.pdflinux
在前面的博文中,咱们先是为本身的Ubuntu安装了一套内核源码树,而后为了方便进行嵌入式交叉编译,咱们又为arm板子构建了一套源码树。
那么如今咱们已经知道如何本身的电脑上去构建、安装一个定制化的Linux内核,可是咱们仍是要在唠叨一些。
当你在内核源码路径里敲下make时究竟发生什么
ios
当咱们刚刚开始接触内核代码时,毫无头绪,这时候Makefile是每每是咱们打开的第一个文件,这个makefile是Linux内核代码的根makefile,内核构建就始于此处。是的,它的内容不少,可是若是你已经读过内核源代码,你就会发现每一个包含代码的目录都有一个本身的Makefile。固然了,咱们不会去描述每一个代码文件是怎么编译连接的,因此咱们将只会挑选一些通用的例子来讲明问题。而你不会在这里找到构建内核的文档、如何整洁内核代码、tags的生成和交叉编译相关的说明,等等。git
咱们仅仅将从make开始,使用标准的内核配置文件,一直到生成了内核镜像bzImage或者zImage结束。
固然在着以前咱们须要了解,咱们make到底是要构建一个什么样的目标,我想这个github
对于Linux内核,编译能够生成不一样格式的映像文件,例如:sql
make zImag make uImage
zImage是ARM Linux经常使用的一种压缩映像文件,uImage是U-boot专用的映像文件,它是在zImage以前加上一个长度为0x40的“头”,说明这个映像文件的类型、加载位置、生成时间、大小等信息。换句话说,若是直接从uImage的0x40位置开始执行,zImage和uImage没有任何区别。另外,Linux2.4内核不支持uImage,Linux2.6内核加入了不少对嵌入式系统的支持,可是uImage的生成也须要设置。shell
几种linux内核文件的区别:api
一、vmlinux 编译出来的最原始的内核文件,未压缩。
二、zImage 是vmlinux通过gzip压缩后的文件。适用于小内核
三、bzImage bz表示“big zImage”,不是用bzip2压缩的。二者的不一样之处在于,zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。若是内核比较小,那么采用zImage或bzImage都行,若是比较大应该用bzImage。适用于大内核
四、uImage U-boot专用的映像文件,它是在zImage以前加上一个长度为0x40的tag。
五、vmlinuz 是bzImage/zImage文件的拷贝或指向bzImage/zImage的连接。
六、initrd 是“initial ramdisk”的简写。通常被用来临时的引导硬件到实际内核vmlinuz可以接管并继续引导的状态。安全
vmlinux是未压缩的内核,是make工做编译出的原始内核,vmlinuz是vmlinux的压缩文件。ruby
vmlinux 是ELF文件,即编译出来的最原始的文件。
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是“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 的,哪一种类型,加载内存中的哪一个位置, 入口点在内存的那个位置以及映象名是什么
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。
接下来咱们会看到不少ifeq条件判断语句,它们负责检查传递给make的参数。内核的Makefile提供了一个特殊的编译选项makehelp,这个选项能够生成全部的可用目标和一些能传给make的有效的命令行参数。
举个例子,首先出现的就是-V
,那么make V=1
会在构建过程当中输出详细的编译信息,第一个ifeq就是检查传递给make的V=n选项。
使用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
的控制
# 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
下一个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)。
下一个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会用来编译外部模块
使用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
紧接着系统检查了变量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的值,这个变量表明了当前的系统架构(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
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
,下一步系统会设置它,默认状况下就是.config,这个文件是否是很熟悉,它就是咱们make menuconfig后的那个.config配置文件,里面写入咱们内核编译的全部信息
使用cat -n Makefile | head -n 292 | tail -n +289
查看
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
使用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_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 <whatever> 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。
接着下一步操做是引入下面的文件:
使用查看 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,那咱们就找找这个神奇的东西是怎么产生的吧。
这里咱们须要从内核根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。
同时咱们能够发现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
内核中有这么多目录,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定义在内核的根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: $(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all
接着是archscripts
archscripts: scripts_basic $(Q)$(MAKE) $(build)=arch/x86/tools relocs
而后是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就完成了它的工做。
vmlinux-dirs也依赖于第二个目标scripts,它会编译接下来的几个程序:filealias,mk_elfconfig,modpost等等。
与prepare相似,因此咱们在这里就不细讲了。
以后,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的文件。 那这个文件,是怎么生成的呢?
那么问题来了,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就是压缩了的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/$@
在这里咱们能够看到第一次为$(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
下一个源码文件是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
最后,咱们编译主机程序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链接到一块儿。最终咱们会看到输出结果,就和那些用源码编译过内核的同行的结果同样: