[TOC]

阅读技巧

  1. 使用make V=1来查看详细的编译过程
  2. 使用make -p来查看所有的变量和规则

顶层makefile

判断GUN make 版本

  • 检查当前的 Make 版本以确保满足条件
  • 如果 output-sync 不在其中,这意味着当前使用的 GNU Make 版本低于 4.0,因此代码会触发一个错误,提示用户需要 GNU Make 4.0 或更高版本,并显示当前的 Make 版本 $(MAKE_VERSION)。
1
2
3
ifeq ($(filter output-sync,$(.FEATURES)),)
$(error GNU Make >= 4.0 is required. Your Make version is $(MAKE_VERSION))
endif

检测内部变量是否被覆盖

  • 检查 $(MAKECMDGOALS) 变量中是否包含以 __ 开头的目标。$(MAKECMDGOALS) 是一个特殊变量,包含了命令行中指定的所有目标。如果发现有以 __ 开头的目标,代码会触发一个错误,提示这些目标仅供内部使用。这种检查有助于防止用户意外调用内部目标,确保构建过程的正确性和安全性。
1
2
$(if $(filter __%, $(MAKECMDGOALS)), \
$(error targets prefixed with '__' are only for internal use))

makefile路径引用

  • $(MAKEFILE_LIST) 是一个特殊变量,它包含了当前正在处理的所有 Makefile 文件的列表。$(lastword …) 函数会返回这个列表中的最后一个元素,即当前正在执行的 Makefile 文件的路径。
  • $(dir …) 函数会返回 this-makefile 所在目录的路径,而 $(realpath …) 函数会将这个路径转换为绝对路径。因此,abs_srctree 变量最终包含了当前 Makefile 文件所在目录的绝对路径。
1
2
this-makefile := $(lastword $(MAKEFILE_LIST))
abs_srctree := $(realpath $(dir $(this-makefile)))

指定模块输出目录

  • word函数获取KBUILD_EXTMOD的第二个单词,如果有则报错
  • foreach%:赋值给x,然后执行findstring函数,从KBUILD_EXTMOD中查找$(x),如果找到,则触发错误,提示模块目录路径不能包含
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
# 不允许有两个模块目录
$(if $(word 2, $(KBUILD_EXTMOD)), \
$(error building multiple external modules is not supported))
# 不允许有特殊字符 % 和 :
$(foreach x, % :, $(if $(findstring $x, $(KBUILD_EXTMOD)), \
$(error module directory path cannot contain '$x')))

ifdef KBUILD_EXTMOD
ifdef KBUILD_OUTPUT
objtree := $(realpath $(KBUILD_OUTPUT))
$(if $(objtree),,$(error specified kernel directory "$(KBUILD_OUTPUT)" does not exist))
else
objtree := $(abs_srctree)
endif
# $(or ...):这是一个逻辑运算函数,它会依次检查其参数,并返回第一个非空的参数值。如果所有参数都为空,则返回空字符串。
# $(filter $(CURDIR),$(objtree) $(abs_srctree)),$(KBUILD_EXTMOD)) 从和 objtree 和 abs_srctree 中查找 CURDIR.并输出到KBUILD_EXTMOD
# output 在KBUILD_EXTMOD_OUTPUT有值时,使用KBUILD_EXTMOD_OUTPUT,否则使用KBUILD_EXTMOD
output := $(or $(KBUILD_EXTMOD_OUTPUT),$(if $(filter $(CURDIR),$(objtree) $(abs_srctree)),$(KBUILD_EXTMOD)))
srcroot := $(realpath $(KBUILD_EXTMOD))
$(if $(srcroot),,$(error specified external module directory "$(KBUILD_EXTMOD)" does not exist))
else
objtree := .
output := $(KBUILD_OUTPUT)
endif

判断源目录不能包含空格或冒号

  1. subst:替换为空格
  2. words函数计算替换后的单词数
  3. ifneq判断单词数是否不等于1,如果不等于1,则报错
1
2
3
ifneq ($(words $(subst :, ,$(abs_srctree))), 1)
$(error source directory cannot contain spaces or colons)
endif

KBUILD_BUILTIN 编译内置对象 KBUILD_MODULES 编译模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 决定是构建 built-in、modular,还是两者兼而有之。
# 通常,只做 built-in.

KBUILD_MODULES :=
KBUILD_BUILTIN := 1

# 如果我们只有 “make modules”,就不要编译内置对象。
ifeq ($(MAKECMDGOALS),modules)
KBUILD_BUILTIN :=
endif

# 如果我们有 “make <whatever> modules”,编译 modules
# 除了我们无论如何所做的任何事情。
# 只需 “make” 或 “make all” 也可以构建模块

ifneq ($(filter all modules nsdeps compile_commands.json clang-%,$(MAKECMDGOALS)),)
KBUILD_MODULES := 1
endif

ifeq ($(MAKECMDGOALS),)
KBUILD_MODULES := 1
endif

scripts 脚本构建

scripts/Kbuild.include 通用定义

1
2
# 顶层makefile导入Kbuild.include文件
include $(srctree)/scripts/Kbuild.include

cmd 判定有调用命令执行,无调用报错

  • 这段代码的目的是根据条件执行特定的命令。如果 cmd_$(1) 变量非空,则执行一系列命令,包括设置错误退出模式、打印日志信息、执行清理操作以及实际的命令执行。如果 cmd_$(1) 变量为空,则执行空命令。这种结构有助于在Makefile中实现灵活的命令控制和错误处理
1
2
3
# scripts/Kbuild.include
# print and execute commands
cmd = @$(if $(cmd_$(1)),set -e; $($(quiet)log_print) $(delete-on-interrupt) $(cmd_$(1)),:)

$(build) 设置编译的目录为Makefile.build 目标为输入的目录

  • 调用scripts/Makefile.build的构建规则,执行目标为输入的构建内容
1
2
3
4
5
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj

