第4章:内核编译实战:make编译流程、多核并行编译、编译输出产物分析、常见编译错误排查
好,咱们直接进入正题。上一章我们把环境搭好了,配置也搞定了。这一章,就是真刀真枪地编译内核。
说实话,我第一次编译内核的时候,心里也没底。几千万行代码,编译一次少说十几分钟。万一错了,还得重来。但别怕,我把这些年踩过的坑、总结的经验,全给你抖出来。
4.1 make编译流程:到底发生了什么?
你执行 make 的时候,系统到底在干嘛?我简单梳理一下,其实就三步:
- 读取配置:
.config文件被读入,决定哪些功能编译、哪些不编译。 - 生成依赖:内核用
Kbuild系统自动分析每个源文件的头文件依赖。这一步很关键,不然你改了头文件,它不知道重新编译谁。 - 编译链接:把 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-y或obj-m是否包含该文件 - 我踩过的一个坑:
EXPORT_SYMBOL宏只在模块编译时有效,如果函数被编译进内核,但模块想用,需要EXPORT_SYMBOL_GPL
4.4.3 编译选项冲突
错误信息: error: #error "CONFIG_X not set, but CONFIG_Y requires it"
原因: 两个配置选项互相依赖,但你只开了一个。
排查方法:
- 用
make olddefconfig自动修复依赖 - 或者用
make menuconfig手动查看依赖关系(按/搜索) - 说实话,这种错误最好解决,因为错误信息已经告诉你怎么改了
我的终极排查三板斧:
- 看错误信息的第一行:别被后面几十行吓到,第一行往往就是根因
- 用
make V=1:显示完整编译命令,能看到具体哪个文件、哪行代码出错 - 清缓存重来:
make clean && make -j4,有时候残留的 .o 文件会捣乱
4.5 本章知识体系图
下面这张图,把整个编译流程串起来了。你照着这个思路走,基本不会迷路。
好了,这一章的内容就到这。编译内核其实没那么神秘,多试几次,你就能摸清它的脾气。下一章我们聊聊怎么把编译好的内核装到板子上跑起来——那又是另一番天地了。