目录

  • 前602行分析
  • make xxx_defconfig 过程
  • Makefile.build 脚本分析
  • make 过程
  • built-in.o 文件编译生成过程
  • make zImage 过程

前几章我们重点讲解了如何移植uboot 到I.MX6U-ALPHA 开发板上,从本章开始我们就开始学习如何移植Linux 内核。同uboot 一样,在具体移植之前,我们先来学习一下Linux 内核的顶层Makefile 文件,因为顶层Makefile 控制着Linux 内核的编译流程。

前602行分析

Linux 的顶层Makefile 和uboot 的顶层Makefile 非常相似,因为uboot 参考了Linux,前602行几乎一样,所以前面部分我们大致看一下就行了。

1、版本号
顶层Makefile 一开始就是Linux 内核的版本号,如下所示:

1 VERSION = 4
2 PATCHLEVEL = 1
3 SUBLEVEL = 15
4 EXTRAVERSION =

可以看出,Linux 内核版本号为4.1.15。

2、MAKEFLAGS 变量
MAKEFLAGS 变量设置如下所示:

16 MAKEFLAGS += -rR --include-dir=$(CURDIR)

3、命令输出
Linux 编译的时候也可以通过“V=1”来输出完整的命令,这个和uboot 一样,相关代码如下所示:

69 ifeq ("$(origin V)", "command line")
70 KBUILD_VERBOSE = $(V)
71 endif
72 ifndef KBUILD_VERBOSE
73 KBUILD_VERBOSE = 0
74 endif
75
76 ifeq ($(KBUILD_VERBOSE),1)
77 quiet =
78 Q =
79 else
80 quiet=quiet_
81 Q = @
82 endif

4、静默输出
Linux 编译的时候使用“make -s”就可实现静默编译,编译的时候就不会打印任何的信息,同uboot 一样,相关代码如下:

87 ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4
88 ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
89 quiet=silent_
90 endif
91 else # make-3.8x
92 ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
93 quiet=silent_
94 endif
95 endif
96
97 export quiet Q KBUILD_VERBOSE

5、设置编译结果输出目录
Linux 编译的时候使用“O=xxx”即可将编译产生的过程文件输出到指定的目录中,相关代码如下

116 ifeq ($(KBUILD_SRC),)
117
118 # OK, Make called in directory where kernel src resides
119 # Do we want to locate output files in a separate directory?
120 ifeq ("$(origin O)", "command line")
121 KBUILD_OUTPUT := $(O)
122 endif

6、代码检查
Linux 也支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层Makefile 中的代码如下:

172 ifeq ("$(origin C)", "command line")
173 KBUILD_CHECKSRC = $(C)
174 endif
175 ifndef KBUILD_CHECKSRC
176 KBUILD_CHECKSRC = 0
177 endif

7、模块编译
Linux 允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层Makefile 中的代码如下:

179 # Use make M=dir to specify directory of external module to build
180 # Old syntax make ... SUBDIRS=$PWD is still supported
181 # Setting the environment variable KBUILD_EXTMOD take precedence
182 ifdef SUBDIRS
183 KBUILD_EXTMOD ?= $(SUBDIRS)
184 endif
185
186 ifeq ("$(origin M)", "command line")
187 KBUILD_EXTMOD := $(M)
188 endif
189
190 # If building an external module we do not care about the all: rule
191 # but instead _all depend on modules
192 PHONY += all
193 ifeq ($(KBUILD_EXTMOD),)
194 _all: all
195 else
196 _all: modules
197 endif
198
199 ifeq ($(KBUILD_SRC),)
200 # building in the source tree
201 srctree := .
202 else
203 ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
204 # building in a subdirectory of the source tree
205 srctree := ..
206 else
207 srctree := $(KBUILD_SRC)
208 endif
209 endif
210 objtree := .
211 src := $(srctree)
212 obj := $(objtree)
213
214 VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
215
216 export srctree objtree VPATH

外部模块编译过程和uboot 也一样,最终导出srctree、objtree 和VPATH 这三个变量的值,其中srctree=.,也就是当前目录,objtree 同样为“.”。

8、设置目标架构和交叉编译器

同uboot 一样,Linux 编译的时候需要设置目标板架构ARCH 和交叉编译器CROSS_COMPILE,在顶层Makefile 中代码如下:

252 ARCH ?= $(SUBARCH)
253 CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)

为了方便,一般直接修改顶层Makefile 中的ARCH 和CROSS_COMPILE,直接将其设置为对应的架构和编译器,比如本教程将ARCH 设置为为arm,CROSS_COMPILE 设置为arm-linux-gnueabihf-,如下所示:

252 ARCH ?= arm
253 CROSS_COMPILE ?= arm-linux-gnueabihf-