if_changed 执行命令并后写入命令到dot-target.cmd

  • if_changed的作用是执行一个命令,并在命令执行后处理生成的 .d 依赖项文件。它会检查命令是否发生了变化,如果发生了变化,则执行命令并更新依赖项文件。
  • 并且会写入执行的命令到dot-target.cmd
  • $(cmd)调用cmd函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查找任何比 target 更新或不存在的先决条件。
# 在这两种情况下都跳过了 PHONY 目标。
# 如果除了 phony 目标之外没有其他先决条件,则 $(newer-prereqs) 变为
# 即使目标不存在,也为空。cmd-check 保存此极端情况。
newer-prereqs = $(filter-out $(PHONY),$?)

# 忘记 FORCE 先决条件是一个典型的错误。在这里检查一下不会再有破损溜进来。
check-FORCE = $(if $(filter FORCE, $^),,$(warning FORCE prerequisite is missing))

if-changed-cond = $(newer-prereqs)$(cmd-check)$(check-FORCE)

cmd_and_savecmd = \
$(cmd); \
printf '%s\n' 'savedcmd_$@ := $(make-cmd)' > $(dot-target).cmd

# 如果命令已更改或先决条件已更新,则执行命令。
if_changed = $(if $(if-changed-cond),$(cmd_and_savecmd),@:)

if_changed_dep 执行命令并后处理生成的 .d 依赖项文件,将命令写入dot-target.cmd

  • 执行cmd函数,调用 fixdep 工具来处理依赖关系。
  • $(depfile) 是依赖文件的路径
  • $@ 是一个自动变量,表示当前目标的名称。
  • $(make-cmd) 是一个变量,通常包含 make 命令的调用。
  • $(dot-target).cmd 将 fixdep 工具的输出重定向到 $(dot-target).cmd 文件中

  • 最后删除依赖文件
1
2
3
4
5
6
if_changed_dep = $(if $(if-changed-cond),$(cmd_and_fixdep),@:)

cmd_and_fixdep = \
$(cmd); \
$(objtree)/scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\
rm -f $(depfile)

scripts/Makefile.lib

subdir path

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Add subdir path

ifneq ($(obj),.) # obj对象不是当前路径
# addprefix 函数用于在每个单词前添加一个前缀。第一个参数是前缀,第二个参数是要添加前缀的单词列表。
# 例如obj=scripts/basic/fixdep,则会将scripts/basic/fixdepp添加到每个单词前.always-y = scripts/basic/fixdep
extra-y := $(addprefix $(obj)/,$(extra-y))
always-y := $(addprefix $(obj)/,$(always-y))
targets := $(addprefix $(obj)/,$(targets))
obj-m := $(addprefix $(obj)/,$(obj-m))
lib-y := $(addprefix $(obj)/,$(lib-y))
real-obj-y := $(addprefix $(obj)/,$(real-obj-y))
real-obj-m := $(addprefix $(obj)/,$(real-obj-m))
multi-obj-m := $(addprefix $(obj)/, $(multi-obj-m))
subdir-ym := $(addprefix $(obj)/,$(subdir-ym))
endif

always-y 总是构建的目标

1
2
3
4
5
6
7
8
9
always-y += $(always-m)	#为空 scripts/Makefile.build line:l9
always-y += $(hostprogs-always-y) $(hostprogs-always-m) # 从构建的目标目录中获取
# userprogs-always-y 也是这样。
always-y += $(userprogs-always-y) $(userprogs-always-m)
ifneq ($(obj),.) # obj对象不是当前路径
# addprefix 函数用于在每个单词前添加一个前缀。第一个参数是前缀,第二个参数是要添加前缀的单词列表。
# 例如obj=scripts/basic/fixdep,则会将scripts/basic/fixdepp添加到每个单词前.always-y = scripts/basic/fixdep
always-y := $(addprefix $(obj)/,$(always-y))
endif

subdir-ym 子目录构建的目标

suffix-search multi-search real-search 搜索

  1. suffix-search: 遍历后缀列表 $3,并将 $1 中匹配 $(strip $2) 后缀的部分替换为每个后缀 s。通过这种方式,可以扩展文件列表或模式,将其转换为具有不同后缀的文件列表。
  2. strip: 删除字符串两端的空格
  3. multi-search: 遍历 $1 中的每个元素 m,并检查是否存在与 $2$3 匹配的后缀。如果存在,则返回该元素 m
  4. real-search: 遍历 $1 中的每个元素 m,并检查是否存在与 $2$3 匹配的后缀。如果存在,则返回扩展后的文件列表,否则返回原始元素 m
1
2
3
4
5
6
# Expand $(foo-objs) $(foo-y) etc. by replacing their individuals
suffix-search = $(strip $(foreach s, $3, $($(1:%$(strip $2)=%$s))))
# List composite targets that are constructed by combining other targets
multi-search = $(sort $(foreach m, $1, $(if $(call suffix-search, $m, $2, $3 -), $m)))
# List primitive targets that are compiled from source files
real-search = $(foreach m, $1, $(if $(call suffix-search, $m, $2, $3 -), $(call suffix-search, $m, $2, $3), $m))

multi-obj-y multi-obj-m multi-obj-ym real-obj-y real-obj-m 对象

  1. multi-obj-y: .o-objs -y匹配的对象
  2. multi-obj-m: .o-objs -y -m匹配的对象
  3. multi-obj-ym: multi-obj-ymulti-obj-m的组合
  4. real-obj-y: .o-objs -y匹配的对象
  5. real-obj-m: .o-objs -y -m匹配的对象
1
2
3
4
5
6
7
8
9
# If $(foo-objs), $(foo-y), $(foo-m), or $(foo-) exists, foo.o is a composite object
multi-obj-y := $(call multi-search, $(obj-y), .o, -objs -y)
multi-obj-m := $(call multi-search, $(obj-m), .o, -objs -y -m)
multi-obj-ym := $(multi-obj-y) $(multi-obj-m)

