找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1036

积分

0

好友

136

主题
发表于 20 小时前 | 查看: 0| 回复: 0

当你下载了一份Linux内核源码,然后在根目录执行简单的 make 命令后,一个功能完整的内核镜像 bzImage 就生成了。这个过程背后究竟发生了什么?庞大的源码树是如何被组织编译的?本文将带你深入Linux内核的顶层Makefile,逐层解析从 make 命令执行到最终 bzImage 生成的完整构建流程。

本文不会手把手教你如何配置和安装自定义内核——这类教程已经很多。我们将聚焦于一个更根本的问题:当你键入 make 时,内核构建系统(Kbuild)到底做了哪些工作。我们将以x86_64架构为例,剖析版本4.2.0-rc3的顶层Makefile(超过1500行),理解其如何协调成千上万个源文件的编译与链接。请注意,我们关注的是标准的、通用的构建场景,不会涉及文档生成、源码清理或交叉编译等特殊任务。

如果你对 make 工具有一定了解会更好,但无论如何,我都会尽力解释清楚每一段关键代码的作用。

内核编译前的准备工作

构建并非从编译C文件开始,而是始于一系列的准备工作:确定输出目录、解析命令行参数、设置环境变量、包含必要的构建脚本等。这一切都发生在顶层Makefile的开头部分。

首先,Makefile定义了内核版本:

VERSION = 4
PATCHLEVEL = 2
SUBLEVEL = 0
EXTRAVERSION = -rc3
NAME = Hurr durr I‘m a sheep

这些变量随后被组合成 KERNELVERSION,用于标识当前构建的版本。

紧接着,是一系列 ifeq 条件判断,用于处理传递给 make 的命令行参数。例如,V=n 选项控制详细输出模式:

ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif
ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif
export quiet Q KBUILD_VERBOSE

Q 变量前的 @ 符号用于抑制命令回显,让输出更简洁。

O=/dir 选项允许你将所有输出文件(包括中间文件)放到指定目录,实现源码与构建产物分离。Makefile会检查该选项,并尝试创建目录,然后递归地在新目录中重新调用自己。

ifeq ($(KBUILD_SRC),)
ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif
ifneq ($(KBUILD_OUTPUT),)
  saved-output := $(KBUILD_OUTPUT)
  KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) && /bin/pwd)
  $(if $(KBUILD_OUTPUT),, $(error failed to create output directory "$(saved-output)"))
  sub-make: FORCE
    $(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))
  skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

接下来设置架构相关变量。通过 uname -m 获取机器硬件名,并规范化为内核认可的架构名称,如 x86_64 被规范为 x86

SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ -e s/sa110/arm/ \
-e s/s390x/s390/ -e s/parisc64/parisc/ \
-e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
-e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ )

SRCARCHhdr-arch 变量则据此确定特定架构的源码和头文件目录。

然后,定义编译所需的核心工具链,包括针对主机程序的编译器 HOSTCC 和针对内核本身的目标编译器 CC

HOSTCC = gcc
HOSTCXX = g++
HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
HOSTCXXFLAGS = -O2

AS      = $(CROSS_COMPILE)as
LD      = $(CROSS_COMPILE)ld
CC      = $(CROSS_COMPILE)gcc
CPP     = $(CC) -E
AR      = $(CROSS_COMPILE)ar
NM      = $(CROSS_COMPILE)nm
STRIP   = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
AWK     = awk

gcc 作为核心的 C/C++ 编译器,其参数设置直接影响生成代码的质量与特性。

随后,Makefile包含了Kbuild系统的核心脚本:

include scripts/Kbuild.include

这个文件定义了许多构建相关的通用函数和变量。接着,它设置了头文件包含路径和内核的默认编译标志(如 -Wall, -std=gnu89 等)。

在完成所有这些变量的设置和导出后,构建的准备工作就告一段落。真正的编译流程即将开始。

内核构建核心流程:vmlinux的生成

准备工作完成后,顶层Makefile将目标指向了 all: vmlinuxvmlinux 是一个静态链接的、ELF格式的完整内核映像。但在此之前,需要包含架构相关的Makefile(对于x86_64,就是 arch/x86/Makefile),以获取架构特定的构建规则。

vmlinux 目标定义如下:

vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE

它依赖一个链接脚本 scripts/link-vmlinux.shvmlinux-depsvmlinux-deps 本身由内核的链接脚本、初始化代码和主代码三部分组成,具体展开后包含了内核各个顶层目录生成的 built-in.o 文件列表,例如:

arch/x86/kernel/vmlinux.lds arch/x86/kernel/head_64.o
arch/x86/kernel/head64.o arch/x86/kernel/head.o
init/built-in.o usr/built-in.o
fs/built-in.o block/built-in.o
...

为了生成这些 built-in.o,构建系统需要递归地进入各个子目录。这由 vmlinux-dirs 目标触发:

$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=$@

vmlinux-dirs 包含了像 init, arch/x86, kernel, drivers 这样的目录列表。但在此之前,它依赖于 preparescripts 两个目标。