设置好以后我们就可以使用如下命令编译Linux 了:

make xxx_defconfig //使用默认配置文件配置Linux
make menuconfig //启动图形化配置界面
make -j16 //编译Linux

9、调用scripts/Kbuild.include 文件

同uboot 一样,Linux 顶层Makefile 也会调用文件scripts/Kbuild.include,顶层Makefile 相应代码如下:

348 # We need some generic definitions (do not try to remake the file).
349 scripts/Kbuild.include: ;
350 include scripts/Kbuild.include

10、交叉编译工具变量设置
顶层Makefile 中其他和交叉编译器有关的变量设置如下:

353 AS = $(CROSS_COMPILE)as
354 LD = $(CROSS_COMPILE)ld
355 CC = $(CROSS_COMPILE)gcc
356 CPP = $(CC) -E
357 AR = $(CROSS_COMPILE)ar
358 NM = $(CROSS_COMPILE)nm
359 STRIP = $(CROSS_COMPILE)strip
360 OBJCOPY = $(CROSS_COMPILE)objcopy
361 OBJDUMP = $(CROSS_COMPILE)objdump

LA、LD、CC 等这些都是交叉编译器所使用的工具。

11、头文件路径变量

顶层Makefile 定义了两个变量保存头文件路径:USERINCLUDE 和LINUXINCLUDE,相关代码如下:

381 USERINCLUDE := \
382 -I$(srctree)/arch/$(hdr-arch)/include/uapi \
383 -Iarch/$(hdr-arch)/include/generated/uapi \
384 -I$(srctree)/include/uapi \
385 -Iinclude/generated/uapi \
386 -include $(srctree)/include/linux/kconfig.h
387
388 # Use LINUXINCLUDE when you must reference the include/ directory.
389 # Needed to be compatible with the O= option
390 LINUXINCLUDE := \
391 -I$(srctree)/arch/$(hdr-arch)/include \
392 -Iarch/$(hdr-arch)/include/generated/uapi \
393 -Iarch/$(hdr-arch)/include/generated \
394 $(if $(KBUILD_SRC), -I$(srctree)/include) \
395 -Iinclude \
396 $(USERINCLUDE)

第381~386 行是USERINCLUDE 是UAPI 相关的头文件路径,第390~396 行是LINUXINCLUDE 是Linux 内核源码的头文件路径。重点来看一下LINUXINCLUDE,其中 srctree=.,hdr-arch=arm,KBUILD_SRC 为空,因此,将USERINCLUDE 和LINUXINCLUDE 展开以后为:

USERINCLUDE := \
-I./arch/arm/include/uapi \
-Iarch/arm/include/generated/uapi \
-I./include/uapi \
-Iinclude/generated/uapi \
-include ./include/linux/kconfig.h
LINUXINCLUDE := \
-I./arch/arm/include \
-Iarch/arm/include/generated/uapi \
-Iarch/arm/include/generated \
-Iinclude \
-I./arch/arm/include/uapi \
-Iarch/arm/include/generated/uapi \
-I./include/uapi \
-Iinclude/generated/uapi \
-include ./include/linux/kconfig.h

12、导出变量

顶层Makefile 会导出很多变量给子Makefile 使用,导出的这些变量如下:

417 export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION
418 export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC
419 export CPP AR NM STRIP OBJCOPY OBJDUMP
420 export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
421 export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
422
423 export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
424 export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN
425 export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
426 export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE
427 export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL
428 export KBUILD_ARFLAGS

make xxx_defconfig 过程

第一次编译Linux 之前都要使用“make xxx_defconfig”先配置Linux 内核,在顶层Makefile中有“%config”这个目标,如下所示:

490 config-targets := 0
491 mixed-targets := 0
492 dot-config := 1
493
494 ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
495 ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
496 dot-config := 0
497 endif
498 endif
499
500 ifeq ($(KBUILD_EXTMOD),)
501 ifneq ($(filter config %config,$(MAKECMDGOALS)),)
502 config-targets := 1
503 ifneq ($(words $(MAKECMDGOALS)),1)
504 mixed-targets := 1
505 endif
506 endif
507 endif
508
509 ifeq ($(mixed-targets),1)
510 # =================================================================
511 # We're called with mixed targets (*config and build targets).
512 # Handle them one by one.
513
514 PHONY += $(MAKECMDGOALS) __build_one_by_one
515
516 $(filter-out __build_one_by_one, $(MAKECMDGOALS)): __build_one_by_one
517 @:
518
519 __build_one_by_one:
520 $(Q)set -e; \
521 for i in $(MAKECMDGOALS); do \
522 $(MAKE) -f $(srctree)/Makefile $$i; \
523 done
524
525 else
526 ifeq ($(config-targets),1)
527 # ================================================================
528 # *config targets only - make sure prerequisites are updated, and
529 # descend in scripts/kconfig to make the *config target
530
531 # Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.
532 # KBUILD_DEFCONFIG may point out an alternative default
533 # configuration used for 'make defconfig'
534 include arch/$(SRCARCH)/Makefile
535 export KBUILD_DEFCONFIG KBUILD_KCONFIG
536
537 config: scripts_basic outputmakefile FORCE
538 $(Q)$(MAKE) $(build)=scripts/kconfig $@
539
540 %config: scripts_basic outputmakefile FORCE
541 $(Q)$(MAKE) $(build)=scripts/kconfig $@
542
543 else
563 endif # KBUILD_EXTMOD
......

