4. 内核编译实战:make zImage与make modules、多线程编译加速、编译错误常见排查
配置完内核,下一步就是编译。这一步看着简单,但坑不少。我见过不少新手在编译阶段卡住,一卡就是半天。其实编译内核就两条主线:内核镜像和模块。搞明白这两条线,剩下的就是耐心和一点排查技巧。
核心思路:先编译内核镜像(zImage/Image),再编译模块(modules),最后安装模块到根文件系统。顺序不能乱,否则模块版本对不上。
4.1 编译内核镜像:make zImage vs make Image
不同架构,内核镜像的名字不一样。ARM 平台常用 zImage,x86 平台用 bzImage,RISC-V 用 Image。但编译命令都是 make 加目标名。
| 架构 | 镜像名 | 典型命令 |
|---|---|---|
| ARM / ARM64 | zImage / Image | make ARCH=arm zImage |
| x86 / x86_64 | bzImage | make bzImage |
| RISC-V | Image | make ARCH=riscv Image |
我个人习惯先跑一遍 make zImage,确认编译链没问题。为什么?因为镜像编译失败通常是最明显的——要么缺工具链,要么配置有语法错误。模块编译失败反而隐蔽,有时编译过了,装到板子上才报错。
小技巧:编译前先执行 make kernelrelease,确认你当前配置的版本号。我遇到过两次因为版本号没改,模块和镜像版本不一致导致 insmod 失败的情况。
4.2 编译模块:make modules 与模块安装
模块编译和镜像编译可以分开做。命令很简单:
# 编译所有模块
make modules
# 安装模块到指定目录
make modules_install INSTALL_MOD_PATH=/path/to/rootfs
这里有个关键点:INSTALL_MOD_PATH 指向你的根文件系统目录。模块会被安装到 /lib/modules/$(kernelrelease)/ 下。如果你不指定这个路径,默认装到本机的 /lib/modules/,那交叉编译就白干了。
我曾经在项目里犯过这个错——编译完模块直接 make modules_install,结果本机的内核模块被覆盖了,系统差点崩掉。嗯,从那以后我每次都会显式指定 INSTALL_MOD_PATH。
4.3 多线程编译加速:-j 参数的合理使用
内核编译很耗时。一个完整的内核,单线程编译可能要半小时甚至更久。多线程编译是必须的。
make -jN 中的 N 一般设为 CPU 核心数的 1.5 到 2 倍。比如 4 核 CPU,可以用 -j6 或 -j8。
# 4 核 CPU,推荐
make -j6 zImage
# 8 核 CPU,可以激进一点
make -j16 modules
注意:-j 参数不是越大越好。N 太大,内存会爆。我见过有人用 32 线程编译,结果 OOM(内存溢出)直接 killed。建议先试 nproc 命令看核心数,再乘以 1.5。
另外,第一次编译建议先用 -j1 跑一遍,确认没有语法错误。为什么?因为多线程编译时,错误信息会被冲乱,你很难定位是哪个文件出了问题。等第一次编译通过后,后续增量编译再用多线程加速。
4.4 编译错误常见排查
编译错误分三类:配置错误、工具链错误、源码错误。下面是我总结的排查清单。
| 错误类型 | 典型现象 | 排查方向 |
|---|---|---|
| 配置错误 | undefined reference to xxx | 检查 .config 中相关选项是否开启 |
| 工具链错误 | cc1: error: unrecognized command line option | 检查交叉编译器版本、ARCH 和 CROSS_COMPILE 变量 |
| 源码错误 | implicit declaration of function | 检查头文件路径、内核版本是否匹配 |
具体排查步骤:
- 看第一行错误:不要被几十行错误吓到。真正的根因通常在第一条。
- 确认 ARCH 和 CROSS_COMPILE:交叉编译时,这两个变量必须设置。我习惯写在
Makefile头部或export到环境变量。 - 检查 .config 依赖:有些选项依赖其他选项。比如你开了某个驱动,但它依赖的子系统没开,编译就会报 undefined reference。
- 清理重试:
make clean或make mrproper后重新配置编译。有时候残留的 .o 文件会干扰增量编译。
避坑指南:我曾经在移植一个 USB 网卡驱动时,编译报了一堆 undefined reference。查了半天,发现是内核配置里 USB 子系统没开。开了之后,编译一次通过。所以,遇到 undefined reference,先查依赖配置。
4.5 内核编译流程总览
下面这张图总结了从配置到镜像生成的完整流程。你可以把它贴在工位旁边,编译时对照着看。
4.6 实战:一次完整的编译过程
假设你已经在 linux-5.10 目录下,配置好了 .config。下面是一次完整的编译命令序列:
# 设置环境变量(也可以写在 Makefile 里)
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 第一步:编译内核镜像
make -j6 zImage
# 第二步:编译模块
make -j6 modules
# 第三步:安装模块到根文件系统
make modules_install INSTALL_MOD_PATH=../rootfs
# 第四步:查看生成的文件
ls arch/arm/boot/zImage
ls ../rootfs/lib/modules/
如果一切顺利,你会看到 zImage 和模块目录。如果报错,别慌。先看第一条错误,再对照上面的排查清单。
我的习惯:编译完成后,我会用 file arch/arm/boot/zImage 确认镜像格式。有时候编译器版本不对,生成的是 ELF 而不是原始二进制,烧进去起不来。
嗯,编译这一步其实不难,但细节决定成败。多线程加速能省时间,但别贪心。排查错误时,保持冷静,从第一条错误开始看。做到这些,编译阶段基本不会卡住你太久。