prepare 阶段:这是一个多层级的准备工作。prepare0 -> archprepare -> prepare1 -> prepare2archprepare 会生成系统调用表,并编译一些架构相关的工具,如 arch/x86/tools/relocs(用于处理重定位信息)。同时,它会先执行 scripts_basic 目标来编译两个基础的主机工具:

scripts_basic:
    $(Q)$(MAKE) $(build)=scripts/basic

scripts/basic/ 目录下,会编译 fixdep(用于优化gcc生成的依赖关系)和可选的 bin2c 工具。这是你在执行 make 后看到的第一行输出:HOSTCC scripts/basic/fixdep

scripts 阶段:编译更多构建所需的主机工具,例如 modpost(模块后处理)、file2alias 等,这些工具后续用于模块处理和符号解析。

preparescripts 都完成后,构建系统开始遍历 vmlinux-dirs 中的每一个目录。在每个目录中,它会调用 scripts/Makefile.build,根据该目录下的 KbuildMakefile 文件,编译所有列入 obj-y 的源文件,最后使用 $(LD) -r 命令将这些目标文件合并为该目录的 built-in.o。你会在终端看到大量 CC xxx.oLD built-in.o 的输出。

所有 built-in.o 生成后,最终链接的时刻到来。scripts/link-vmlinux.sh 脚本被调用,它将所有 built-in.o 和库文件链接在一起,生成 vmlinux 以及内核符号表 System.map。终端输出会显示:

LINK vmlinux
LD vmlinux.o
MODPOST vmlinux.o
...
LD vmlinux
SYSMAP System.map

此时,源码根目录下就出现了 vmlinuxSystem.map 文件。但这不是最终的启动镜像,我们还需要将其打包成引导程序能加载的格式。

构建可启动的bzImage镜像

bzImage(big zImage)是经过压缩的、可被引导加载器(如GRUB)直接加载的内核镜像。对于x86架构,执行 make(无参数)默认就会构建 bzImage,因为 arch/x86/Makefile 中定义了 all: bzImage

bzImage 目标依赖于 vmlinux,其核心命令是进入 arch/x86/boot 目录进行构建:

bzImage: vmlinux
    $(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)
    ...

这里 boot := arch/x86/boot。这个目录的构建分为两个主要部分:

  1. 构建实模式代码 setup.bin:这部分代码负责早期的硬件检测、模式切换等。它由 arch/x86/boot 目录下的汇编和C文件编译链接成 setup.elf,再通过 objcopy 生成纯二进制的 setup.bin。在编译过程中,它需要等待 voffset.hzoffset.h 两个头文件的生成,这两个文件包含了从 vmlinux 中提取出的关键地址信息。

  2. 构建保护模式内核 vmlinux.bin:这部分工作主要在 arch/x86/boot/compressed 目录下完成。它的输入是之前生成的 vmlinux。流程如下:

    • 使用 objcopyvmlinux 生成一个去除了调试信息的纯二进制文件 vmlinux.bin
    • vmlinux.bin 进行重定位处理,并压缩(通常是bzip2,生成 vmlinux.bin.bz2)。
    • 编译一个小工具 mkpiggy,该工具将压缩后的内核大小等信息生成一个汇编文件 piggy.S,并编译为 piggy.o
    • head_64.o, misc.o, piggy.o 等文件链接成 compressed/vmlinux。这个过程依赖于特定的链接脚本,并最终生成供 arch/x86/boot 使用的 zoffset.h

setup.bin 和压缩的 vmlinux.bin 都准备好后,arch/x86/boot 目录下的主机程序 tools/build 被调用。它的工作非常简单:将 setup.binvmlinux.bin 拼接在一起,并在头部添加一些校验信息和大小字段,最终输出为 bzImage

你会在终端看到熟悉的总结信息:

Setup is 16268 bytes (padded to 16384 bytes).
System is 4704 kB
CRC 94a88f9a
Kernel: arch/x86/boot/bzImage is ready (#5)

至此,一个完整、可启动的Linux内核镜像就构建完成了。

总结

从一句简单的 makebzImage 的诞生,Linux内核构建系统(Kbuild)完成了一场精密的协作。它通过顶层的 Makefile 协调全局,利用 scripts/ 目录下的众多工具处理依赖、链接和打包,并递归地深入每个子目录,遵循本地 Kbuild 文件的指示,将成千上万的源文件编译、汇集成一个个 built-in.o,最终组装成完整的内核。

这个过程完美体现了 操作系统 和大型系统软件构建的复杂性。理解它不仅能让你在自定义内核时更加得心应手,更能加深对软件工程中模块化、自动化构建的理解。希望这篇解析能为你打开Linux内核构建奥秘的大门。




上一篇:Go结构体设计最佳实践:从业务建模到代码落地的实战指南
下一篇:高盛AI展望解析:2026年变革加剧,电力成新增长天花板
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-2 23:28 , Processed in 0.284646 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表