第490~507 行和uboot 一样,都是设置定义变量config-targets、mixed-targets 和dot-config
的值,最终这三个变量的值为:

config-targets= 1
mixed-targets= 0
dot-config= 1

因为config-targets=1,因此第534 行~541 行成立。第534 行引用arch/arm/Makefile 这个文件,这个文件很重要,因为zImage、uImage 等这些文件就是由arch/arm/Makefile 来生成的。

第535 行导出变量KBUILD_DEFCONFIG KBUILD_KCONFIG。

第537 行,没有目标与之匹配,因此不执行。

第540 行,“make xxx_defconfig”与目标“%config”匹配,因此执行。“%config”依赖scripts_basic、outputmakefile 和FORCE,“%config”真正有意义的依赖就只有scripts_basic,scripts_basic 的规则如下:

448 scripts_basic:
449 $(Q)$(MAKE) $(build)=scripts/basic
450 $(Q)rm -f .tmp_quiet_recordmcount

build 定义在文件scripts/Kbuild.include 中,值为build := -f $(srctree)/scripts/Makefile.build
obj,因此将示例代码35.5.1.2 展开就是:

scripts_basic:
@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
@rm -f . tmp_quiet_recordmcount //也可以没有@

接着回到示例代码35.5.1.1 的目标“%config”处,内容如下:

%config: scripts_basic outputmakefile FORCE$(Q)$(MAKE) $(build)=scripts/kconfig $@

将命令展开就是:

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

Makefile.build 脚本分析

从上一小节可知,“make xxx_defconfig“配置Linux 的时候如下两行命令会执行脚本
scripts/Makefile.build:

@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

我们依次来分析一下:

1、scripts_basic 目标对应的命令

scripts_basic 目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/basic。打开文件scripts/Makefile.build,有如下代码:

41 # The filename Kbuild has precedence over Makefile
42 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
43 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
44 include $(kbuild-file)

将kbuild-dir 展开后为:

kbuild-dir=./scripts/basic

将kbuild-file 展开后为:

kbuild-file= ./scripts/basic/Makefile

最后将59 行展开,即:

include ./scripts/basic/Makefile

继续分析scripts/Makefile.build,如下代码:

94 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
95 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
96 $(subdir-ym) $(always)
97 @:

__build 是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指
定目标,所以会使用到默认目标__build。在顶层Makefile 中,KBUILD_BUILTIN 为1,
KBUILD_MODULES 为空,因此展开后目标__build 为:

__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:

可以看出目标__build 有5 个依赖:builtin-target、lib-target、extra-y、subdir-ym 和always。
这5 个依赖的具体内容如下:

builtin-target =
lib-target =
extra-y =
subdir-ym =
always = scripts/basic/fixdep scripts/basic/bin2c

只有always 有效,因此__build 最终为:

__build: scripts/basic/fixdep scripts/basic/bin2c
@:

__build 依赖于scripts/basic/fixdep 和scripts/basic/bin2c,所以要先将scripts/basic/fixdep 和scripts/basic/bin2c.c 这两个文件编译成fixdep 和bin2c。

综上所述,scripts_basic 目标的作用就是编译出scripts/basic/fixdep 和scripts/basic/bin2c 这两个软件。

2、%config 目标对应的命令
%config 目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig,此命令会使用到的各个变量值如下:

src= scripts/kconfig
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./scripts/kconfig/Makefile

可以看出,Makefile.build 会读取scripts/kconfig/Makefile 中的内容,此文件有如下所示内容:

113 %_defconfig: $(obj)/conf
114 $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

目标%_defconfig 与xxx_defconfig 匹配,所以会执行这条规则,将其展开就是:

%_defconfig: scripts/kconfig/conf
@ scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig

%_defconfig 依赖scripts/kconfig/conf,所以会编译scripts/kconfig/conf.c 生成conf 这个软件。此软件就会将%_defconfig 中的配置输出到.config 文件中,最终生成Linux kernel 根目录下的.config 文件。

