编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

Linux 源代码makefile文件功能解析

wxchong 2025-05-02 13:57:16 开源技术 4 ℃ 0 评论

**Linux 内核源码树根目录下的 `Makefile`**。这是一个非常核心且复杂的文件,它的主要作用是**组织和驱动整个 Linux 内核的编译、配置和安装过程**。可以把它看作是构建 Linux 内核的总指挥或“菜谱”。


由于其复杂性,我会分块解释其主要功能和含义,并尽量详细:


**一、 文件头部与基本信息**


```makefile

# SPDX-License-Identifier: GPL-2.0

VERSION = 6

PATCHLEVEL = 15

SUBLEVEL = 0

EXTRAVERSION = -rc1

NAME = Baby Opossum Posse

```


* **`SPDX-License-Identifier`**: 指定了该文件的许可证是 GPL-2.0。

* **`VERSION`, `PATCHLEVEL`, `SUBLEVEL`, `EXTRAVERSION`, `NAME`**: 定义了当前内核的版本号和名称。这些变量组合起来构成内核的版本字符串(例如 `6.15.0-rc1`),并用于各种地方,如模块路径、版本信息显示等。`NAME` 是一个有趣的代号。


**二、 文档与帮助信息**


```makefile

# *DOCUMENTATION*

# To see a list of typical targets execute "make help"

# More info can be located in ./README

# Comments in this file are targeted only to the developer, do not

# expect to learn how to build the kernel reading this file.

```


* 明确指出可以通过 `make help` 获取常用目标列表。

* 说明更多信息在 `README` 文件中。

* 强调此文件的注释主要面向开发者,而非初学者教程。


**三、 GNU Make 版本检查与内部目标限制**


```makefile

ifeq ($(filter output-sync,$(.FEATURES)),)

$(error GNU Make >= 4.0 is required. Your Make version is $(MAKE_VERSION))

endif


$(if $(filter __%, $(MAKECMDGOALS)), \

$(error targets prefixed with '__' are only for internal use))

```


* 检查 `make` 命令的版本是否 `>= 4.0`,因为内核构建系统依赖特定版本的 Make 功能。如果版本过低,会报错并退出。

* 禁止用户直接调用以 `__` 开头的内部目标,这些目标仅供 Makefile 内部逻辑使用。


**四、 默认目标与递归构建说明**


```makefile

# That's our default target when none is given on the command line

PHONY := __all

__all:


# ... (递归构建的说明注释) ...

```


* `__all` 是默认目标。如果在命令行没有指定目标(直接运行 `make`),则会执行 `__all` 目标。

* 注释解释了 Linux 内核使用**递归构建 (Recursive Build)** 的方式。这意味着顶层 Makefile 会调用子目录中的 Makefile 来构建各个部分。这种方式的关键在于确保正确的构建顺序和依赖关系,子 Makefile 通常只修改自己目录下的文件。


**五、 路径设置与环境准备 (首次调用时)**


```makefile

this-makefile := $(lastword $(MAKEFILE_LIST))

abs_srctree := $(realpath $(dir $(this-makefile))) # 内核源代码根目录的绝对路径

abs_output := $(CURDIR) # 当前工作目录,默认是输出目录


ifneq ($(sub_make_done),1) # 检查是否是首次调用


# ... (设置 MAKEFLAGS, LC_*, GREP_OPTIONS 等环境变量) ...


# ... (设置编译输出的详细程度 KBUILD_VERBOSE, quiet, Q) ...


# ... (设置静态代码检查 KBUILD_CHECKSRC, C=1/2) ...


# ... (设置 Rust linter KBUILD_CLIPPY, CLIPPY=1) ...


# ... (处理外部模块构建 KBUILD_EXTMOD, M=dir) ...


# ... (处理额外的警告 W=n) ...


# ... (处理输出目录 KBUILD_OUTPUT, O=dir) ...

# 确定 objtree (实际编译输出根目录) 和 srcroot (源码根目录,对于外部模块可能是模块目录)


export sub_make_done := 1 # 标记首次调用已完成


endif # sub_make_done

```


* 这段代码只在 `make` 命令第一次被调用时执行(通过 `sub_make_done` 变量控制)。

* **路径确定:** 获取源代码树 (`abs_srctree`) 和当前工作目录 (`abs_output`) 的绝对路径。

* **环境变量清理:** 清理一些可能干扰构建的环境变量,如 `LC_ALL`, `GREP_OPTIONS`,并设置 `LC_COLLATE`, `LC_NUMERIC` 为 `C` 以保证一致性。

* **输出控制 (`V=`, `quiet`, `Q`):** 设置构建过程的输出详细程度。`make V=1` 会显示完整的编译命令。`@` 符号 (`Q`) 用于在非 verbose 模式下隐藏命令本身。

