1 zImage编译#
_all
是默认目标,如果使用命令make或者make all编译 Linux 的话此目标就会被匹配。KBUILD_EXTMOD
为空的,因此194 行的代码成立, 因此_all
依赖all。all又依赖vmlinux,开始编译vmlinux。
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
1.1 vmlinux编译#
找到vmlinux目标开始分析:
目标 vmlinux 依赖 scripts/link-vmlinux.sh
$(vmlinux-deps)
FORCE
。
1 | vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) |
其中:
1 | KBUILD_VMLINUX_INIT= $(head-y) $(init-y) |
综上所述,vmlinux 的依赖为:
1 | scripts/link-vmlinux.sh |
1.1.1 head-y#
head-y 定义在文件 arch/arm/Makefile 中:
1 | 135 head-y := arch/arm/kernel/head$(MMUEXT).o |
当不使能 MMU 的话 MMUEXT=-nommu
,如果使能 MMU 的话为空,因此 head-y为:head-y = arch/arm/kernel/head.o
1.1.2 init-y/drivers-y和net-y#
顶层Makefile内容如下:
展开后:
1 | init-y = init/built-in.o |
1.1.3 libs-y#
顶层Makefile中:
1 | 561 libs-y := lib/ |
在arch/arm/Makefile中,对libs-y又追加了:libs-y := arch/arm/lib/ $(libs-y)
展开后:libs-y = arch/arm/lib lib/
回到顶层Makefile:
1 | 900 libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) |
展开后:libs-y = arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o
1.1.4 core-y#
顶层Makefile中:
1 | 532 core-y := usr/ |
在 arch/arm/Makefile 中会对 core-y 进行追加,代码如下:
1 | 269 core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/ |
比如使能 VFP
的话就会在.config
中有 CONFIG_VFP=y 这一行,那么 core-y 就会追加“arch/arm/vfp/”
。
顶层 Makefile 中还有:
1 | 897 core-y := $(patsubst %/, %/built-in.o, $(core-y)) |
最终 core-y 的值为:
1 | core-y = usr/built-in.o arch/arm/vfp/built-in.o \ |
可以看到和 uboot 一样这些变量都是一些 built-in.o 或.a 等文件,都是将相应目录中的源码文件进行编译,然后在各自目录下生成 built-in.o 文件,有些生成了.a 库文件。最终将这些 built-in.o 和.a 文件进行链接即可形成 ELF 格式的可执行文件,也就是 vmlinux。但是链接是需要链接脚本的,vmlinux 的依赖 arch/arm/kernel/vmlinux.lds 就是整个 Linux 的链接脚本。
1.1.5 built-in.o产生过程#
1.1.5.1 vmlinux-deps展开#
vmliux 依赖 vmlinux-deps:vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
这些变量进行展开:
1 | $(head-y) 、$(init-y)、$(core-y) 、 |
展开:
1 | init-y := init/ |
最后:
1 | vmlinux-deps = arch/arm/kernel/vmlinux.lds |
除了 arch/arm/kernel/vmlinux.lds 以外,其他都是要编译链接生成的。顶层 Makefile 中有如下代码:937 $(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
sort 是排序函数,用于对 vmlinux-deps 的字符串列表进行排序,并且去掉重复的单词。可以看出 vmlinux-deps
依赖 vmlinux-dirs
,``vmlinux-dirs` 也定义在顶层 Makefile 中,定义如下:
1 | 889 vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \ |
因此展开vmlinux-dirs为:
1 | vmlinux-dirs = init usr arch/arm/vfp \ |
在顶层 Makefile 中有如下代码:
1 | 946 $(vmlinux-dirs): prepare scripts |
1.1.5.2 prepare准备工作#
1 | vmlinux-deps -> vmlinux-dirs -> prepare |
1.1.5.2.1 prepare 的框架#
prepare0
:
1 | 1 prepare0: archprepare |
第2行,等于make -f ./scripts/Makefile.build obj=scripts/mod
首先进入到 scripts/Makefile.build
,然后包含 scripts/mod/Makefile
文件,执行scripts/mod/Makefile
下的默认目标:
scripts/mod/Makefile
内容如下:
1 | hostprogs-y := modpost mk_elfconfig |
这部分命令将生成 scripts/mod/devicetable-offsets.h
这个全局偏移文件。
同时,modpost
和 mk_elfconfig
这两个目标(这是两个主机程序)被赋值给always
变量,根据 scripts/Makefile.build
中的规则,将在Makefile.host
中被生成。modpost
和 mk_elfconfig
是两个主机程序,负责处理模块与编译符号相关的内容。
archprepare
:
看到 arch 前缀就知道这是架构相关的,这部分的定义与 arch/$(ARCH)/Makefile
有非常大的关系.archprepare
并没有命令部分,仅仅是四个依赖:
1 | archprepare: archheaders archscripts prepare1 scripts |
archheaders
1 | archheaders: |
进入scripts/Makefile.build
中,并包含 arch/arm/tools/Makefile
,编译目标为 uapi
:
1 | uapi-hdrs-y := $(uapi)/unistd-common.h |
uapi 为用户 API 的头文件,包含unistd-common.h、unistd-oabi.h、unistd-eabi.h
等通用头文件。
archscripts
archscripts 产生平台相关的支持脚本。
scripts
1 | scripts: scripts_basic scripts_dtc |
等于make -f ./scripts/Makefile.build obj=scripts
, 找到找到 scripts
下的 Makefile,Makefile 下的内容是这样的:
1 | hostprogs-$(CONFIG_BUILD_BIN2C) += bin2c |
编译了一系列的主机程序,包括 bin2c
、kallsyms
、pnmtologo
等。
scripts_basic
前面讲过了,编译出fixdep
。
Uboot顶层Makefile解析-1. defconfig过程分析 - fuzidage - 博客园 (cnblogs.com)
uboot-编译defconfig分析 | Hexo (fuzidage.github.io)
scripts_dtc
定义如下:
1 | scripts_dtc: scripts_basic |
同样的,进入 scripts/dtc/Makefile
:
1 | hostprogs-$(CONFIG_DTC) := dtc |
可以看到,该目标的作用就是生成 dtc。
prepare1
1 | prepare1: prepare3 outputMakefile asm-generic $(version_h) $(autoksyms_h) \ |
1 | md_crmodverdir = $(Q)mkdir -p $(MODVERDIR) \ |
$(MODVERDIR)
仅与编译外部模块相关,这里针对外部模块的处理,如果指定编译外部模块,则不做任何事,如果没有指定编译外部模块,清除$(MODVERDIR)/
目录下所有内容。
4.1 prepare3
1 | prepare3: include/config/kernel.release |
当我们指定了 O=DIR
参数时,发现如果源代码中在之前的编译中有残留的之前编译过的中间文件,编译过程就会报错,系统会提示我们使用make mrprope
将之前的所有编译中间文件清除,再重新编译。prepare3
的作用就是起到检查内核源码是否干净。
从源码中可以看出,prepare3
将会检查的文件有:
$(srctree)/include/config、$(srctree)/arch/$(SRCARCH)/include/generated、$(srctree)/.config
。
1 | make help |
输入make help
可以看到clean目标的程度。
4.2 outputMakefile
前面讲过了。
Uboot顶层Makefile解析-1. defconfig过程分析 - fuzidage - 博客园 (cnblogs.com)
uboot-编译defconfig分析 | Hexo (fuzidage.github.io)
4.3 asm-generic、version_h、autoksyms_h、 include/generated/utsrelease.h
产生相关头文件。
1.1.5.3 vmlinux-dirs
展开#
1 | 946 $(vmlinux-dirs): prepare scripts |
第947行内容进行展开:@ make -f ./scripts/Makefile.build obj=$@
$@在makefile中表示target,因此就是为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
这个命令为例,讲解一下详细的运行过程。可以看到这些命令运行过程其实都是一样的。再次打开scripts/Makefile.build,这个在做xxx_defconfig时已经分析过了:
当 只 编 译 Linux 内 核 镜 像 文 件 , 也 就 是 使 用 “ make zImage ”
编 译 的 时 候 ,KBUILD_BUILTIN=1,KBUILD_MODULES
为空。“make”
命令是会编译所有的东西,包括 Linux内核镜像文件和一些模块文件。如果只编译 Linux 内核镜像的话,__build
目标简化为:__build: $(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:
重点看下builtin-target
这个依赖,同样定义在scripts/Makefile.build如下:
1 | 86 ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),) |
可以看到只要obj-y、obj-m、obj-、subdir-m 和 lib-target
这些变量不能全部为空,builtin-target
变量的值为:“$(obj)/built-in.o”
, 这就是这些 built-in.o 的来源了。我们以@ make -f ./scripts/Makefile.build obj=init
这个命令为例:
那么现在开始编译:builtin-target := init/built-in.o
目标builtin-target
依赖为 obj-y,命令为$(call if_changed,link_o_target)
,也就是调用函数 if_changed
,参数为 link_o_target
,其返回值就是具体的命令。前面讲过了if_changed
uboot顶层makefile-2编译过程,
它会调用 cmd_$(1)
所对应的命令($(1)就是函数的第 1 个参数)
,因此这里就是调用:cmd_link_o_target
,也就是:
1 | 331 cmd_link_o_target = $(if $(strip $(obj-y)),\ |
cmd_link_o_target
就是使用 LD将某个目录下的所有.o 文件链接在一起,最终形成 built-in.o。
命令会记录到.built-in.o.cmd
:
1 | cmd_init/built-in.o := arm-linux-gnueabihf-ld -EL -r -o init/built-in.o init/main.o init/version.o init/mounts.o init/initramfs.o init/calibrate.o init/init_task.o |
同理,其他的依赖项built-in.o
也是同样的方式编译。
1.1.6 link-vmlinux#
vmlinux的依赖产生完后,调用+$(call if_changed,link-vmlinux)
链接生成 vmlinux。
命令“+$(call if_changed,link-vmlinux)”
表示将$(call if_changed,link-vmlinux)
的结果作为最终生成 vmlinux 的命令,前面的“+”表示该命令结果不可忽略。$(call if_changed,link-vmlinux)
是调用函数 if_changed
,link-vmlinux
是函数if_changed
的参数,函数 if_changed
定义在文件 scripts/Kbuild.include 中:
uboot顶层makefile-2编译过程有具体分析 if_changed
。
1 | 总结:当依赖比目标新的时候,或者命令有改变的时候,if_changed 就会执行一些命令 |
打开.vmlinux.cmd
文件内容如下:cmd_vmlinux := /bin/bash scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --no-undefined -X --pic-veneer --build-id
cmd_link-vmlinux
在顶层 Makefile 中定义如下:
1 | 914 # Final link of vmlinux |
第 915 行就是 cmd_link-vmlinux
的值,其中 CONFIG_SHELL=/bin/bash
,$<
表示目标 vmlinux的第一个依赖文件,也就是 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。脚本内容如下:
vmliux_link 就是最终链接出 vmlinux 的函数,判断 SRCARCH
是否不等于“um”
,因为 SRCARCH=arm
,因此条件成立。执行:
1 | ${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \ |
这三行代码应该很熟悉了! 就是普通的链接操作,连接脚本为lds= ./arch/arm/kernel/vmlinux.lds
,需要链接的文件由变量KBUILD_VMLINUX_INIT
和KBUILD_VMLINUX_MAIN
来决定,这两个变量在前面讲过了:
1 | KBUILD_VMLINUX_INIT= $(head-y) $(init-y) |
第 217 行调用 vmlinux_link 函数来链接出 vmlinux。
编译时V=1即可看到详细LD链接过程如下:
总结:将各个子目录下的 built-in.o、.a 等文件链接在一起,最终生成 vmlinux 这个 ELF 格式的可执行文件。链接脚本为arch/arm/kernel/vmlinux.lds,链接过程是由shell脚本scripts/link-vmlinux.s来完成的。
1.1.7 新版的link-vmlinux#
1 | cmd_link-vmlinux = \ |
1.1.7.1 autoksyms_recursive#
内核的符号优化。
1 | autoksyms_recursive: $(vmlinux-deps) |
如果在源码的配置阶段定义了CONFIG_TRIM_UNUSED_KSYMS
这个变量,就执行:
1 | $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \ |
默认情况下,这里的脚本并不会执行。
if_changed
,执行link-vmlinux
,也就是cmd_link-vmlinux
.
1.2 make zImage过程#
当我们输入:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all
会编译vmlinux, zImage,然后再编译dtb,编译ko。
1.2.1 vmlinux、Image,zImage、uImage 的区别#
①、vmlinux 是编译出来的最原始的内核文件,是未压缩的,比如正点原子提供的 Linux 源码编译出来的 vmlinux 差不多有 16MB。
1 | $ ls -lh vmlinux |
②、Image 是 Linux 内核镜像文件,但是 Image 仅包含可执行的二进制数据。Image 就是使用 objcopy 取消掉 vmlinux 中的一些其他信息,比如符号表什么的。但是 Image 是没有压缩过的,Image 保存在 arch/arm/boot 目录下,其大小大概在 12MB 左右。
1 | $ ls -lh arch/arm/boot/Image |
③、zImage 是经过 gzip 压缩后的 Image,经过压缩以后其大小大概在 6MB 左右。
1 | $ ls -lh arch/arm/boot/zImage |
④、uImage 是老版本 uboot 专用的镜像文件,uImag 是在 zImage 前面加了一个长度为 64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。但是新的 uboot 已经支持了 zImage 启动!所以已经很少用到 uImage 了,除非你用的很古老的 uboot。
1.2.2 Image的编译过程#
使用“make”、“make all”、“make zImage”
都可以编译出zImage, arch/arm/Makefile 中有如下代码:
all依赖$(KBUILD_IMAGE) $(KBUILD_DTBS)
, KBUILD_IMAGE
为zImage, KBUILD_DTBS
为dtbs。
1 | #boot 对应目录 |
可以看到当vmlinux产生后,就会去继续产生zImage, 展开:@ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/zImage
可以看到又是通过scripts/Makefile.build来完成的编译。进入arch/arm/boot
的makefile:
1 | rm Image zImage |
Image生成过程:
1 | # Image 生成的规则 |
执行cmd_objcopy
,该命令定义在scripts/Makefile.lib
中:
1 | #在arch/$(ARCH)/boot/Makefile 下定义 |
打印如下:
1 | make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE= arch/arm/boot/Image |
1.2.3 zImage的编译过程#
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage V=1 |
zImage生成过程:
1 | $(obj)/zImage: $(obj)/compressed/vmlinux FORCE |
打印如下:
1 | make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE= arch/arm/boot/zImage |
1.2.4 OBJCOPYFLAGS#
1 | -O binary:表示输出格式为 binary,也就是二进制 |
2 生成的文件#
System.map
: 内核镜像中所有的符号,符号表.*.o.d 和 .*.o.cmd:
记录编译指令的内容,如:.vmlinux.cmd
modules.order
、 modules.build
和 modules.builtin.modinfo :
这两个文件主要负责记录编译的模块,modules.builtin.modinfo
记录模块信息,以供 modprobe 使用arch/$(ARCH)/boot
: 编译出的文件,一般包含这几部分:镜像、内核符号表、系统dtb。include/generate/*
: 内核编译过程将会生成一些头文件,其中比较重要的是 autoconf.h,这是 .config 的头文件版本,以及uapi/目录下的文件,这个目录下的保存着用户头文件。include/config/*
: 为了解决 autoconf.h 牵一发而动全身的问题(即修改一个配置导致所有依赖 autoconf.h 的文件需要重新编译),将 autoconf.h 分散为多个头文件放在 include/config/ 下,以解决 autoconf.h 的依赖问题。
3 设备树的编译过程#
Linux内核目录下make all
会把dtb也编译出来。详见字符设备驱动-4.设备树 第2.3 DTB文件的编译。
make dtbs V=1
可查看详细编译过程。
3.1 生成dtc工具#
3.2 生成dtb#
vmlinux和zImage产生后会继续编译设备树,用dtc编译dts得到dtb。