make 过程

使用命令“make xxx_defconfig”配置好Linux 内核以后就可以使用“make”或者“make all”命令进行编译。顶层Makefile 有如下代码:

125 PHONY := _all
126 _all:
......
192 PHONY += all
193 ifeq ($(KBUILD_EXTMOD),)
194 _all: all
195 else
196 _all: modules
197 endif
......
608 all: vmlinux

第126 行,_all 是默认目标,如果使用命令“make”编译Linux 的话此目标就会被匹配。
第193 行,如果KBUILD_EXTMOD 为空的话194 行的代码成立。
第194 行,默认目标_all 依赖all。
第608 行,目标all 依赖vmlinux,所以接下来的重点就是vmlinux!
顶层Makefile 中有如下代码:

904 # Externally visible symbols (used by link-vmlinux.sh)
905 export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
906 export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
907 export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
908 export LDFLAGS_vmlinux
909 # used by scripts/pacmage/Makefile
910 export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools virt)
911
912 vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
913
914 # Final link of vmlinux
915 cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
916 quiet_cmd_link-vmlinux = LINK $@
917
918 # Include targets which we want to
919 # execute if the rest of the kernel build went well.
920 vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
921 ifdef CONFIG_HEADERS_CHECK
922 $(Q)$(MAKE) -f $(srctree)/Makefile headers_check
923 endif
924 ifdef CONFIG_SAMPLES
925 $(Q)$(MAKE) $(build)=samples
926 endif
927 ifdef CONFIG_BUILD_DOCSRC
928 $(Q)$(MAKE) $(build)=Documentation
929 endif
930 ifdef CONFIG_GDB_SCRIPTS
931 $(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.py
932 endif
933 +$(call if_changed,link-vmlinux)

从第920 行可以看出目标vmlinux 依赖scripts/link-vmlinux.sh $(vmlinux-deps) FORCE。第912 行定义了vmlinux-deps,值为:
vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

第905 行,KBUILD_VMLINUX_INIT= $(head-y) $(init-y)。

第906 行,KBUILD_VMLINUX_MAIN = $(core-y) $(libs-y) $(drivers-y) $(net-y)。

第907 行,KBUILD_LDS= arch/ $ (SRCARCH)/kernel/vmlinux.lds,其中SRCARCH=arm,因此KBUILD_LDS= arch/arm/kernel/vmlinux.lds。

综上所述,vmlinux 的依赖为:scripts/link-vmlinux.sh、$ (head-y) 、$ (init-y)、$ (core-y) 、$ (libs-y) 、$ (drivers-y) 、$ (net-y)、arch/arm/kernel/vmlinux.lds 和FORCE。

第933 行的命令用于链接生成vmlinux。

重点来看一下$ (head-y) 、$ (init-y)、$ (core-y) 、$ (libs-y) 、$ (drivers-y) 和$ (net-y)这六个变量的值。

1、head-y
head-y 定义在文件arch/arm/Makefile 中,内容如下:

135 head-y := arch/arm/kernel/head$(MMUEXT).o

当不使能MMU 的话MMUEXT=-nommu,如果使能MMU 的话为空,因此head-y 最终的值为:

head-y = arch/arm/kernel/head.o

2、init-y、drivers-y 和net-y
在顶层Makefile 中有如下代码:

558 init-y := init/
559 drivers-y := drivers/ sound/ firmware/
560 net-y := net/
......
896 init-y := $(patsubst %/, %/built-in.o, $(init-y))
898 drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
899 net-y := $(patsubst %/, %/built-in.o, $(net-y))

从示例代码35.5.3.4 可知,init-y、libs-y、drivers-y 和net-y 最终的值为:

init-y = init/built-in.o
drivers-y = drivers/built-in.o sound/built-in.o firmware/built-in.o
net-y = net/built-in.o

3、libs-y

libs-y 基本和init-y 一样,在顶层Makefile 中存在如下代码:
561 libs-y := lib/
......
900 libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
901 libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
902 libs-y := $(libs-y1) $(libs-y2)

根据示例代码35.5.3.5 可知,libs-y 应该等于“lib.a built-in.o”,这个只正确了一部分!因为
在arch/arm/Makefile 中会向libs-y 中追加一些值,代码如下:

286 libs-y := arch/arm/lib/ $(libs-y)

arch/arm/Makefile 将libs-y 的值改为了:arch/arm/lib $(libs-y),展开以后为:

libs-y = arch/arm/lib lib/

因此根据示例代码35.5.3.5 的第900~902 行可知,libs-y 最终应该为:

libs-y = arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o

4、core-y
core-y 和init-y 也一样,在顶层Makefile 中有如下代码:

532 core-y := usr/
......
887 core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/

但是在arch/arm/Makefile 中会对core-y 进行追加,代码如下:

269 core-$(CONFIG_FPE_NWFPE)     += arch/arm/nwfpe/
270 core-$(CONFIG_FPE_FASTFPE)  += $(FASTFPE_OBJ)
271 core-$(CONFIG_VFP)          += arch/arm/vfp/
272 core-$(CONFIG_XEN)          += arch/arm/xen/
273 core-$(CONFIG_KVM_ARM_HOST) += arch/arm/kvm/
274 core-$(CONFIG_VDSO)         += arch/arm/vdso/
275
276 # If we have a machine-specific directory, then include it in the build.
277 core-y      += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
278 core-y      += arch/arm/probes/
279 core-y      += arch/arm/net/
280 core-y      += arch/arm/crypto/
281 core-y      += arch/arm/firmware/
282 core-y      += $(machdirs) $(platdirs)

第269~274 行根据不同的配置向core-y 追加不同的值,比如使能VFP 的话就会在.config中有CONFIG_VFP=y 这一行,那么core-y 就会追加“arch/arm/vfp/”。
第277~282 行就是对core-y 直接追加的值。
在顶层Makefile 中有如下一行:

897 core-y := $(patsubst %/, %/built-in.o, $(core-y))

经过上述代码的转换,最终core-y 的值为:

core-y = usr/built-in.o                 arch/arm/vfp/built-in.o \arch/arm/vdso/built-in.o       arch/arm/kernel/built-in.o \arch/arm/mm/built-in.o          arch/arm/common/built-in.o \arch/arm/probes/built-in.o      arch/arm/net/built-in.o \arch/arm/crypto/built-in.o     arch/arm/firmware/built-in.o \arch/arm/mach-imx/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

关于head-y 、init-y、core-y 、libs-y 、drivers-y 和net-y 这6 个变量就讲解到这里。这些变量都是一些built-in.o 或.a 等文件,这个和uboot 一样,都是将相应目录中的源码文件进行编译,然后在各自目录下生成built-in.o 文件,有些生成了.a 库文件。最终将这些built-in.o 和.a 文件进行链接即可形成ELF 格式的可执行文件,也就是vmlinux!但是链接是需要链接脚本的,vmlinux 的依赖arch/arm/kernel/vmlinux.lds 就是整个Linux 的链接脚本。

示例代码35.5.3.2 第933 行的命令“+(callifchanged,link−vmlinux)”表示将(call if_changed,link-vmlinux) ”表示将(callifc​hanged,link−vmlinux)”表示将(call if_changed,link-vmlinux)的结果作为最终生成vmlinux 的命令,前面的“+”表示该命令结果不可忽略。$(call if_changed,link-vmlinux)是调用函数if_changed,link-vmlinux 是函数if_changed 的参数,函数if_changed 定义在文件scripts/Kbuild.include 中,如下所示:

247 if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
248 @set -e; \
249 $(echo-cmd) $(cmd_$(1)); \
250 printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)

