目录
- 前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 = https://www.it610.com/article/$(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= https://www.it610.com/article/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.oarch/arm/vfp/built-in.o \
arch/arm/vdso/built-in.oarch/arm/kernel/built-in.o \
arch/arm/mm/built-in.oarch/arm/common/built-in.o \
arch/arm/probes/built-in.oarch/arm/net/built-in.o \
arch/arm/crypto/built-in.oarch/arm/firmware/built-in.o \
arch/arm/mach-imx/built-in.okernel/built-in.o\
mm/built-in.ofs/built-in.o \
ipc/built-in.osecurity/built-in.o \
crypto/built-in.oblock/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 行的命令“+ ( c a l l i f c h a n g e d , l i n k ? v m l i n u x ) ” 表 示 将 (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
【正点IMX6ULL系统移植|Linux 内核顶层Makefile 详解】看来又是使用scripts/Makefile.build 文件来完成vmlinux 到zImage 的转换。
关于Linux 顶层Makefile 就讲解到这里,基本和uboot 的顶层Makefile 一样,重点在于vmlinux 的生成。最后将vmlinux 压缩成我们最常用的zImage 或uImage 等文件。
推荐阅读
- Linux|【Linux篇】第十九篇——网络套接字编程(二)(TCP套接字的编写+多进程版本+多线程版本+线程池版本)
- Linux|EulerOS配置yum源以及安装内核头文件
- 数据库|MySQL 配置主从复制实践
- linux|阿里前端面试题(最全),持续更新中
- 程序设计|985北京航空航天大学软件考研改考!数据结构+软件工程+操作系统
- centos常用命令|centos常用命令_Linux常用命令
- python|UNIX文件系统命令
- Linux|Linux文件操作、文件夹操作详解(linux中如何创建文件删除文件及移动、复制、查看、编辑等常用操作)
- linux|linux 终端显示emoji,如何从Linux命令行快速搜索表情符号 | MOS86