(一)前言
kbuild,即 kernel build,用于编译 Linux 内核文件。 kbuild 对 makefile 进行了功能上的扩充,使其在编译内核文件时更加高效,简洁。大部分内核中的 Makefile 都是使用 Kbuild 组织结构的 kbuild Makefile。
Kbuild 执行的几个步骤(大致):
- 根据内核配置生成文件 .config
- 将内核的版本号存储在 include/linux/version.h
- 生成指向 include/asm-$(ARCH) 的符号链接
- 更新所有编译所需的文件: 附加的文件由 arch/$(ARCH)/Makefile 指定。
- 递归向下访问所有在下列变量中列出的目录: init-* core* drivers-* net-* libs-*,并编译生成目标文件。这些变量的值可以在 arch/$(ARCH)/Makefile 中扩充。
- 连接所有的目标文件,在源代码树顶层目录中生成 vmlinux。最先联接是在 head-y 中列出的文件,该变量由 arch/$(ARCH)/Makefile 赋值。
- 最后完成具体架构的特殊要求,并生成最终的启动镜像。(包含生成启动指令,准备 initrd 镜像或类似文件)
(二)Linux内核Makefile文件组成
Linux 内核的 Makefile 分为 5 个部分 :
名称 | 描述 |
顶层 Makefile | 它是所有Makefile文件的核心,从总体上控制着内核的编译、连接 |
arch/$(ARCH)/Makefile | 对应体系结构的Makefile,它用来决定哪些体系结构相关的文件参与内核的生成,并提供一些规则来生成特定格式的内核映像 |
scripts/Makefile.* | Makefile公用的通用规则、脚本等 |
子目录kbuild Makefiles | 各级子目录的Makefile相对简单,被上一层Makefile.build调用来编译当前目录的文件。 |
顶层.config | 配置文件,配置内核时生成。所有的Makefile文件(包括顶层目录和各级子目录)都是根据.config来决定使用哪些文件的 |
顶层 Makefile 阅读的.config 文件,而该文件是由内核配置程序生成的。顶层 Makefile 负责制作: vmlinux(内核文件)与模块(任何模块文件)。制作的过程主要是通过递归向下访问子目录的形式完成。并根据内核配置文件确定访问哪些子目录。顶层 Makefile 要原封不动的包含一具体架构的Makefile,其名字类似于 arch/$(ARCH)/Makefile。该架构 Makefile 向顶层 Makefile 提供其架构的特别信息。
每一个子目录都有一个 Kbuild Makefile 文件,用来执行从其上层目录传递下来的命令。Kbuild Makefile 从.config 文件中提取信息,生成 Kbuild 完成内核编译所需的文件列表。
scripts/Makefile.*包含了所有的定义、规则等信息。这些文件被用来编译基于 kbuild Makefile的内核 。
Makefile.build | 被顶层Makefile所调用,与各级子目录的Makefile合起来构成一个完整的Makefile文件,定义.lib、built-in.o以及目标文件.o的生成规则。这个Makefile文件生成了子目录的.lib、built-in.o以及目标文件.o |
Makefile.clean | 被顶层Makefile所调用,用来删除目标文件等 |
Makefile.lib | 被Makefile.build所调用,主要是对一些变量的处理,比如说在obj-y前边加上obj目录 |
Kbuild.include | 被Makefile.build所调用,定义了一些函数,如if_changed、if_changed_rule、echo-cmd |
总结:
- Linux内核Makefile体系核心的Makefile文件就两个:顶层Makefile、scripts/Makefile.build。
- 子目录中的Makefile、kbuild不是Makefile文件(完整的Makefile文件),只能算作是Makefile的包含文件。
- 顶层Makefile文件负责将各个目录生成的*.built-in.o、lib.a等文件连接到一起。而scripts/Makefile.build 包含子目录中的Makefile文件来生成这些*.built-in.o、lib.a、*.o等文件。
(三)编译过程分析
本文的实验源码是对“linux-3.10.y”进行移植后的运行在HI3520DV400开发板上的源码包
1.顶层Makefile编译
内核配置完成后,在顶层目录中执行“#make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- uImage”便开始编译内核。但是,uImage却不是在顶层Makefile中定义,而是在arch/$(ARCH)/Makefile中定义。
linux-3.10.y/Makefile
496 # ===========================================================================
497 # *config targets only - make sure prerequisites are updated, and descend
498 # in scripts/kconfig to make the *config target
499
500 # Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.
501 # KBUILD_DEFCONFIG may point out an alternative default configuration
502 # used for 'make defconfig'
503 include $(srctree)/arch/$(SRCARCH)/Makefile
其中srctree为源码绝对路径,以我的环境为例,它的值等于/home/biao/test/kernel/linux-3.10.y;而SRCARCH := $(ARCH),即该变量等于架构名称,我们以arm为例进行说明。
arch/arm/Makefile
301 zImage Image xipImage bootpImage uImage: vmlinux
可见uImage依赖于vmlinux,要先生成vmlinux,然后执行下边这条指令完成编译
arch/arm/Makefile
302 $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
<1> Q的定义:选择静态编译与否(是否打印编译信息)
linux-3.10.y/Makefile
309 ifeq ($(KBUILD_VERBOSE),1)
310 quiet =
311 Q =
312 else
313 quiet=quiet_
314 Q = @
315 endif
<2> MAKE: 系统环境变量,值为make
<3> build: 值为“-f scripts/Makefile.build obj=”实际上就是调用子Makefile--scripts/Makefile.build,然后传递参数目标文件夹。
160 ###
161 # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
162 # Usage:
163 # $(Q)$(MAKE) $(build)=dir
164 build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
Kbuild.include被顶层Makefile所包含
linux-3.10.y/Makefile
330 # We need some generic definitions (do not try to remake the file).
331 $(srctree)/scripts/Kbuild.include: ;
332 include $(srctree)/scripts/Kbuild.include
2.vmlinux的生成
linux-3.10.y/Makefile
576 # The all: target is the default when no target is given on the
577 # command line.
578 # This allow a user to issue only 'make' to build a kernel including modules
579 # Defaults to vmlinux, but the arch makefile usually adds further targets
580 all: vmlinux
顶层Makefile生成的目标文件就是vmlinux,它是默认编译目标文件
linux-3.10.y/Makefile
778 # Include targets which we want to
779 # execute if the rest of the kernel build went well.
780 vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
linux-3.10.y/Makefile
764 # Externally visible symbols (used by link-vmlinux.sh)
765 export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
766 export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
767 export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
768 export LDFLAGS_vmlinux
769 # used by scripts/pacmage/Makefile
770 export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools virt)
771
772 vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
vmlinux的依赖是vmlinux-deps,vmlinux-deps又依赖于KBUILD_LDS,KBUILD_VMLINUX_INIT,KBUILD_VMLINUX_MAIN,要想生成vmlinux必须要先把这些原材料准备好。
<1> KBUILD_LDS
在编译之前要把连接脚本先生成,最后的连接阶段会用的着。
linux-3.10.y/Makefile
767 export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
<2>KBUILD_VMLINUX_INIT
linux-3.10.y/Makefile
765 export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
①head-y
linux-3.10.y/arch/arm/Makefile
133 head-y := arch/arm/kernel/head$(MMUEXT).o
②init-y
linux-3.10.y/Makefile
530 init-y := init/
756 init-y := $(patsubst %/, %/built-in.o, $(init-y))
init-y最终等于init/built-in.o
<3>KBUILD_VMLINUX_MAIN
linux-3.10.y/Makefile
766 export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
① core-y
linux-3.10.y/arch/arm/Makefile
267 # If we have a machine-specific directory, then include it in the build.
268 core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
269 core-y += arch/arm/net/
270 core-y += arch/arm/crypto/
271 core-y += $(machdirs) $(platdirs)
linux-3.10.y/Makefile
534 core-y := usr/
745 core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
757 core-y := $(patsubst %/, %/built-in.o, $(core-y))
core-y最终等于:
core-y := usr/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 arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o
② libs-y
linux-3.10.y/arch/arm/Makefile
275 libs-y := arch/arm/lib/ $(libs-y)
libs-y最终等于arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o
linux-3.10.y/Makefile
533 libs-y := lib/
760 libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
761 libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
762 libs-y := $(libs-y1) $(libs-y2)
③drivers-y
linux-3.10.y/Makefile
531 drivers-y := drivers/ sound/ firmware/
758 drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
drivers-y值最终等于rivers/built-in.o sound/built-in.o firmware/built-in.o
④net-y
linux-3.10.y/Makefile
532 net-y := net/
759 net-y := $(patsubst %/, %/built-in.o, $(net-y))
net-y最终等于net/built-in.o
<4> FORCE
它的目的是强迫重建vmlinux(无论上边的原材料是否已经构建,无论上边的原材料是否比已经生成的目标新,都要重建),这种用法在Linux Makefile体系中经常见到。
linux-3.10.y/scripts/Makefile.build
461 # Add FORCE to the prequisites of a target to force it to be always rebuilt.
462 # ---------------------------------------------------------------------------
463
464 PHONY += FORCE
465
466 FORCE:
可以看到FORCE为一个伪目标,所以无论如何都要重建vmlinux。
<5> 通过编译查看最终的依赖文件
[18:36:20]biao@ubuntu:~test/linux-3.18.y$ sudo make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- uImage V=1 -n
arm-hisiv500-linux-ld -EL -p --no-undefined -X --build-id -o vmlinux -T ./arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o init/built-in.o --start-group usr/built-in.o arch/arm/vfp/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/net/built-in.o arch/arm/crypto/built-in.o arch/arm/firmware/built-in.o arch/arm/mach-hisi/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 arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o drivers/built-in.o sound/built-in.o firmware/built-in.o net/built-in.o --end-group .tmp_kallsyms2.o
命令中的参数V=1的含义是“顶层Makefile # Use 'make V=1' to see the full commands”。
3.uImage镜像文件的生成
(1)编译目标文件
arch/arm/Makefile
302 boot := arch/arm/boot
303
304 archprepare:
305 $(Q)$(MAKE) $(build)=arch/arm/tools include/generated/mach-types.h
306
307 # Convert bzImage to zImage
308 bzImage: zImage
309
310 BOOT_TARGETS = zImage Image xipImage bootpImage uImage
311 INSTALL_TARGETS = zinstall uinstall install
312
313 PHONY += bzImage $(BOOT_TARGETS) $(INSTALL_TARGETS)
314
315 $(BOOT_TARGETS): vmlinux
316 $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
用户输入:make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- uImag
实际执行的命令是:make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE= arch/arm/boot/uImage
会进入到arch/arm/boot/的Makefile执行。
(2)目标依赖
①uImage:
arch/arm/boot/Makefile
92 $(obj)/uImage: $(obj)/zImage-dtb FORCE
93 @$(check_for_multiple_loadaddr)
94 $(call if_changed,uimage)
95 @$(kecho) ' Image $@ is ready'
②zImage-dtb:
arch/arm/boot/Makefile
68 $(obj)/zImage-dtb: $(obj)/zImage $(DTB_OBJS) FORCE
69 $(call if_changed,cat)
70 @echo ' Kernel: $@ is ready'
③zImage:
arch/arm/boot/Makefile
64 $(obj)/zImage: $(obj)/compressed/vmlinux FORCE
65 $(call if_changed,objcopy)
66 @$(kecho) ' Kernel: $@ is ready'
④compressed/vmlinux:
arch/arm/boot/Makefile
61 $(obj)/compressed/vmlinux: $(obj)/Image FORCE
62 $(Q)$(MAKE) $(build)=$(obj)/compressed $@
⑤Image:
arch/arm/boot/Makefile
57 $(obj)/Image: vmlinux FORCE
58 $(call if_changed,objcopy)
59 @$(kecho) ' Kernel: $@ is ready'
⑥vmlinux
linux-3.18.y/Makefile
604 # The all: target is the default when no target is given on the
605 # command line.
606 # This allow a user to issue only 'make' to build a kernel including modules
607 # Defaults to vmlinux, but the arch makefile usually adds further targets
608 all: vmlinux
依赖关系为:uImage->zImage-dtb->zImage->compressed/vmlinux->Image->vmlinux,要生成uImage文件,必须先把它的依赖文件全部生成了。
(3)规则:
上面各个目标的规则基本相同,
①自动化变量
- $@ 表示规则中的目标文件集
- $< 依赖目标中的第一个目标名字,如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
- $(@F) 表示"$@"的文件部分
- $^所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
- filter-out 反过滤函数。
(4)Kbuild 自定义命令
如果要为自定义命令使用这一功能,需要设置 2 个变量:
quiet_cmd_<command> - 要显示的命令
cmd_<command> - 要执行的命令
①if_changed:
scripts/Kbuild.include
236 # Execute command if command has changed or prerequisite(s) are updated.
237 #
238 if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
239 @set -e; \
240 $(echo-cmd) $(cmd_$(1)); \
241 printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
208 # if_changed - execute command if any prerequisite is newer than
209 # target, or command line has changed
②objcopy:
scripts/Makefile.lib
240 # Objcopy
241 # ---------------------------------------------------------------------------
242
243 quiet_cmd_objcopy = OBJCOPY $@
244 cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
linux-3.18.y/Makefile
362 OBJCOPY = $(CROSS_COMPILE)objcopy
arch/arm/Makefile
22 OBJCOPYFLAGS :=-O binary -R .comment -S
实例:arm-hisiv500-linux-objcopy -O binary -R .comment -S vmlinux Image
- #使用 -O binary (或--out-target=binary) 输出为原始的二进制文件
- #使用 -R .note (或--remove-section) 输出文件中不要.note这个section,缩小了文件尺寸
- #使用 -R .comment(或--remove-section) 输出文件中不要.comment这个section,缩小了文件尺寸
- #使用 -S (或 --strip-all)输出文件中不要重定位信息和符号信息,缩小了文件尺寸
③cat
scripts/Makefile.lib
286 # cat
287 # ---------------------------------------------------------------------------
288 # Concatentate multiple files together
289 quiet_cmd_cat = CAT $@
290 cmd_cat = (cat $(filter-out FORCE,$^) > $@) || (rm -f $@; false)
$@ 目标文件, $^ 依赖文件,(filter-out FORCE,$^)方向过滤,将FORCE从依赖文件中去除
(cat arch/arm/boot/zImage arch/arm/boot/dts/hi3520dv400-demb.dtb > arch/arm/boot/zImage-dtb) || (rm -f arch/arm/boot/zImage-dtb; false); 实际上这是一个文件的拼接。
④uimage
scripts/Makefile.lib
352 quiet_cmd_uimage = UIMAGE $(UIMAGE_OUT)
353 cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \
354 -C $(UIMAGE_COMPRESSION) $(UIMAGE_OPTS-y) \
355 -T $(UIMAGE_TYPE) \
356 -a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \
357 -n $(UIMAGE_NAME) -d $(UIMAGE_IN) $(UIMAGE_OUT)
linux-3.18.y/Makefile
294 CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
295 else if [ -x /bin/bash ]; then echo /bin/bash; \
296 else echo sh; fi ; fi)
scripts/Makefile.lib
338 MKIMAGE := $(srctree)/scripts/mkuboot.sh
scripts/Makefile.lib
342 UIMAGE_ARCH ?= $(SRCARCH)
linux-3.18.y/Makefile
256 SRCARCH := $(ARCH)
scripts/Makefile.lib
340 # SRCARCH just happens to match slightly more than ARCH (on sparc), so reduces
341 # the number of overrides in arch makefiles
342 UIMAGE_ARCH ?= $(SRCARCH)
343 UIMAGE_COMPRESSION ?= $(if $(2),$(2),none)
344 UIMAGE_OPTS-y ?=
345 UIMAGE_TYPE ?= kernel
346 UIMAGE_LOADADDR ?= arch_must_set_this
347 UIMAGE_ENTRYADDR ?= $(UIMAGE_LOADADDR)
348 UIMAGE_NAME ?= 'Linux-$(KERNELRELEASE)'
349 UIMAGE_IN ?= $<
350 UIMAGE_OUT ?= $@
最终的uimge执行的命令是:/bin/sh ./scripts/mkuboot.sh -A arm -O linux -C none -T kernel -a 0x80008000 -e 0x80008000 -n '\''Linux-3.18.20'\'' -d arch/arm/boot/zImage-dtb arch/arm/boot/uImage
4.生成镜像文件
与上面的依赖反过来就是编译生成文件的顺序:
vmlinux : 由顶层Makefile编译生成,在目录linux-3.18.y下
-rwxr-xr-x 1 root root 8349832 Dec 9 16:14 vmlinux*
Image:由vmlinux压缩得到,文件信息:
-rwxr-xr-x 1 root root 6236864 Dec 9 16:14 Image*
compressed/vmlinux:由Image进一步压缩得到 文件信息:
-rwxr-xr-x 1 root root 3266708 Dec 9 16:14 vmlinux*
zImage: 由compressed/vmlinux压缩的到
-rwxr-xr-x 1 root root 3192168 Dec 9 16:14 zImage*
zImage-dtb: 由zImage和设备树hi3520dv400-demb.dtb拼接而来:
-rw-r--r-- 1 root root 3202016 Dec 6 15:45 zImage-dtb
uImage:由zImage-dtb 添加了64字节头信息得到
-rw-r--r-- 1 root root 3202080 Dec 6 15:45 uImage
uImage的产生过程已经看完了,但是读者可能会被这有点复杂的关系绕晕了,所以现在可以结合一下的流程图简单地总结一下,下图是zImage的生成过程示意图:
首先顶层vmlinux是ELF格式的可执行文件,必须将其二进制化生成Image后才可以被bootloader引导。为了实现压缩的内核映像,arch/arm/boot/compressed/Makefile又将这个非压缩映像Image做gzip压缩,生成了piggy.gzip。但要实现在启动时自解压,必须将这个piggy.gzip转化为.o文件,并同初始化程序head.o和自解压程序misc.o一同链接,生成arch/arm/boot/compressed/vmlinux。最后arch/arm/boot/Makefile将这个ELF格式的arch/arm/boot/compressed/vmlinux二进制化得到可被bootloader引导的映像文件zImage。
(四)Linux内核整体编译过程
1、生成准备文件
①控制C程序的头文件
include/linux/version.h include/linux/utsrelease.h、include/linux/autoconf.h
②控制编译连接的文件
arch/arm/kernel/vmlinux.lds、include/config/auto.conf等文件。
2、由C程序源码和汇编语言源码生成目标文件(*.o)
3、将目标文件连接成*.built-in.o、*/lib.a等文件
4、将紧接着顶层目录的子目录中的*.built-in.o以及部分重要的*.o文件连接生成vmlinux
5、根据arch/arm/Makefile的规则生成zImage、uImage等
(五)Linux内核编译构成元素
1.Makefile的目标
(1)总目标
总目标实际上是在arch/arm/Makefile中定义了,比方说zImage、uImage,顶层Makefile紧接着定义了这些终极目标直接的依赖目标vmlinux。
(2)各级子目标
各级子目标是在scripts/Makefile.build中的__build中定义的,例如传递参数obj=drivers后的目标是drivers/built-in.o。
这些目标的依赖其实又成为了新的目标,例如drivers/net/built-in.o、drivers/net/dm9000.o。
2.Makefile的依赖
(1)总目标的依赖
KBUILD_LDS、KBUILD_VMLINUX_INIT、KBUILD_VMLINUX_MAIN
(2)各级子目标的依赖
各级子目标的依赖是由子目录中的Makefile(实际是scripts/Makefile.build的包含文件)和scripts/Makefile.lib共同完成确定的。
子目录中的Makefile负责选材,而scripts/Makefile.lib负责加工。
3.Makefile的规则
(1)总目标的连接规则
总目标vmlinux的连接规则就是在顶层Makefile中定义的,至于zImage、uImage则是在arch/arm/Makefile中定义的。
(2)子目标的编译连接规则
主要是在scripts/Makefile.build、scripts/Kbuild.include中定义的,其中scripts/Kbuild.include定义了许多诸如if_changed的函数。
(六)Linux内核Makefile特点
1.两个Makefile
顶层Makefile文件负责将各个目录生成的*.built-in.o、lib.a等文件连接到一起生成vmlinux。而scripts/Makefile.build 包含子目录中的Makefile文件以及scripts/中的众多脚本来生成这些*.built-in.o、lib.a、*.o等文件。
通过“make -f scripts/Makefile.build obj=”的方法完成了顶层Makefile到scripts/Makefile.build的调用生成*/built-in.o,以及scripts/Makefile.build的自调用生成更低一级子目录的*/built-in.o。
2.编译的目录始终是顶层目录
“make -C”命令会先切换工作目录,然后执行该目录中的Makefile,u-boot就是采用这种方法。而linux则是利用“make -f”的方法,所以编译的目录始终是顶层目录。
3.通用规则
Linux内核Makefile的通用子Makefile是scripts/Makefile.build,而通用的其他规则则是scripts中的其他文件。
(七)引用与致谢: