4. 内核编译实战:make命令详解、编译选项与多线程加速
内核配置完了,接下来就是编译。这一步看着简单,但坑不少。我见过不少新手在 make 这一步卡住,要么编出来的内核起不来,要么编译时间长得让人崩溃。今天咱们就把 make 的用法、编译目标、多线程加速这些事彻底讲透。
4.1 make 命令的基本用法
Linux 内核的编译系统基于 GNU Make。你想想看,几千个源文件,如果没有一套完善的构建系统,根本没法管理。内核的 Makefile 体系非常复杂,但咱们只需要掌握几个核心用法。
最基本的编译命令就一行:
make
不加任何参数时,make 会读取顶层 Makefile,然后根据 .config 中的配置,编译出默认目标。这个默认目标通常是 zImage 或 bzImage,取决于你的架构。
我个人习惯在第一次编译时,先执行 make help 看看当前架构支持哪些目标:
make help
输出会列出所有可用的编译目标,比如 zImage、uImage、modules、dtbs 等等。这个命令我几乎每次编译新内核都会跑一遍,确认一下目标平台支持什么。
make kernelversion 查看。这个命令不会触发编译,只是读取顶层 Makefile 中的版本号。
4.2 编译目标详解:zImage、uImage、modules
不同的编译目标对应不同的输出产物。咱们一个一个说。
| 编译目标 | 输出文件 | 适用场景 |
|---|---|---|
zImage |
arch/arm/boot/zImage |
ARM 平台压缩内核镜像,体积较小 |
bzImage |
arch/x86/boot/bzImage |
x86 平台大内核镜像,实际也是压缩的 |
uImage |
arch/arm/boot/uImage |
U-Boot 引导的 ARM 内核镜像 |
Image |
arch/arm/boot/Image |
未压缩的内核镜像,调试用 |
modules |
分散在各目录的 .ko 文件 |
编译内核模块 |
dtbs |
arch/arm/boot/dts/*.dtb |
编译设备树二进制文件 |
4.2.1 zImage 与 bzImage
zImage 和 bzImage 都是压缩后的内核镜像。名字里的 z 和 bz 其实跟压缩算法有关——zImage 用 gzip 压缩,bzImage 用 bzip2 压缩。不过现在内核默认都用 bzImage 了,因为它支持更大的内核。
在 x86 平台上,编译命令是:
make bzImage
在 ARM 平台上,通常用:
make zImage
4.2.2 uImage
uImage 是 U-Boot 专用的内核镜像格式。它在 zImage 前面加了一个 64 字节的 U-Boot 头部,包含镜像类型、加载地址、入口地址等信息。
编译 uImage 需要先安装 mkimage 工具:
sudo apt-get install u-boot-tools
make uImage
我曾经在一个项目里忘了装 mkimage,结果 make uImage 报错说找不到命令。折腾了半天才发现是工具链的问题。嗯,这种基础依赖最容易忽略。
4.2.3 modules 与 modules_install
内核模块的编译分两步:
make modules
make modules_install
make modules 编译所有配置为 M 的模块,生成 .ko 文件。make modules_install 把这些 .ko 文件安装到 /lib/modules/$(uname -r)/ 目录下。
make modules_install 会把模块安装到当前主机的 /lib/modules 下,而不是目标板的根文件系统。交叉编译时,建议手动指定安装路径:
make modules_install INSTALL_MOD_PATH=/path/to/rootfs
4.3 多线程编译与编译加速
内核编译是个耗时活。我第一次编译完整的内核,单线程跑了将近一个小时。后来学会了多线程编译,时间直接缩短到十几分钟。
4.3.1 -j 参数
-j 参数指定并行编译的线程数:
make -j4
这个 4 表示同时运行 4 个编译任务。一般建议设置为 CPU 核心数的 1.5 到 2 倍。比如 4 核 CPU,可以用 -j6 或 -j8。
我常用的命令是:
make -j$(nproc)
nproc 命令返回当前 CPU 的核心数。这样写的好处是,换到不同机器上编译时,不用手动改数字。
4.3.2 编译加速技巧
除了多线程,还有几个加速技巧值得掌握:
- 只编译修改的部分: 内核 Makefile 会检测文件的时间戳,只重新编译有变化的文件。所以第一次编译慢,后续增量编译会快很多。
- 使用 ccache: ccache 是一个编译器缓存工具。它会把编译结果缓存起来,下次编译同样的代码时直接命中缓存。
- 跳过无关目标: 如果你只需要内核镜像,不需要模块,可以只编译
zImage或bzImage,跳过modules。
ccache 的用法很简单:
sudo apt-get install ccache
export CC="ccache gcc"
make -j$(nproc)
或者直接修改内核 Makefile,把 CC 变量设成 ccache gcc。我个人习惯在 .bashrc 里加一行 export CC="ccache gcc",这样所有编译任务都能受益。
4.4 完整编译流程示例
下面是一个完整的编译流程,从配置到安装:
# 1. 配置内核
make menuconfig
# 2. 编译内核镜像
make -j$(nproc) zImage
# 3. 编译设备树
make -j$(nproc) dtbs
# 4. 编译模块
make -j$(nproc) modules
# 5. 安装模块到目标根文件系统
make modules_install INSTALL_MOD_PATH=/path/to/rootfs
# 6. 安装内核镜像(可选)
cp arch/arm/boot/zImage /path/to/tftp/
cp arch/arm/boot/dts/your-board.dtb /path/to/tftp/
这个流程我用了很多年,基本没出过问题。唯一要注意的是,make modules_install 的路径一定要写对,否则模块装错地方,启动时内核找不到驱动。
4.5 内核编译知识体系
下面这张图总结了内核编译的核心流程和关键目标:
4.6 常见问题与避坑指南
编译过程中我遇到过不少问题,挑几个典型的说说:
- 编译报错 "No rule to make target": 通常是
.config配置有问题,或者某个依赖的库没装。先检查.config是否完整,再确认工具链和依赖库。 - 编译到一半卡住: 可能是内存不足。多线程编译时,每个线程都会占用内存。如果内存不够,可以降低
-j的数值。 - uImage 编译失败: 确认是否安装了
u-boot-tools。我曾经在 Ubuntu 18.04 上遇到过mkimage版本不兼容的问题,升级工具包后解决。 - 模块安装后不生效: 检查
INSTALL_MOD_PATH是否正确,以及模块的.ko文件是否与内核版本匹配。
CROSS_COMPILE 变量:
make CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) zImage