# Replace multi-part objects by their individual parts,
# including built-in.a from subdirectories
real-obj-y := $(call real-search, $(obj-y), .o, -objs -y)
real-obj-m := $(call real-search, $(obj-m), .o, -objs -y -m)

scripts/Makefile.build

i最开始的nclude

1
2
3
4
5
6
7
8
9
# Read auto.conf if it exists, otherwise ignore
-include $(objtree)/include/config/auto.conf

include $(srctree)/scripts/Kbuild.include
include $(srctree)/scripts/Makefile.compiler
# 顶层Kbuild 或 Makefile 的路径。Kbuild 优先于 Makefile。
# kbuild-file = $(or $(wildcard $(src)/Kbuild),$(src)/Makefile)
include $(kbuild-file)
include $(srctree)/scripts/Makefile.lib

必要时包含其他构建规则

  • 其中$(hostprogs)由scripts/Makefile.lib传入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# scripts/Makefile.lib
hostprogs += $(hostprogs-always-y) $(hostprogs-always-m)
always-y += $(hostprogs-always-y) $(hostprogs-always-m)

# scripts/Makefile.build
# $(sort ...) 此处用于删除重复的单词和过多的空格。
hostprogs := $(sort $(hostprogs))
ifneq ($(hostprogs),)
include $(srctree)/scripts/Makefile.host
endif

# $(sort ...) 此处用于删除重复的单词和过多的空格。
userprogs := $(sort $(userprogs))
ifneq ($(userprogs),)
include $(srctree)/scripts/Makefile.userprogs
endif

ifneq ($(need-dtbslist)$(dtb-y)$(dtb-)$(filter %.dtb %.dtb.o %.dtbo.o,$(targets)),)
include $(srctree)/scripts/Makefile.dtbs
endif

$(obj)/: 构建传入的目录下的目标

  • 如果KBUILD_BUILTIN有值,则构建targets-for-builtin
  • 如果KBUILD_MODULES有值,则构建targets-for-modules
  • subdir-ym$(always-y)在此时还没有在本文件值,在后面会赋值;在这里是scripts/Makefile.lib中进行了赋值
1
2
3
4
5
6
7
8
9
10
# scripts/Makefile.lib
# addprefix 函数用于在每个单词前添加一个前缀。第一个参数是前缀,第二个参数是要添加前缀的单词列表。
# 例如obj=scripts/basic/fixdep,则会将scripts/basic/fixdepp添加到每个单词前.always-y = scripts/basic/fixdep
always-y := $(addprefix $(obj)/,$(always-y))
subdir-ym := $(addprefix $(obj)/,$(subdir-ym))
# scripts/Makefile.build
$(obj)/: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
$(if $(KBUILD_MODULES), $(targets-for-modules)) \
$(subdir-ym) $(always-y)
@:

targets-for-builtin 内置目标

  1. targets-for-builtin = $(extra-y)
  2. $(lib-y)$(lib-m)$(lib-)有一个不为空,添加构建$(obj)/lib.a
  3. 有定义need-builtin ,添加构建$(obj)/built-in.a
1
2
3
4
5
6
7
8
9
targets-for-builtin := $(extra-y)
# strip将 $(lib-y)、$(lib-m) 和 $(lib-) 的值连接起来,并去除其中的空白字符
ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),)
targets-for-builtin += $(obj)/lib.a
endif

ifdef need-builtin
targets-for-builtin += $(obj)/built-in.a
endif

scripts/basic fixdep:用于在构建过程中生成依赖信息

1
hostprogs-always-y	+= fixdep

scripts/Makefile.host 主机程序构建

host-csingle 从单个 .c 文件创建可执行文件

1
2
3
4
5
6
7
8
# Create executable from a single .c file
# host-csingle -> Executable
# `$<` 引用第一个依赖项
quiet_cmd_host-csingle = HOSTCC $@
cmd_host-csingle = $(HOSTCC) $(hostc_flags) $(KBUILD_HOSTLDFLAGS) -o $@ $< \
$(KBUILD_HOSTLDLIBS) $(HOSTLDLIBS_$(target-stem))
$(host-csingle): $(obj)/%: $(obj)/%.c FORCE
$(call if_changed_dep,host-csingle)
  • 示例如下
1
2
# HOSTCC  scripts/basic/fixdep
gcc -Wp,-MMD,scripts/basic/.fixdep.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -o scripts/basic/fixdep scripts/basic/fixdep.c

outputmakefile 输出目录的构建

  • 可以看到outputmakefile的构建依赖于building_out_of_srctree变量,在不是当前目录构建时才会触发
1
2
3
ifdef building_out_of_srctree
outputmakefile:
endif
  • building_out_of_srctree在构建目录就是当前目录时,没有值,否则有值
1
2
3
4
5
ifeq ($(srcroot),$(CURDIR))
building_out_of_srctree :=
else
export building_out_of_srctree := 1
endif
  • 如果判定源码树不干净,将会打印错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
outputmakefile:
ifeq ($(KBUILD_EXTMOD),)
@if [ -f $(srctree)/.config -o \
-d $(srctree)/include/config -o \
-d $(srctree)/arch/$(SRCARCH)/include/generated ]; then \
echo >&2 "***"; \
echo >&2 "*** The source tree is not clean, please run 'make$(if $(findstring command line, $(origin ARCH)), ARCH=$(ARCH)) mrproper'"; \
echo >&2 "*** in $(abs_srctree)";\
echo >&2 "***"; \
false; \
fi
else
@if [ -f $(srcroot)/modules.order ]; then \
echo >&2 "***"; \
echo >&2 "*** The external module source tree is not clean."; \
echo >&2 "*** Please run 'make -C $(abs_srctree) M=$(realpath $(srcroot)) clean'"; \
echo >&2 "***"; \
false; \
fi
endif
  • 具体的构建过程如下:
    1. 创建软连接
    2. 生成Makefile,有KBUILD_EXTMOD时,会将KBUILD_OUTPUTKBUILD_EXTMOD_OUTPUT写入Makefile;
      没有KBUILD_EXTMOD时,会将KBUILD_OUTPUT写入Makefile
    3. 生成.gitignore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ifdef KBUILD_EXTMOD
