第4章:内核编译实战:make编译流程、多核并行编译、编译输出产物分析、常见编译错误排查

好,咱们直接进入正题。上一章我们把环境搭好了,配置也搞定了。这一章,就是真刀真枪地编译内核。

说实话,我第一次编译内核的时候,心里也没底。几千万行代码,编译一次少说十几分钟。万一错了,还得重来。但别怕,我把这些年踩过的坑、总结的经验,全给你抖出来。

4.1 make编译流程:到底发生了什么?

你执行 make 的时候,系统到底在干嘛?我简单梳理一下,其实就三步:

  1. 读取配置.config 文件被读入,决定哪些功能编译、哪些不编译。
  2. 生成依赖:内核用 Kbuild 系统自动分析每个源文件的头文件依赖。这一步很关键,不然你改了头文件,它不知道重新编译谁。
  3. 编译链接:把 C 代码变成 .o 文件,再链接成 vmlinux 和模块 .ko

嗯,这里要注意:make 默认是单线程的。你想想看,现在 CPU 都 8 核 16 核了,单线程编译那得多慢?

核心命令速查:

make          # 编译内核和模块(单线程)
make -jN      # N 核并行编译
make modules  # 只编译模块
make bzImage  # 只编译内核镜像

4.2 多核并行编译:让 CPU 跑满

我个人习惯,编译内核时一定用 -j 参数。怎么设?有个经验公式:

并行数 = CPU 物理核心数 × 1.5 到 2

比如你 4 核 8 线程的 CPU,设 -j6-j8 比较合适。设太大反而会因为上下文切换过多而变慢。

我的小技巧:

nproc 命令查看 CPU 核心数:

make -j$(nproc)   # 自动使用所有核心

我在项目中经常用这个,省得每次手动算。

但注意,并行编译有个坑——内存不够。我曾经在 4GB 内存的虚拟机上开 -j8,结果直接 OOM(内存溢出)了。每个编译线程大约需要 200-500MB 内存,你算算看。

警告:

如果编译过程中系统卡死或报 fork: Cannot allocate memory,说明并行数设太大了。减少 -j 数值,或者加内存。

4.3 编译输出产物分析:你得到了什么?

编译完成后,你会看到一堆文件。我刚开始学的时候,看着这些文件一头雾水。现在给你拆解清楚:

文件/目录 说明 我的备注
vmlinux 未压缩的内核镜像(带调试信息) 很大,几十 MB 到上百 MB
arch/xxx/boot/bzImage 压缩后的内核镜像(实际启动用) 我们最终要的就是这个
System.map 内核符号表(函数名→地址映射) 调试内核崩溃时必备
.config 编译配置(会被更新) 建议备份一份
modules_install 安装模块到 /lib/modules/ 别忘了这一步

这里我特别想强调 System.map。有一次我遇到内核 panic,屏幕上只显示一个地址 0xffffffff81234567。没有 System.map,你根本不知道这个地址对应哪个函数。有了它,用 addr2line 一查,立马定位到问题代码行。

4.4 常见编译错误排查

编译内核,不出错才奇怪。我总结了最常见的三类错误,以及我的排查方法:

4.4.1 头文件找不到

错误信息: fatal error: linux/xxx.h: No such file or directory

原因: 你配置里选了某个功能,但它依赖的头文件没包含。

排查方法:

  • 检查 .config 中对应的选项是否开启
  • make menuconfig 搜索这个头文件对应的功能
  • 我曾经遇到过,选了 CONFIG_DEBUG_FS 但没选 CONFIG_DEBUG_FS_ALLOW_ALL,结果头文件路径不对

4.4.2 链接错误:undefined reference

错误信息: undefined reference to 'xxx_function'

原因: 某个函数声明了,但没实现。或者实现被条件编译屏蔽了。

排查方法:

  • grep -r "xxx_function" 搜索源码,看它到底在哪
  • 检查 Makefile 中对应的 obj-yobj-m 是否包含该文件
  • 我踩过的一个坑:EXPORT_SYMBOL 宏只在模块编译时有效,如果函数被编译进内核,但模块想用,需要 EXPORT_SYMBOL_GPL

4.4.3 编译选项冲突

错误信息: error: #error "CONFIG_X not set, but CONFIG_Y requires it"

原因: 两个配置选项互相依赖,但你只开了一个。

排查方法:

  • make olddefconfig 自动修复依赖
  • 或者用 make menuconfig 手动查看依赖关系(按 / 搜索)
  • 说实话,这种错误最好解决,因为错误信息已经告诉你怎么改了

我的终极排查三板斧:

  1. 看错误信息的第一行:别被后面几十行吓到,第一行往往就是根因
  2. make V=1:显示完整编译命令,能看到具体哪个文件、哪行代码出错
  3. 清缓存重来make clean && make -j4,有时候残留的 .o 文件会捣乱

4.5 本章知识体系图

下面这张图,把整个编译流程串起来了。你照着这个思路走,基本不会迷路。

内核编译实战知识体系 1. 配置阶段 .config / menuconfig 2. 编译阶段 make -jN / Kbuild 3. 输出产物 vmlinux / bzImage 多核并行编译 • -jN:N为并行数 • 经验公式:核心数×1.5~2 • 注意内存不足问题 常见编译错误 • 头文件找不到 • 链接错误:undefined reference • 配置选项冲突 排查三板斧 ① 看错误信息第一行 ② 用 make V=1 显示完整命令 ③ make clean 清缓存重来 记住:编译内核不是一次就能成功的,耐心排查才是关键

好了,这一章的内容就到这。编译内核其实没那么神秘,多试几次,你就能摸清它的脾气。下一章我们聊聊怎么把编译好的内核装到板子上跑起来——那又是另一番天地了。