any-prereq 用于检查依赖文件是否有变化,如果依赖文件有变化那么any-prereq 就不为空,否则就为空。arg-check 用于检查参数是否有变化,如果没有变化那么arg-check 就为空。
第248 行,“@set -e”告诉bash,如果任何语句的执行结果不为true(也就是执行出错)的话就直接退出。
第249 行,$ (echo-cmd)用于打印命令执行过程,比如在链接vmlinux 的时候就会输出“LINK vmlinux”。$ (cmd_$ (1))中的$ (1)表示参数,也就是link-vmlinux,因此$ (cmd_$(1))表示执行cmd_link-vmlinux 的内容。cmd_link-vmlinux 在顶层Makefile 中有如下所示定义:

914 # Final link of vmlinux
915 cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
916 quiet_cmd_link-vmlinux = LINK $@

第915 行就是cmd_link-vmlinux 的值,其中CONFIG_SHELL=/bin/bash,$<表示目标vmlinux的第一个依赖文件,根据示例代码35.5.3.2 可知,这个文件为scripts/link-vmlinux.sh。LD= arm-linux-gnueabihf-ld -EL,LDFLAGS 为空。LDFLAGS_vmlinux 的值由顶层Makefile 和arch/arm/Makefile 这两个文件共同决定,最终LDFLAGS_vmlinux=-p --no-undefined -X --pic-veneer --build-id。因此cmd_link-vmlinux 最终的值为:

cmd_link-vmlinux = /bin/bash scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --no-undefined -X --pic-veneer --build-id

cmd_link-vmlinux 会调用scripts/link-vmlinux.sh 这个脚本来链接出vmlinux!在link-vmlinux.sh 中有如下所示代码:

