imx6ull-系统移植篇18——linux顶层 Makefile(下)
目录
前言
make 过程
目标 _all
vmlinux
head-y
init-y、 drivers-y 和 net-y
libs-y
core-y
vmlinux.lds
函数 if_changed
cmd_link-vmlinux
vmliux_link函数
built-in.o 文件编译生成过程
vmlinux-deps
vmlinux-dirs
举例
make zImage 过程
vmlinux、 Image, zImage、 uImage 的区别
1. vmlinux
2. Image
3. zImage
4. uImage
make zImage
前言
在上一讲内容:linux顶层 Makefile(上),我们简单分析了linux内核顶层Makefile源码,分析了make xxx_defconfig 过程、Makefile.build 脚本。
这一讲内容,我们继续分析linux顶层 Makefile:make 过程、built-in.o 文件编译生成过程、make zImage 过程。
make 过程
使用命令“make xxx_defconfig”配置好 Linux 内核以后就可以使用“make”或者“make all”命令进行编译。
目标 _all
顶层 Makefile 有如下代码:
# 声明伪目标(防止与同名文件冲突)
PHONY := _all# 默认目标入口(直接make时执行的目标)
_all:[...]# 定义all目标为伪目标
PHONY += all# 根据是否外部模块构建设置依赖链
ifeq ($(KBUILD_EXTMOD),)# 常规内核构建:_all依赖all_all: all
else# 外部模块构建:_all仅需构建modules_all: modules
endif[...]# all目标的最终定义(在文件后部)
all: vmlinux[...]
默认目标_all 依赖 all,目标 all 依赖 vmlinux,所以接下来的重点就是 vmlinux。
vmlinux
顶层 Makefile 中有如下代码:
# 导出给 link-vmlinux.sh 脚本使用的符号
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) # 初始化段目标文件
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y) # 主段目标文件
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds # 链接脚本路径
export LDFLAGS_vmlinux # 特殊链接标志
export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools virt) # 构建涉及的所有目录# vmlinux 的依赖项(链接脚本+所有目标文件)
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)# 定义链接命令模板
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
quiet_cmd_link-vmlinux = LINK $@# vmlinux 目标规则(最终生成内核镜像)
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE# 可选:头文件检查ifdef CONFIG_HEADERS_CHECK$(Q)$(MAKE) -f $(srctree)/Makefile headers_checkendif# 可选:构建示例代码ifdef CONFIG_SAMPLES$(Q)$(MAKE) $(build)=samplesendif# 可选:构建文档ifdef CONFIG_BUILD_DOCSRC$(Q)$(MAKE) $(build)=Documentationendif# 可选:生成GDB调试脚本ifdef CONFIG_GDB_SCRIPTS$(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.pyendif# 执行实际链接操作+$(call if_changed,link-vmlinux)
由代码可知:
vmlinux 的依赖为: scripts/link-vmlinux.sh、 $(head-y) 、 $(init-y)、 $(core-y) 、$(libs-y) 、 $(drivers-y) 、 $(net-y)、 arch/arm/kernel/vmlinux.lds 和 FORCE。
重点来看一下$(head-y) 、 $(init-y)、 $(core-y) 、 $(libs-y) 、 $(drivers-y) 和$(net-y)这六个变量的值。
head-y
head-y 定义在文件 arch/arm/Makefile 中,内容如下:
head-y := arch/arm/kernel/head$(MMUEXT).o
- 当不使能 MMU 的话: MMUEXT=-nommu,
- 如果使能 MMU 的话:MMUEXT为空,
因此 head-y 最终的值为:
head-y = arch/arm/kernel/head.o
init-y、 drivers-y 和 net-y
在顶层 Makefile 中有如下代码:
# 阶段1:定义原始目录列表(初始化变量)
init-y := init/ # 初始化代码目录
drivers-y := drivers/ sound/ firmware/ # 驱动相关目录
net-y := net/ # 网络协议栈目录
[...其他目录定义...]# 阶段2:转换为built-in.o目标(实际构建目标)
init-y := $(patsubst %/, %/built-in.o, $(init-y)) # init/built-in.o
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) # drivers/built-in.o sound/built-in.o
net-y := $(patsubst %/, %/built-in.o, $(net-y)) # net/built-in.o
init-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
libs-y
libs-y 基本和 init-y 一样,在顶层 Makefile 中存在如下代码:
# 原始定义:库目录
libs-y := lib/ # 基础库代码目录# 转换为两种目标格式:
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) # 生成静态库文件(lib/lib.a)
libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y)) # 生成合并目标文件(lib/built-in.o)# 最终合并两种目标
libs-y := $(libs-y1) $(libs-y2) # 同时包含lib.a和built-in.o
可知, libs-y 应该等于“lib.a built-in.o”。
再加上, arch/arm/Makefile 中会向 libs-y 中追加一些值,代码如下:
libs-y := arch/arm/lib/ $(libs-y)
展开以后为:
libs-y = arch/arm/lib lib/
所以,libs-y 最终应该为:
libs-y = arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o
core-y
core-y 和 init-y 也一样,在顶层 Makefile 中有如下代码:
core-y := usr/
......
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
在 arch/arm/Makefile 中会对 core-y 进行追加,代码如下:
# 条件性包含浮点运算单元支持
core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/ # 软件浮点模拟
core-$(CONFIG_FPE_FASTFPE) += $(FASTFPE_OBJ) # 快速浮点模拟(已废弃)
core-$(CONFIG_VFP) += arch/arm/vfp/ # 硬件向量浮点# 虚拟化支持
core-$(CONFIG_XEN) += arch/arm/xen/ # Xen 虚拟化
core-$(CONFIG_KVM_ARM_HOST) += arch/arm/kvm/ # KVM 虚拟化# 其他内核特性
core-$(CONFIG_VDSO) += arch/arm/vdso/ # 虚拟动态共享对象# 强制包含的ARM核心目录(无条件包含)
core-y += arch/arm/kernel/ # 核心内核代码
core-y += arch/arm/mm/ # 内存管理
core-y += arch/arm/common/ # 通用ARM代码
core-y += arch/arm/probes/ # 动态探针
core-y += arch/arm/net/ # 网络优化
core-y += arch/arm/crypto/ # 加密算法
core-y += arch/arm/firmware/ # 固件支持# 包含机器/平台特定目录
core-y += $(machdirs) $(platdirs) # 由arch/arm/Makefile定义
根据不同的配置向 core-y 追加不同的值,比如使能 VFP 的话就会在.config中有 CONFIG_VFP=y 这一行,那么 core-y 就会追加“arch/arm/vfp/”。
在顶层 Makefile 中有如下一行:
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.lds
但是链接是需要链接脚本的, vmlinux 的依赖 arch/arm/kernel/vmlinux.lds 就是整个 Linux 的链接脚本。
还记得上面提到的代码:
# 执行实际链接操作+$(call if_changed,link-vmlinux)
表示将$(call if_changed,link-vmlinux)的结果作为最终生成 vmlinux 的命令,
- 前面的“+”表示该命令结果不可忽略。
- $(call if_changed,link-vmlinux)是调用函数 if_changed,
- link-vmlinux 是函数 if_changed 的参数,
函数 if_changed
函数 if_changed 定义在文件 scripts/Kbuild.include 中,如下所示:
if_changed = $(if $(strip $(any-prereq) $(arg-check)), \@set -e; \ # 出错立即退出$(echo-cmd) $(cmd_$(1)); \ # 打印并执行命令printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd) # 保存命令记录
- any-prereq 用于检查依赖文件是否有变化,如果依赖文件有变化那么 any-prereq 就不为空,否则就为空。
- arg-check 用于检查参数是否有变化,如果没有变化那么 arg-check 就为空。
- “@set -e”告诉 bash,如果任何语句的执行结果不为 true(也就是执行出错)的话就直接退出。
- $(echo-cmd)用于打印命令执行过程,比如在链接 vmlinux 的时候就会输出“LINK vmlinux”。
- $(cmd_$(1))中的$(1)表示参数,也就是 link-vmlinux,因此$(cmd_$(1))表示执行 cmd_link-vmlinux 的内容。
cmd_link-vmlinux
cmd_link-vmlinux 在顶层 Makefile 中有如下所示定义:
# 详细版链接命令(实际执行)
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)# 简洁版显示(默认输出)
quiet_cmd_link-vmlinux = LINK $@
命令组成要素
变量/参数 | 作用 |
---|---|
| 确保使用正确的shell解释器(通常为bash) |
| 第一个依赖文件(即scripts/link-vmlinux.sh) |
| 链接器路径(如arm-linux-gnueabi-ld) |
| 通用链接标志(如 |
| 内核专用链接标志(如 |
cmd_link-vmlinux 最终的值为:
cmd_link-vmlinux = /bin/bash scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --noundefined -X --pic-veneer --build-id
cmd_link-vmlinux 会调用 scripts/link-vmlinux.sh 这个脚本来链接出 vmlinux。
vmliux_link函数
在 linkvmlinux.sh 中有如下所示代码:
vmlinux_link() {# 1. 链接脚本路径设置local lds="${objtree}/${KBUILD_LDS}"# 2. 常规架构链接(非UML)if [ "${SRCARCH}" != "um" ]; then${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \-T ${lds} ${KBUILD_VMLINUX_INIT} \--start-group ${KBUILD_VMLINUX_MAIN} --end-group ${1}# 3. 用户模式(UML)特殊处理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 linuxfi
}# 实际调用
vmlinux_link "${kallsymso}" vmlinux
vmliux_link 就是最终链接出 vmlinux 的函数,
- 链接脚本为lds= ./arch/arm/kernel/vmlinux.lds ,
- 需要链接的文件由变量 KBUILD_VMLINUX_INIT 和KBUILD_VMLINUX_MAIN 来决定。
使用命令“make V=1”编译 Linux,会有如图所示的编译信息:
总结一下:
make 的过程,重点就是将各个子目录下的 built-in.o、 .a 等文件链接在一起,最终生成 vmlinux 这个 ELF 格式的可执行文件。
链接脚本为 arch/arm/kernel/vmlinux.lds,链接过程是由shell脚本scripts/link-vmlinux.s来完成的。
built-in.o 文件编译生成过程
各个子目录下的builtin.o、 .a 等文件又是如何编译出来的呢?
vmlinux-deps
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
可知, vmliux 依赖 vmlinux-deps,
而 vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
最终 vmlinux-deps 的值如下:
vmlinux-deps = arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o \
init/built-in.o 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 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
除了 arch/arm/kernel/vmlinux.lds 以外,其他都是要编译链接生成的。
在顶层 Makefile 中有如下代码:
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
sort 是排序函数,用于对 vmlinux-deps 的字符串列表进行排序,并且去掉重复的单词。
可以看出 vmlinux-deps 依赖 vmlinux-dirs。
vmlinux-dirs
vmlinux-dirs 也定义在顶层 Makefile 中,定义如下:
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-dirs 看名字就知道和目录有关,此变量保存着生成 vmlinux 所需源码文件的目录,
值如下:
vmlinux-dirs = init usr arch/arm/vfp \arch/arm/vdso arch/arm/kernel arch/arm/mm \arch/arm/common arch/arm/probes arch/arm/net \arch/arm/crypto arch/arm/firmware arch/arm/mach-imx\kernel mm fs \ipc security crypto \block drivers sound \firmware net arch/arm/lib \lib
在顶层 Makefile 中有如下代码:
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
目标 vmlinux-dirs 依赖 prepare 和 scripts。
build 的值为“-f ./scripts/Makefile.build obj”,代入上面代码展开为:
@ 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
这段代码展示了 Linux 内核构建系统如何通过递归调用 Makefile.build
来构建各个子系统的过程。
举例
以“@ make -f ./scripts/Makefile.build obj=init”这个命令为例,讲解一下详细的运行过程。
默认目标为__build,__build 目标对应的规则如下
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \$(subdir-ym) $(always)@: # 空命令(实际工作由依赖项完成)
目标类型说明
目标变量 | 内容类型 | 典型示例 |
---|---|---|
| 内核核心目标 |
|
| 内核库文件 |
|
| 额外构建目标 |
|
| 可加载模块 |
|
| 模块顺序文件 |
|
| 需要递归构建的子目录 |
|
| 总是构建的目标 |
|
make”命令是会编译所有的东西,包括 Linux内核镜像文件和一些模块文件。
如果只编译 Linux 内核镜像的话, __build 目标简化为:
__build: $(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)@:
builtin-target 这个依赖, 定义在文件 scripts/Makefile.build中,定义如下:
# 检查是否有需要构建的目标
ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)# 如果存在任何构建目标,则设置built-in.o路径builtin-target := $(obj)/built-in.o
endif
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 中有如下代码:
# 条件检查:是否需要生成built-in.o
ifdef builtin-target# 定义链接命令(静默版和完整版)quiet_cmd_link_o_target = LD $@cmd_link_o_target = $(if $(strip $(obj-y)), \# 非空obj-y:使用ld合并目标文件$(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) $(cmd_secanalysis), \# 空obj-y:创建空归档文件rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)# 构建规则$(builtin-target): $(obj-y) FORCE$(call if_changed,link_o_target) # 增量构建控制# 注册目标targets += $(builtin-target)
endif
builtin-target,依赖为 obj-y,命令为“$(call if_changed,link_o_target)”,也就是调用函数 if_changed,参数为 link_o_target,其返回值就是具体的命令。
调用cmd_link_o_target 所对应的命令,用 LD将某个目录下的所有.o 文件链接在一起,最终形成 built-in.o。
make zImage 过程
vmlinux、 Image, zImage、 uImage 的区别
在 Linux 内核构建系统中,vmlinux
、Image
、zImage
和 uImage
是不同阶段生成的内核镜像文件,它们的主要区别如下:
镜像类型 | 格式 | 压缩 | 调试符号 | 引导方式 | 典型使用场景 |
---|---|---|---|---|---|
| ELF | ❌ | ✅ | 不可直接引导 | 内核开发与调试 |
| Raw Binary | ❌ | ❌ | 需 Bootloader | 部分嵌入式系统 |
| Self-extracting | ✅ | ❌ | 通用 Bootloader | PC/小型嵌入式设备 |
| U-Boot Wrapper | ✅ | ❌ | 仅 U-Boot | U-Boot 嵌入式系统 |
1. vmlinux
-
性质:原始未压缩的 ELF 格式 可执行文件
-
特点:
-
包含完整的调试符号和重定位信息(文件体积最大)
-
未经过任何压缩或打包处理
-
直接由内核源码链接生成(
ld
链接所有built-in.o
)
-
-
用途:
-
内核调试(
gdb
可直接加载) -
生成其他镜像的基础文件
-
-
文件大小:约 20-500 MB(取决于配置)
-
生成命令:make vmlinux
2. Image
-
性质:未压缩的原始内核镜像(Raw Binary)
-
特点:
-
通过
objcopy
从vmlinux
剥离调试信息生成 -
保留纯二进制代码和数据(仍较大)
-
部分架构(如 ARM)可能直接使用此格式
-
-
用途:
-
某些嵌入式系统的直接加载
-
生成
zImage
/uImage
的中间文件
-
-
文件大小:约 5-50 MB
-
生成路径:arch/arm/boot/Image
3. zImage
-
性质:压缩后的内核镜像(自解压格式)
-
特点:
-
使用
gzip
压缩Image
生成(压缩率约 50-70%) -
包含自解压代码(Bootloader 加载后自动解压到内存)
-
兼容性最广的通用格式
-
-
限制:
-
大小受限(ARM 通常需 < 512KB)
-
不支持附加信息(如 DTBs)
-
-
用途:
-
传统 PC 或小型嵌入式设备
-
-
文件大小:约 2-10 MB
-
生成路径:arch/arm/boot/zImage
4. uImage
-
性质:U-Boot 专用的封装格式
-
特点:
-
在
zImage
基础上添加 U-Boot 头信息(加载地址、入口点等) -
支持 CRC 校验和可选的加密
-
专用于 U-Boot 引导加载程序
-
- 用途:
-
使用 U-Boot 的嵌入式系统(如开发板)
-
文件大小:略大于
zImage
(增加约 64B 头) -
生成命令:make uImage # 需安装 U-Boot 的 mkimage 工具
-
make zImage
实际中,我们使用 zImage 或 uImage 这样的 Linux 内核镜像文件。
使用“ make”、“ make all”、“ make zImage”这些命令就可以编译出 zImage 镜像。
在arch/arm/Makefile 中有如下代码:
# 定义支持的启动镜像类型
BOOT_TARGETS = zImage Image xipImage bootpImage uImage# 所有启动镜像都依赖vmlinux
$(BOOT_TARGETS): vmlinux$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
变量 BOOT_TARGETS 包含 zImage, Image, xipImage 等镜像文件。
BOOT_TARGETS 依赖 vmlinux,因此如果使用“make zImage”编译的 Linux 内核的话,首先肯定要先编译出 vmlinux。
比如要编译 zImage,那么命令展开以后如下所示:
@ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/zImage
也就是使用 scripts/Makefile.build 文件来完成 vmlinux 到 zImage 的转换。
本讲的重点,在于vmlinux 的生成,最后将 vmlinux 压缩成我们最常用的 zImage 或 uImage 等文件。