print_env_for_makefile = \
echo "export KBUILD_OUTPUT = $(objtree)"; \
echo "export KBUILD_EXTMOD = $(realpath $(srcroot))" ; \
echo "export KBUILD_EXTMOD_OUTPUT = $(CURDIR)"
else
print_env_for_makefile = \
echo "export KBUILD_OUTPUT = $(CURDIR)"
endif
quiet_cmd_makefile = GEN Makefile
cmd_makefile = { \
echo "\# Automatically generated by $(abs_srctree)/Makefile: don't edit"; \
$(print_env_for_makefile); \
echo "include $(abs_srctree)/Makefile"; \
} > Makefile
# 在开始树外构建之前,请确保源代码树是干净的。
# outputmakefile 会在输出目录中生成一个 Makefile,如果使用
# 单独的输出目录。这允许在output 目录中。
# 在生成输出 Makefile 的同时,生成 .gitignore 以忽略整个输出目录
$(Q)ln -fsn $(srcroot) source # 创建软连接
$(call cmd,makefile) # 调用quiet_cmd_makefile
$(Q)test -e .gitignore || \ # 检查是否存在.gitignore,不存在则写入注释与忽略规则
{ echo "# this is build directory, ignore it"; echo "*"; } > .gitignore

scripts_basic 基础脚本构建

  • 展开为 make -f ./scripts/Makefile.build obj=scripts/basic
1
2
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
  • ./scripts/Makefile.build执行了$(obj)/:构建
1
$(obj)/: $(subdir-ym) $(always-y)
  • ./scripts/Makefile.build下导入了./scripts/Makefile.lib,将always-ysubdir-ym赋值
1
2
3
4
5
6
7
#./scripts/Makefile.build
include $(srctree)/scripts/Makefile.lib
# ./scripts/Makefile.lib
# addprefix 函数用于在每个单词前添加一个前缀。第一个参数是前缀,第二个参数是要添加前缀的单词列表。
# 例如obj=scripts/basic/fixdep,则会将scripts/basic/fixdepp添加到每个单词前.always-y = scripts/basic/fixdep
always-y := $(addprefix $(obj)/,$(always-y))
subdir-ym := $(addprefix $(obj)/,$(subdir-ym))
  • 所以$(obj)/:构建的目标为fixdep,进而调用./scripts/Makefile.build执行scripts/basic/fixdep的构建
  • 这时候进入scripts/Makefile.lib,hostprogs-always-yscripts/basic/Makefile进行了赋值为fixdep
1
2
hostprogs += $(hostprogs-always-y) $(hostprogs-always-m)
always-y += $(hostprogs-always-y) $(hostprogs-always-m)
  • 所以scripts/Makefile.build导入了scripts/Makefile.host
1
2
3
4
hostprogs := $(sort $(hostprogs))
ifneq ($(hostprogs),)
include $(srctree)/scripts/Makefile.host
endif
  • scripts/Makefile.host中,执行了host-csingle的构建
1
2
$(host-csingle): $(obj)/%: $(obj)/%.c FORCE
$(call if_changed_dep,host-csingle)
  • 最终执行了gcc命令,生成fixdep
1
gcc -Wp,-MMD,scripts/basic/.fixdep.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11   -I ./scripts/include     -o scripts/basic/fixdep scripts/basic/fixdep.c

scripts/basic/fixdep.c 处理依赖关系,确保只有需要修改的配置会被重新编译

  • fixdep 是 Linux 内核构建过程中使用的一个工具,用于处理依赖关系。它的主要作用是优化由 gcc -MD 生成的依赖项列表,以避免不必要的重新编译。以下是 fixdep 的作用及其工作原理的详细解释:

作用

  1. 优化依赖项列表
    fixdep 工具读取由 gcc -MD 生成的依赖项文件,并对其进行处理,以优化依赖关系。具体来说,它会过滤掉对 autoconf.h 的依赖,并添加对每个 include/config/CONFIG_OPTION 文件的依赖。

  2. 避免不必要的重新编译
    通过优化依赖项列表,fixdep 工具可以避免在用户重新运行 make *config 后,重新编译所有包含 autoconf.h 的文件。这样可以显著减少不必要的重新编译,提高构建效率。

工作原理

fixdep 工具的工作原理可以分为以下几个步骤:

  1. 读取依赖项文件
    fixdep 工具首先读取由 gcc -MD 生成的依赖项文件。这个文件包含了目标文件及其依赖的源文件列表。

    1
    2
    3
    void *read_file(const char *filename) {
    // 打开并读取文件内容
    }
  2. 解析依赖项文件
    fixdep 工具解析依赖项文件,提取目标文件及其依赖的源文件。它会过滤掉对 autoconf.h 的依赖,并添加对每个 include/config/CONFIG_OPTION 文件的依赖。

    1
    2
    3
    static void parse_dep_file(char *p, const char *target) {
    // 解析依赖项文件内容
    }
  3. 处理 CONFIG_ 选项*:
    fixdep 工具会查找依赖项文件中提到的所有 CONFIG_* 选项,并将它们添加到依赖关系中。这样,当配置选项发生变化时,相关的目标文件会被重新编译。

    1
    2
    3
    static void parse_config_file(const char *p) {
    // 查找并处理 CONFIG_* 选项
    }
  4. 生成优化后的依赖项列表
    fixdep 工具生成优化后的依赖项列表,并将其输出到标准输出。这个列表包含了目标文件及其依赖的源文件,以及对 include/config/CONFIG_OPTION 文件的依赖。

    1
    2
    3
    int main(int argc, char *argv[]) {
    // 读取依赖项文件并生成优化后的依赖项列表
    }