51 vmlinux_link()
52 {53 local lds="${objtree}/${KBUILD_LDS}"
54
55 if [ "${SRCARCH}" != "um" ]; then
56 ${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \
57 -T ${lds} ${KBUILD_VMLINUX_INIT} \
58 --start-group ${KBUILD_VMLINUX_MAIN} --end-group ${1}
59 else
60 ${CC} ${CFLAGS_vmlinux} -o ${2} \
61 -Wl,-T,${lds} ${KBUILD_VMLINUX_INIT} \
62 -Wl,--start-group \
63 ${KBUILD_VMLINUX_MAIN} \
64 -Wl,--end-group \
65 -lutil ${1}
66 rm -f linux
67 fi
68 }
......
216 info LD vmlinux
217 vmlinux_link "${kallsymso}" vmlinux

vmliux_link 就是最终链接出vmlinux 的函数,第55 行判断SRCARCH 是否等于“um”,如果不相等的话就执行56~58 行的代码。因为SRCARCH=arm,因此条件成立,执行56~58 行的代码。这三行代码就应该很熟悉了!就是普通的链接操作,连接脚本为lds= ./arch/arm/kernel/vmlinux.lds ,需要链接的文件由变量KBUILD_VMLINUX_INIT 和KBUILD_VMLINUX_MAIN 来决定,这两个变量在示例代码35.5.3.2 中已经讲解过了。

第217 行调用vmlinux_link 函数来链接出vmlinux。

使用命令“make V=1”编译Linux,会有如图35.5.3.1 所示的编译信息:


至此我们基本理清了make 的过程,重点就是将各个子目录下的built-in.o、.a 等文件链接在一起,最终生成vmlinux 这个ELF 格式的可执行文件。链接脚本为arch/arm/kernel/vmlinux.lds,链接过程是由shell 脚本scripts/link-vmlinux.s 来完成的。接下来的问题就是这些子目录下的built-in.o、.a 等文件又是如何编译出来的呢?

built-in.o 文件编译生成过程

根据示例代码35.5.3.2 第920 行可知,vmliux 依赖vmlinux-deps,而vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN),KBUILD_LDS是链接脚本,这里不考虑,剩下的KBUILD_VMLINUX_INIT 和KBUILD_VMLINUX_MAIN 就是各个子目录下的built-in.o、.a 等文件。最终vmlinux-deps 的值如下:

除了arch/arm/kernel/vmlinux.lds 以外,其他都是要编译链接生成的。在顶层Makefile 中有如下代码:

937 $(sort $(vmlinux-deps)): $(vmlinux-dirs) ;

sort 是排序函数,用于对vmlinux-deps 的字符串列表进行排序,并且去掉重复的单词。可以看出vmlinux-deps 依赖vmlinux-dirs,vmlinux-dirs 也定义在顶层Makefile 中,定义如下:

889 vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
890 $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
891 $(net-y) $(net-m) $(libs-y) $(libs-m)))

vmlinux-dirs 看名字就知道和目录有关,此变量保存着生成vmlinux 所需源码文件的目录,值如下:

在顶层Makefile 中有如下代码:

946 $(vmlinux-dirs): prepare scripts
947 $(Q)$(MAKE) $(build)=$@

目标vmlinux-dirs 依赖prepare 和scripts,这两个依赖不去浪费时间了,重点看一下第947行的命令。build 前面已经说了,值为“-f ./scripts/Makefile.build obj”,因此将947 行的命令展开就是:

@ make -f ./scripts/Makefile.build obj=$@

$@表示目标文件,也就是vmlinux-dirs 的值,将vmlinux-dirs 中的这些目录全部带入到命令中,结果如下:

@ make -f ./scripts/Makefile.build obj=init
@ make -f ./scripts/Makefile.build obj=usr
@ make -f ./scripts/Makefile.build obj=arch/arm/vfp
@ make -f ./scripts/Makefile.build obj=arch/arm/vdso
@ make -f ./scripts/Makefile.build obj=arch/arm/kernel
@ make -f ./scripts/Makefile.build obj=arch/arm/mm
@ make -f ./scripts/Makefile.build obj=arch/arm/common
@ make -f ./scripts/Makefile.build obj=arch/arm/probes
@ make -f ./scripts/Makefile.build obj=arch/arm/net
@ make -f ./scripts/Makefile.build obj=arch/arm/crypto
@ make -f ./scripts/Makefile.build obj=arch/arm/firmware
@ make -f ./scripts/Makefile.build obj=arch/arm/mach-imx
@ make -f ./scripts/Makefile.build obj=kernel
@ make -f ./scripts/Makefile.build obj=mm
@ make -f ./scripts/Makefile.build obj=fs
@ make -f ./scripts/Makefile.build obj=ipc
@ make -f ./scripts/Makefile.build obj=security
@ make -f ./scripts/Makefile.build obj=crypto
@ make -f ./scripts/Makefile.build obj=block
@ make -f ./scripts/Makefile.build obj=drivers
@ make -f ./scripts/Makefile.build obj=sound
@ make -f ./scripts/Makefile.build obj=firmware
@ make -f ./scripts/Makefile.build obj=net
@ make -f ./scripts/Makefile.build obj=arch/arm/lib
@ make -f ./scripts/Makefile.build obj=lib