* **静态检查 (`C=1/2`, `KBUILD_CHECKSRC`):** 配置是否在编译 C 代码时运行静态检查工具(如 `sparse`)。`C=1` 检查重新编译的文件,`C=2` 检查所有文件。

* **Rust Linter (`CLIPPY=1`, `KBUILD_CLIPPY`):** 配置是否在编译 Rust 代码时运行 `clippy` linter。

* **外部模块 (`M=dir`, `KBUILD_EXTMOD`):** 处理构建内核树之外的模块(驱动程序等)。`M=dir` 指定外部模块的源代码目录。

* **额外警告 (`W=n`, `KBUILD_EXTRA_WARN`):** 允许用户启用额外的编译器警告级别。

* **输出目录 (`O=dir`, `KBUILD_OUTPUT`):** 这是非常重要的功能,允许用户将编译生成的文件(包括 `.config`、目标文件、最终内核镜像等)放在与源代码树不同的目录中,保持源代码树的清洁。`O=` 命令行参数优先于 `KBUILD_OUTPUT` 环境变量。

* `objtree`: 最终确定的编译输出根目录。

* `srcroot`: 对于内核构建是源码根目录,对于外部模块构建是该模块的源码目录。

* **标记完成:** 设置 `sub_make_done := 1`,这样下次递归调用 `make` 时就不会再执行这段初始化代码。


**六、 递归调用处理**


```makefile

ifeq ($(abs_output),$(CURDIR))

# ... (如果在最终输出目录,设置 --no-print-directory) ...

else

# ... (需要递归调用) ...

need-sub-make := 1

endif


# ... (处理 Make 4.4.1 的兼容性问题) ...


ifeq ($(need-sub-make),1)

# ... (定义 __sub-make 目标) ...

__sub-make:

$(Q)$(MAKE) $(no-print-directory) -C $(abs_output) \

-f $(abs_srctree)/Makefile $(MAKECMDGOALS)

else

# ... (进入实际的构建逻辑) ...

endif

```


* 这段逻辑判断当前 `make` 调用是否在最终的输出目录 (`abs_output`) 中。

* 如果**不在**(通常是用户直接在源代码目录运行 `make O=...` 的情况),则设置 `need-sub-make := 1`。

* 如果 `need-sub-make` 为 1,则定义一个 `__sub-make` 目标。其他所有目标(或默认的 `__all`)都依赖于 `__sub-make`。

* `__sub-make` 规则的作用是:**再次调用 `make` 命令,但这次使用 `-C $(abs_output)` 切换到指定的输出目录**,并使用 `-f $(abs_srctree)/Makefile` 指定使用源代码树中的 Makefile。这样后续所有的构建操作都在输出目录中进行。

* `$(no-print-directory)` 用于控制是否显示 `make` 进入/离开目录的消息。


**七、 实际构建逻辑 (在最终输出目录中执行时)**


这部分是当 `make` 最终在 `abs_output` 目录(可能是 `.`,也可能是 `O=` 指定的目录)中执行时的核心逻辑。