示例

假设有一个依赖项文件 foo.d,内容如下:

1
foo.o: foo.c bar.h include/generated/autoconf.h

fixdep 工具会读取这个文件,并生成如下的优化后的依赖项列表:

1
2
savedcmd_foo.o := gcc -MD -o foo.o foo.c
foo.o: foo.c bar.h $(wildcard include/config/CONFIG_FOO)

通过这种方式,fixdep 工具可以确保在配置选项发生变化时,相关的目标文件会被重新编译,而不会因为 autoconf.h 的变化而重新编译所有文件。

总结

fixdep 工具在 Linux 内核构建过程中起到了关键作用,通过优化依赖项列表,避免不必要的重新编译,提高了构建效率。它通过读取和解析依赖项文件,处理 CONFIG_* 选项,并生成优化后的依赖项列表,确保构建过程的高效和准确。

scripts/kconfig

scripts/kconfig/conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  gcc -Wp,-MMD,scripts/basic/.fixdep.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11   -I ./scripts/include     -o scripts/basic/fixdep scripts/basic/fixdep.c   
make -f ./scripts/Makefile.build obj=scripts/kconfig stm32_defconfig
# HOSTCC scripts/kconfig/conf.o
gcc -Wp,-MMD,scripts/kconfig/.conf.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -c -o scripts/kconfig/conf.o scripts/kconfig/conf.c
# HOSTCC scripts/kconfig/confdata.o
gcc -Wp,-MMD,scripts/kconfig/.confdata.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -c -o scripts/kconfig/confdata.o scripts/kconfig/confdata.c
# HOSTCC scripts/kconfig/expr.o
gcc -Wp,-MMD,scripts/kconfig/.expr.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -c -o scripts/kconfig/expr.o scripts/kconfig/expr.c
# LEX scripts/kconfig/lexer.lex.c
flex -oscripts/kconfig/lexer.lex.c -L scripts/kconfig/lexer.l
# YACC scripts/kconfig/parser.tab.[ch]
bison -o scripts/kconfig/parser.tab.c --defines=scripts/kconfig/parser.tab.h -t -l scripts/kconfig/parser.y
# HOSTCC scripts/kconfig/lexer.lex.o
gcc -Wp,-MMD,scripts/kconfig/.lexer.lex.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -I ./scripts/kconfig -c -o scripts/kconfig/lexer.lex.o scripts/kconfig/lexer.lex.c
# HOSTCC scripts/kconfig/menu.o
gcc -Wp,-MMD,scripts/kconfig/.menu.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -c -o scripts/kconfig/menu.o scripts/kconfig/menu.c
# HOSTCC scripts/kconfig/parser.tab.o
gcc -Wp,-MMD,scripts/kconfig/.parser.tab.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -I ./scripts/kconfig -c -o scripts/kconfig/parser.tab.o scripts/kconfig/parser.tab.c
# HOSTCC scripts/kconfig/preprocess.o
gcc -Wp,-MMD,scripts/kconfig/.preprocess.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -c -o scripts/kconfig/preprocess.o scripts/kconfig/preprocess.c
# HOSTCC scripts/kconfig/symbol.o
gcc -Wp,-MMD,scripts/kconfig/.symbol.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -c -o scripts/kconfig/symbol.o scripts/kconfig/symbol.c
# HOSTCC scripts/kconfig/util.o
gcc -Wp,-MMD,scripts/kconfig/.util.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ./scripts/include -c -o scripts/kconfig/util.o scripts/kconfig/util.c
# HOSTLD scripts/kconfig/conf
gcc -o scripts/kconfig/conf scripts/kconfig/conf.o scripts/kconfig/confdata.o scripts/kconfig/expr.o scripts/kconfig/lexer.lex.o scripts/kconfig/menu.o scripts/kconfig/parser.tab.o scripts/kconfig/preprocess.o scripts/kconfig/symbol.o scripts/kconfig/util.o
scripts/kconfig/conf --defconfig=arch/arm/configs/stm32_defconfig Kconfig

conf 是 Linux 内核配置系统的一部分,用于处理内核配置文件(.config)的生成和更新。它提供了多种模式来配置内核选项,并确保配置文件的一致性和正确性。以下是 conf 的作用及其工作原理的详细解释:

作用

  1. 生成和更新配置文件
    conf 工具用于生成和更新内核配置文件(.config)。它可以根据用户输入或预定义的配置文件来设置内核选项。

  2. 支持多种配置模式
    conf 工具支持多种配置模式,包括交互式配置、自动配置、同步配置等。每种模式适用于不同的使用场景,提供了灵活的配置方式。

  3. 确保配置文件的一致性
    conf 工具会检查配置文件中的依赖关系和约束条件,确保生成的配置文件是有效的,并且符合内核的要求。

工作原理