这些命令运行过程其实都是一样的,我们就以“@ make -f ./scripts/Makefile.build obj=init”这个命令为例,讲解一下详细的运行过程。这里又要用到Makefile.build 这个脚本了,此脚本默认目标为__build,这个在35.5.2 小节已经讲过了,我们再来看一下,__build 目标对应的规则如下:

94 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
95 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
96 $(subdir-ym) $(always)
97 @:

当只编译Linux 内核镜像文件,也就是使用“make zImage ”编译的时候,
KBUILD_BUILTIN=1,KBUILD_MODULES 为空。“make”命令是会编译所有的东西,包括Linux内核镜像文件和一些模块文件。如果只编译Linux 内核镜像的话,__build 目标简化为:

__build: $(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:

重点来看一下builtin-target 这个依赖,builtin-target 同样定义在文件scripts/Makefile.build中,定义如下:

86 ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)
87 builtin-target := $(obj)/built-in.o
88 endif

第87 行就是builtin-target 变量的值,为“$(obj)/built-in.o”,这就是这些built-in.o 的来源了。
要生成built-in.o,要求obj-y、obj-m、obj-、subdir-m 和lib-target 这些变量不能全部为空。最后一个问题:built-in.o 是怎么生成的?在文件scripts/Makefile.build 中有如下代码:

325 #
326 # Rule to compile a set of .o files into one .o file
327 #
328 ifdef builtin-target
329 quiet_cmd_link_o_target = LD $@
330 # If the list of objects to link is empty, just create an empty built-in.o
331 cmd_link_o_target = $(if $(strip $(obj-y)),\
332 $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
333 $(cmd_secanalysis),\
334 rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)
335
336 $(builtin-target): $(obj-y) FORCE
337 $(call if_changed,link_o_target)
338
339 targets += $(builtin-target)
340 endif # builtin-target

第336 行的目标就是builtin-target,依赖为obj-y,命令为“KaTeX parse error: Double subscript at position 24: …_changed,link_o_̲target)”,也就是调用函…(1)所对应的命令($(1)就是函数的第1 个参数),在这里就是调用
cmd_link_o_target 所对应的命令,也就是第331~334 行的命令。cmd_link_o_target 就是使用LD将某个目录下的所有.o 文件链接在一起,最终形成built-in.o。

make zImage 过程

1、vmlinux、Image,zImage、uImage 的区别
前面几小节重点是讲vmlinux 是如何编译出来的,vmlinux 是ELF 格式的文件,但是在实际中我们不会使用vmlinux,而是使用zImage 或uImage 这样的Linux 内核镜像文件。那么vmlinux、zImage、uImage 他们之间有什么区别呢?
①、vmlinux 是编译出来的最原始的内核文件,是未压缩的,比如正点原子提供的Linux 源码编译出来的vmlinux 差不多有16MB,如图35.5.5.1 所示:

②、Image 是Linux 内核镜像文件,但是Image 仅包含可执行的二进制数据。Image 就是使用objcopy 取消掉vmlinux 中的一些其他信息,比如符号表什么的。但是Image 是没有压缩过的,Image 保存在arch/arm/boot 目录下,其大小大概在12MB 左右如图35.5.5.2 所示:

相比vmlinux 的16MB,Image 缩小到了12MB。
③、zImage 是经过gzip 压缩后的Image,经过压缩以后其大小大概在6MB 左右,如图35.5.5.3 所示:

④、uImage 是老版本uboot 专用的镜像文件,uImag 是在zImage 前面加了一个长度为64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。但是新的uboot 已经支持了zImage 启动!所以已经很少用到uImage 了,除非你用的很古老的uboot。

使用“make”、“make all”、“make zImage”这些命令就可以编译出zImage 镜像,在arch/arm/Makefile 中有如下代码:

310 BOOT_TARGETS = zImage Image xipImage bootpImage uImage
......
315 $(BOOT_TARGETS): vmlinux
316 $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