```makefile

# ... (确定 srcroot, srctree, VPATH 等变量) ...


# ... (处理 *config 目标,调用 scripts/kconfig/Makefile) ...

# 区分纯配置目标和混合目标 (如 make oldconfig all)

# 判断是否需要读取 .config (need-config)

# 判断是否需要同步配置 (may-sync-config)


include $(srctree)/scripts/Kbuild.include # 包含 Kbuild 辅助函数


# ... (读取 KERNELRELEASE, 设置 KERNELVERSION) ...


# ... (设置架构相关的变量 ARCH, SRCARCH, UTS_MACHINE) ...


# ... (设置交叉编译 CROSS_COMPILE) ...


# ... (设置 KCONFIG_CONFIG 文件路径) ...


# ... (设置 HOST 工具链变量 HOSTCC, HOSTCXX, HOSTRUSTC 等) ...


# ... (设置 HOST 编译/链接标志 KBUILD_HOSTCFLAGS, KBUILD_HOSTLDFLAGS 等) ...


# ... (设置目标 (内核) 工具链变量 CC, LD, AR, NM, RUSTC 等) ...

# 根据是否定义 LLVM 环境变量来选择 clang/lld 或 gcc/ld 工具链


# ... (设置 KBUILD 构建标志 KBUILD_CFLAGS, KBUILD_RUSTFLAGS, KBUILD_AFLAGS, KBUILD_CPPFLAGS) ...

# 包含大量的默认编译选项,如 -std=gnu11, -fno-common, Rust edition 等


# ... (根据内核配置 CONFIG_xxx 打开或关闭特定的编译/链接选项) ...

# 例如:优化级别 (-O2, -Os), 栈保护, Ftrace, LTO, CFI, GCOV, 调试信息, KASAN, UBSAN 等


# ... (包含特定功能的 Makefile 片段,如 scripts/Makefile.debug, scripts/Makefile.kasan) ...


# ... (添加用户自定义的标志 KCFLAGS, KRUSTFLAGS 等) ...


# ... (设置最终的链接器标志 LDFLAGS_vmlinux, KBUILD_LDFLAGS_MODULE) ...


# ... (核心目标定义) ...

ifeq ($(KBUILD_EXTMOD),) # 如果不是构建外部模块

# ... (定义内核构建需要的目标和依赖) ...

core-y:= # 内核核心对象

drivers-y:= # 驱动对象

libs-y:= # 库对象

export KBUILD_VMLINUX_OBJS # vmlinux 链接需要的目标文件列表

export KBUILD_VMLINUX_LIBS # vmlinux 链接需要的库文件列表

export KBUILD_LDS # 内核链接脚本路径


vmlinux.a: ... # 将各部分 .a 文件归档成 vmlinux.a

vmlinux: vmlinux.o ... modpost # 最终链接生成 vmlinux 文件

$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.vmlinux

else # 如果是构建外部模块

# ... (外部模块的特定逻辑) ...

modules: modules_prepare

endif


# ... (定义 prepare 目标及其依赖) ...

# prepare0, prepare, archprepare: 在递归构建开始前需要完成的准备工作

# 例如:生成头文件、版本文件、检查配置同步、编译脚本等


# ... (定义头文件生成和安装目标 headers, headers_install) ...


# ... (定义安装目标 install, vdso_install) ...


# ... (定义内核工具构建目标 tools/, tools/%) ...


# ... (定义内核自测试目标 kselftest, kselftest-*) ...


# ... (定义设备树 (Devicetree) 相关目标 dtbs, dtbs_install, dtbs_check) ...

# 只在相关架构下有效


# ... (定义模块构建相关目标 modules, modules_install, modpost) ...

# modpost: 处理模块符号、生成 Module.symvers 等


# ... (定义清理目标 clean, mrproper, distclean) ...

# clean: 删除大部分生成文件,保留配置和外部模块构建所需文件

# mrproper: 删除所有生成文件和配置

# distclean: mrproper + 编辑器备份文件等


# ... (定义打包目标 %pkg, %src-pkg) ...


# ... (定义 help 目标,打印帮助信息) ...


# ... (定义静态分析和检查目标 includecheck, versioncheck, coccicheck, checkstack) ...


# ... (定义 Rust 相关目标 rustavailable, rustfmt, rustdoc, rusttest, rust-analyzer) ...


# ... (处理单一目标构建,如 make fs/ext4/ext4.o) ...


# ... (读取之前保存的目标命令缓存 .cmd 文件) ...


.PHONY: $(PHONY) # 声明所有 PHONY 目标

```


**总结来说,这个 Makefile 的核心功能是:**


1. **配置管理:** 处理内核配置选项 (`make *config`),生成配置文件 (`.config`, `auto.conf`, `autoconf.h`)。

2. **环境设置:** 设置编译所需的各种环境变量、工具链路径、编译和链接标志。

3. **依赖管理:** 定义了复杂的依赖关系,确保文件按正确的顺序编译和链接。

4. **递归构建:** 通过调用子目录中的 `Kbuild` 或 `Makefile` 文件来构建内核的各个子系统。

5. **目标构建:** 定义了各种构建目标,如:

* `vmlinux`: 构建核心内核镜像。

* `modules`: 构建可加载内核模块 (`.ko` 文件)。

* `dtbs`: 构建设备树文件。

* `headers_install`: 安装内核头文件供用户空间使用。

* `install`: 安装内核和模块。

* `clean`/`mrproper`/`distclean`: 清理构建生成的文件。

6. **工具链选择:** 支持 GCC 和 Clang/LLVM 工具链。

7. **语言支持:** 支持 C 和 Rust 语言的编译。

8. **外部模块支持:** 允许编译不在内核源码树中的模块。

9. **可定制性:** 提供了大量变量(如 `ARCH`, `CROSS_COMPILE`, `O`, `V`, `C`, `W`)供用户在命令行或环境 中设置,以定制构建过程。

10. **自动化与检查:** 集成了静态分析、格式检查、版本检查等工具。


理解这个 Makefile 需要对 `make` 工具的工作原理、C 语言编译链接过程以及 Linux 内核的模块化结构有深入的了解。它是 Kbuild 系统(Linux 内核的构建系统)的核心驱动文件。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表