conf 工具的工作原理可以分为以下几个步骤:

  1. 解析命令行参数
    conf 工具首先解析命令行参数,以确定使用的配置模式和相关选项。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    static const struct option long_opts[] = {
    {"help", no_argument, NULL, 'h'},
    {"silent", no_argument, NULL, 's'},
    {"oldaskconfig", no_argument, &input_mode_opt, oldaskconfig},
    {"oldconfig", no_argument, &input_mode_opt, oldconfig},
    {"syncconfig", no_argument, &input_mode_opt, syncconfig},
    {"defconfig", required_argument, &input_mode_opt, defconfig},
    {"savedefconfig", required_argument, &input_mode_opt, savedefconfig},
    {"allnoconfig", no_argument, &input_mode_opt, allnoconfig},
    {"allyesconfig", no_argument, &input_mode_opt, allyesconfig},
    {"allmodconfig", no_argument, &input_mode_opt, allmodconfig},
    {"alldefconfig", no_argument, &input_mode_opt, alldefconfig},
    {"randconfig", no_argument, &input_mode_opt, randconfig},
    {"listnewconfig", no_argument, &input_mode_opt, listnewconfig},
    {"helpnewconfig", no_argument, &input_mode_opt, helpnewconfig},
    {"olddefconfig", no_argument, &input_mode_opt, olddefconfig},
    {"yes2modconfig", no_argument, &input_mode_opt, yes2modconfig},
    {"mod2yesconfig", no_argument, &input_mode_opt, mod2yesconfig},
    {"mod2noconfig", no_argument, &input_mode_opt, mod2noconfig},
    {NULL, 0, NULL, 0}
    };
  2. 读取配置文件
    根据命令行参数,conf 工具读取现有的配置文件或生成新的配置文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    switch (input_mode) {
    case defconfig:
    if (conf_read(defconfig_file)) {
    fprintf(stderr, "*** Can't find default configuration \"%s\"!\n", defconfig_file);
    exit(1);
    }
    break;
    case savedefconfig:
    case syncconfig:
    case oldaskconfig:
    case oldconfig:
    case listnewconfig:
    case helpnewconfig:
    case olddefconfig:
    case yes2modconfig:
    case mod2yesconfig:
    case mod2noconfig:
    conf_read(NULL);
    break;
    case allnoconfig:
    case allyesconfig:
    case allmodconfig:
    case alldefconfig:
    case randconfig:
    name = getenv("KCONFIG_ALLCONFIG");
    if (!name)
    break;
    if ((strcmp(name, "") != 0) && (strcmp(name, "1") != 0)) {
    if (conf_read_simple(name, S_DEF_USER)) {
    fprintf(stderr, "*** Can't read seed configuration \"%s\"!\n", name);
    exit(1);
    }
    break;
    }
    switch (input_mode) {
    case allnoconfig: name = "allno.config"; break;
    case allyesconfig: name = "allyes.config"; break;
    case allmodconfig: name = "allmod.config"; break;
    case alldefconfig: name = "alldef.config"; break;
    case randconfig: name = "allrandom.config"; break;
    default: break;
    }
    if (conf_read_simple(name, S_DEF_USER) && conf_read_simple("all.config", S_DEF_USER)) {
    fprintf(stderr, "*** KCONFIG_ALLCONFIG set, but no \"%s\" or \"all.config\" file found\n", name);
    exit(1);
    }
    break;
    default:
    break;
    }
  3. 处理配置选项
    根据配置模式,conf 工具处理配置选项。对于交互式模式,它会提示用户输入配置选项;对于自动模式,它会根据预定义的规则设置配置选项。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    switch (input_mode) {
    case allnoconfig:
    conf_set_all_new_symbols(def_no);
    break;
    case allyesconfig:
    conf_set_all_new_symbols(def_yes);
    break;
    case allmodconfig:
    conf_set_all_new_symbols(def_mod);
    break;
    case alldefconfig:
    conf_set_all_new_symbols(def_default);
    break;
    case randconfig:
    conf_set_all_new_symbols(def_random);
    break;
    case defconfig:
    conf_set_all_new_symbols(def_default);
    break;
    case savedefconfig:
    break;
    case yes2modconfig:
    conf_rewrite_tristates(yes, mod);
    break;
    case mod2yesconfig:
    conf_rewrite_tristates(mod, yes);
    break;
    case mod2noconfig:
    conf_rewrite_tristates(mod, no);
    break;
    case oldaskconfig:
    rootEntry = &rootmenu;
    conf(&rootmenu);
    input_mode = oldconfig;
    /* fall through */
    case oldconfig:
    case listnewconfig:
    case helpnewconfig:
    case syncconfig:
    /* Update until a loop caused no more changes */
    do {
    conf_cnt = 0;
    check_conf(&rootmenu);
    } while (conf_cnt);
    break;
    case olddefconfig:
    default:
    break;
    }
  4. 写入配置文件
    处理完所有配置选项后,conf 工具将生成的配置写入配置文件auto.conf与.config中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    if (input_mode == savedefconfig) {
    if (conf_write_defconfig(defconfig_file)) {
    fprintf(stderr, "*** Error while saving defconfig to: %s\n", defconfig_file);
    return 1;
    }
    } else if (input_mode != listnewconfig && input_mode != helpnewconfig) {
    if (!no_conf_write && conf_write(NULL)) {
    fprintf(stderr, "\n*** Error during writing of the configuration.\n\n");
    exit(1);
    }

    /* Create auto.conf if it does not exist. */
    if (conf_write_autoconf(sync_kconfig) && sync_kconfig) {
    fprintf(stderr, "\n*** Error during sync of the configuration.\n\n");
    return 1;
    }
    }

总结

conf 工具在 Linux 内核配置系统中起到了关键作用,通过支持多种配置模式,生成和更新内核配置文件,并确保配置文件的一致性和正确性。它通过解析命令行参数、读取配置文件、处理配置选项和写入配置文件,提供了灵活且高效的内核配置方式。

*config 构建

  • config选项有如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
配置目标:
config - 使用行定向程序更新当前配置
nconfig - 使用基于 ncurses 菜单的程序更新当前配置
menuconfig - 使用基于菜单的程序更新当前配置
xconfig - 使用基于 Qt 前端的程序更新当前配置
gconfig - 使用基于 GTK+ 前端的程序更新当前配置
oldconfig - 使用提供的 .config 作为基础更新当前配置
localmodconfig - 更新当前配置,禁用未加载的模块
除了由 LMC_KEEP 环境变量保留的那些
localyesconfig - 更新当前配置,将本地模块转换为核心
除了由 LMC_KEEP 环境变量保留的那些
defconfig - 使用 ARCH 提供的默认配置创建新配置
savedefconfig - 将当前配置保存为 ./defconfig(最小配置)
allnoconfig - 创建所有选项都回答为否的新配置
allyesconfig - 创建所有选项都回答为是的新配置
allmodconfig - 创建尽可能选择模块的新配置
alldefconfig - 创建所有符号设置为默认值的新配置
randconfig - 创建所有选项随机回答的新配置
yes2modconfig - 将回答从是更改为模块(如果可能)
mod2yesconfig - 将回答从模块更改为是(如果可能)
mod2noconfig - 将回答从模块更改为否(如果可能)
listnewconfig - 列出新选项
helpnewconfig - 列出新选项和帮助文本
olddefconfig - 与 oldconfig 相同,但将新符号设置为默认值而不提示
tinyconfig - 配置最小可能的内核
testconfig - 运行 Kconfig 单元测试(需要 python3 和 pytest)
  • 可以看到*config的构建都是通过这里来实现的
  • config构建依赖于outputmakefilescripts_basic