第310 行,变量BOOT_TARGETS 包含zImage,Image,xipImage 等镜像文件。
第315 行,BOOT_TARGETS 依赖vmlinux,因此如果使用“make zImage”编译的Linux 内
核的话,首先肯定要先编译出vmlinux。
第316 行,具体的命令,比如要编译zImage,那么命令展开以后如下所示:

@ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/zImage

看来又是使用scripts/Makefile.build 文件来完成vmlinux 到zImage 的转换。
关于Linux 顶层Makefile 就讲解到这里,基本和uboot 的顶层Makefile 一样,重点在于vmlinux 的生成。最后将vmlinux 压缩成我们最常用的zImage 或uImage 等文件。

Linux 内核顶层Makefile 详解相关推荐

  1. 【正点原子Linux连载】第三十五章 Linux内核顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  2. linux内核顶层Makefile详解

    文章目录 一.linux内核获取 二.linux内核初次编译 三.linux工程目录分析 1.获取源码 2.目录介绍 1.总体浏览 2.arch 目录 3.block 目录 4.crypto 目录 5 ...

  3. Linux 内核顶层 Makefile 详解

    Linux 内核获取 Linux 由 Linux 基金会管理与发布, Linux 官网为 https://www.kernel.org,所以你想获取最新的 Linux 版本就可以在这个网站上下载 最新 ...

  4. 【正点原子Linux连载】第三十一章 U-Boot顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  5. U-Boot 顶层 Makefile 详解

    U-Boot 顶层 Makefile 详解 1.U-Boot 工程目录分析 我们在分析 uboot 源码之前一定要 先在 Ubuntu 中编译一下 uboot 源码,因为编译过程会生成一些文件,而生成 ...

  6. Linux内核Thermal框架详解十四、Thermal Governor(4)

    本文部分内容参考 万字长文 | Thermal框架源码剖析, Linux Thermal机制源码分析之框架概述_不捡风筝的玖伍贰柒的博客-CSDN博客, "热散由心静,凉生为室空" ...

  7. Linux内核Thermal框架详解十三、Thermal Governor(3)

    接前一篇文章Linux内核Thermal框架详解十二.Thermal Governor(2) 二.具体温控策略 上一篇文章介绍并详细分析了bang_bang governor的源码.本文介绍第2种温控 ...

  8. Linux内核Thermal框架详解十二、Thermal Governor(2)

    本文部分内容参考 万字长文 | Thermal框架源码剖析, Linux Thermal机制源码分析之框架概述_不捡风筝的玖伍贰柒的博客-CSDN博客, "热散由心静,凉生为室空" ...

  9. cgroup使用举例和linux内核源码详解

    cgroup的原理其实并不复杂,用法也比较简单.但是涉及的内核数据结构真的复杂,错综复杂的数据结构感觉才是cgroup真正的难点.本文结合个人学习cgroup源码的心得,尽可能以举例的形式,总结cgr ...

最新文章

  1. PCL中outofcore模块---基于核外八叉树的大规模点云的显示
  2. /var/spool/clientmqueue文件分析
  3. Java总结篇系列:Java多线程(二)
  4. 线性代数的学习及相关资源
  5. 大数据产业不只是建设数据中心
  6. python 求组合数最快方法_Python-生成符合条件的大集合组合的最有效方法?
  7. 手动添加ubuntu服务
  8. Linux 命令之 env -- 显示系统的环境变量,定义执行命令时的环境变量
  9. 每次获取不同的图片按顺序从1-xxx
  10. git 解决ahead behind分叉以及删除远端commit
  11. sshd启动报错解决:sshd re-exec requires execution with an absolute path
  12. android之下载文件
  13. make的时候会报错g++: Command not found
  14. 通达OA2013完美平台补丁以及手机短信服务配置
  15. Js~Boxy中的复杂弹出框
  16. c语言程序负数,C语言中负数怎么表示的?
  17. 查看电脑安装的Office是32位还是64位的
  18. 趁年轻,别把自己关在笼子里
  19. [源码和文档分享]基于C语言实现的汽车牌照的快速查询
  20. linux md文件 编辑,用Vim写md文档的简单姿势

热门文章

  1. myBatis xml if、where、if-else?、foreach 心得
  2. SpringMVC连接多数据源配置
  3. centos linux 系统日常管理4 scp,rsync,md5sum,sha1sum,strace ,find Rsync 常见错误及解决方法 第十七节课...
  4. SimpleDateFormat解析的时候字符串过长问题
  5. 好文章,被架构师秒杀之后
  6. android获取网络图片
  7. SQL Server Insert 操作效率(堆表 VS 聚集索引表)
  8. 在ubuntu上搭建LNMP服务器
  9. LINUX framebuffer
  10. 大力智能台灯与飞利浦台灯 智能调光功能体验