1
2
3
4
5
6
7
8
9
10
ifdef config-build
include $(srctree)/arch/$(SRCARCH)/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG CC_VERSION_TEXT RUSTC_VERSION_TEXT

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

%config: outputmakefile scripts_basic FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
endif
  • 举例make stm32def_config
1
2
# 展开为
make -f ./scripts/Makefile.build obj=scripts/kconfig stm32_defconfig
  • 进入scripts/kconfig/Makefile后,执行%_defconfig构建.其中依赖于conf用于处理.config的程序
1
2
3
4
5
%_defconfig: $(obj)/conf
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

# 展开为
scripts/kconfig/conf --defconfig=arch/arm/configs/stm32_defconfig Kconfig
  • 从而执行conf程序,处理stm32_defconfig转成.config

vmlinux 构建

  • 当使用make -j构建时,由于没有指定目标,makefile将会使用第一个目标作为默认构建目标
  • 当编译内核时,vmlinux就是默认目标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 当我们在命令行上没有给出 no 时,这就是我们的默认目标
PHONY := __all
__all:

# 当不是编译模块时,且不是编译*config.config-build为1
# 否则就是编译内核,config-build为0
ifeq ($(KBUILD_EXTMOD),)
ifneq ($(filter %config,$(MAKECMDGOALS)),)
config-build := 1
endif
endif

# 这允许用户只发出 'make' 来构建包含模块的内核默认为 vmlinux,但 arch makefile 通常会添加更多目标
all: vmlinux

ifdef config-build
# config的构建规则
else #!config-build

# 仅构建目标 - 这包括 vmlinux、特定于 arch 的目标、clean目标和其他。通常,除 *config 目标之外的所有目标。
# 如果构建外部模块,我们不关心 all: 规则.但__all依赖于模块
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
__all: all
else
__all: modules
endif
  • vmlinux的构建规则
1
2
3
4
5
6
7
8
PHONY += vmlinux
# 私有变量
vmlinux: private _LDFLAGS_vmlinux := $(LDFLAGS_vmlinux)
# 导出变量
vmlinux: export LDFLAGS_vmlinux = $(_LDFLAGS_vmlinux)
# 依赖及构建
vmlinux: vmlinux.o $(KBUILD_LDS) modpost
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.vmlinux

依赖构建 vmlinux.a

  • 可以看到vmlinux的所有依赖
  • cmd_ar_vmlinux.a : 这段代码定义了如何创建和操作 vmlinux.a 归档文件。它首先删除旧的归档文件,然后使用 ar 工具创建一个新的归档文件,并将指定的对象文件添加到归档文件中。最后,它对归档文件中的对象文件进行排序和移动操作,以确保特定的对象文件位于归档文件的开头。这种方式确保了构建过程中的文件依赖关系和操作的一致性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# '$(AR) mPi' needs 'T' to workaround the bug of llvm-ar <= 14
quiet_cmd_ar_vmlinux.a = AR $@
cmd_ar_vmlinux.a = \
rm -f $@; \
$(AR) cDPrST $@ $(KBUILD_VMLINUX_OBJS); \
$(AR) mPiT $$($(AR) t $@ | sed -n 1p) $@ $$($(AR) t $@ | grep -F -f $(srctree)/scripts/head-object-list.txt)

targets += vmlinux.a
vmlinux.a: $(KBUILD_VMLINUX_OBJS) scripts/head-object-list.txt FORCE
$(call if_changed,ar_vmlinux.a)

PHONY += vmlinux_o
vmlinux_o: vmlinux.a $(KBUILD_VMLINUX_LIBS)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.vmlinux_o

vmlinux.o modules.builtin.modinfo modules.builtin: vmlinux_o
@:
  1. vmlinux.a依赖于KBUILD_VMLINUX_OBJS

    1
    2
    3
    4
    5
    6
    7
    8
    KBUILD_VMLINUX_OBJS := ./built-in.a
    ifdef CONFIG_MODULES
    KBUILD_VMLINUX_OBJS += $(patsubst %/, %/lib.a, $(filter %/, $(libs-y)))
    KBUILD_VMLINUX_LIBS := $(filter-out %/, $(libs-y))
    else
    # 将 $(libs-y) 中的所有目录转换为相应的 lib.a 文件路径,并将结果赋值给 KBUILD_VMLINUX_LIBS
    KBUILD_VMLINUX_LIBS := $(patsubst %/,%/lib.a, $(libs-y))
    endif
  2. KBUILD_VMLINUX_OBJS依赖于built-in.a,built-in.a调用如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
       # scripts/Makefile.build
    # 将一组 .o 文件编译为一个 .a 文件的规则(无符号表)
    #
    # 要使此规则能够抵御“Argument list too long”错误,请删除 $(obj)/ 前缀,然后通过 shell 命令恢复它。

    quiet_cmd_ar_builtin = AR $@
    cmd_ar_builtin = rm -f $@; \
    $(if $(real-prereqs), printf "$(obj)/%s " $(patsubst $(obj)/%,%,$(real-prereqs)) | xargs) \
    $(AR) cDPrST $@
    $(obj)/built-in.a: $(real-obj-y) FORCE
    $(call if_changed,ar_builtin)
    • 展开为:
    1
    2
    3
    # AR      init/built-in.a
    rm -f init/built-in.a; printf "init/%s " main.o version.o do_mounts.o do_mounts_rd.o do_mounts_initrd.o initramfs.o calibrate.o init_task.o | xargs arm-none-eabi-ar cDPrST init/built-in.a
    make -f ./scripts/Makefile.build obj=usr \
  3. $(real-obj-y)所需要的obj-y

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # Kbuild
    # Ordinary directory descending
    # ---------------------------------------------------------------------------

    obj-y += init/
    obj-y += usr/
    obj-y += arch/$(SRCARCH)/
    obj-y += $(ARCH_CORE)
    obj-y += kernel/
    obj-y += certs/
    obj-y += mm/
    obj-y += fs/
    obj-y += ipc/
    obj-y += security/
    obj-y += crypto/
    obj-$(CONFIG_BLOCK) += block/
    obj-$(CONFIG_IO_URING) += io_uring/
    obj-$(CONFIG_RUST) += rust/
    obj-y += $(ARCH_LIB)
    obj-y += drivers/
    obj-y += sound/
    obj-$(CONFIG_SAMPLES) += samples/
    obj-$(CONFIG_NET) += net/
    obj-y += virt/
    obj-y += $(ARCH_DRIVERS)

vmlinux_o 构建

  1. 执行scripts/Makefile.vmlinux_o的makefile
1
2
vmlinux_o: vmlinux.a $(KBUILD_VMLINUX_LIBS)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.vmlinux_o
  1. scripts/Makefile.vmlinux_o的默认构建为如下:
1
__default: vmlinux.o modules.builtin.modinfo modules.builtin
  1. 构建vmlinux.o
  • 这段代码定义了一个链接命令,用于生成 vmlinux.o 文件。它通过指定链接器、链接选项、输入文件和库文件,确保在构建过程中正确地链接所有必要的对象文件和库文件。这种方式确保了构建过程中的文件依赖关系和操作的一致性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ifdef CONFIG_LTO_CLANG
initcalls-lds := .tmp_initcalls.lds
endif

quiet_cmd_ld_vmlinux.o = LD $@
cmd_ld_vmlinux.o = \
$(LD) ${KBUILD_LDFLAGS} -r -o $@ \
$(vmlinux-o-ld-args-y) \
$(addprefix -T , $(initcalls-lds)) \
--whole-archive vmlinux.a --no-whole-archive \
--start-group $(KBUILD_VMLINUX_LIBS) --end-group \
$(cmd_objtool)

define rule_ld_vmlinux.o
$(call cmd_and_savecmd,ld_vmlinux.o)
$(call cmd,gen_objtooldep)
endef

vmlinux.o: $(initcalls-lds) vmlinux.a $(KBUILD_VMLINUX_LIBS) FORCE
$(call if_changed_rule,ld_vmlinux.o)
  • 展开为
1
2
# LD      vmlinux.o
arm-none-eabi-ld -EL -z noexecstack -r -o vmlinux.o --whole-archive vmlinux.a --no-whole-archive --start-group arch/arm/lib/lib.a lib/lib.a --end-group

KBUILD_LDS vmlinux.lds

1
2
3
#Makefile
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
# 例如:arch/arm/kernel/vmlinux.lds

vmlinux 构建

1
2
3
4
5
6
7
8
9
10
ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)

# vmlinux 的最终链接,在最终链接之后具有可选的 arch pass
cmd_link_vmlinux = \
$< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)"; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)

targets += vmlinux
vmlinux: scripts/link-vmlinux.sh vmlinux.o $(KBUILD_LDS) FORCE
+$(call if_changed_dep,link_vmlinux)

Image 构建

  • 使用objcopy将vmlinux转换为Image
  • objcopy 是一个 GNU Binutils 工具,用于在不同的目标文件格式之间转换对象文件。它可以用于从一个格式转换到另一个格式,剥离不需要的部分,或者添加特定的部分。在内核构建过程中,objcopy 通常用于将内核镜像从 ELF 格式转换为二进制格式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# arch/arm/boot/Makefile
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)

$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@

$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
# scripts/Makefile.lib
# Objcopy
# ---------------------------------------------------------------------------

quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@

zImage 构建

  • 通过makefile的指令可以看出使用的是linux系统的gzip命令
    • -n:不保存原始文件名和时间戳。这意味着压缩后的文件不会包含原始文件的名称和修改时间,这对于生成可重复的构建非常有用。
    • -f:强制覆盖输出文件。如果目标文件已经存在,gzip 将覆盖它,而不会提示确认。
    • -9:使用最高压缩级别。gzip 提供了从 -1 到 -9 的压缩级别,其中 -1 是最快的压缩(但压缩比最低),-9 是最慢的压缩(但压缩比最高)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# arch/arm/boot/compressed/Makefile
compress-$(CONFIG_KERNEL_GZIP) = gzip
compress-$(CONFIG_KERNEL_LZO) = lzo_with_size
compress-$(CONFIG_KERNEL_LZMA) = lzma_with_size
compress-$(CONFIG_KERNEL_XZ) = xzkern_with_size
compress-$(CONFIG_KERNEL_LZ4) = lz4_with_size

$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \
$(addprefix $(obj)/, $(OBJS)) \
$(efi-obj-y) FORCE
@$(check_for_multiple_zreladdr)
$(call if_changed,ld)
@$(check_for_bad_syms)

$(obj)/piggy_data: $(obj)/../Image FORCE
$(call if_changed,$(compress-y))

$(obj)/piggy.o: $(obj)/piggy_data
# scripts/Makefile.lib
# Gzip
# ---------------------------------------------------------------------------

quiet_cmd_gzip = GZIP $@
cmd_gzip = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@
1
cat arch/arm/boot/compressed/../Image | gzip -n -f -9 > arch/arm/boot/compressed/piggy_data