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 检查头文件路径、内核版本是否匹配

具体排查步骤:

  1. 看第一行错误:不要被几十行错误吓到。真正的根因通常在第一条。
  2. 确认 ARCH 和 CROSS_COMPILE:交叉编译时,这两个变量必须设置。我习惯写在 Makefile 头部或 export 到环境变量。
  3. 检查 .config 依赖:有些选项依赖其他选项。比如你开了某个驱动,但它依赖的子系统没开,编译就会报 undefined reference。
  4. 清理重试:make clean 或 make mrproper 后重新配置编译。有时候残留的 .o 文件会干扰增量编译。

避坑指南:我曾经在移植一个 USB 网卡驱动时,编译报了一堆 undefined reference。查了半天,发现是内核配置里 USB 子系统没开。开了之后,编译一次通过。所以,遇到 undefined reference,先查依赖配置。

4.5 内核编译流程总览

下面这张图总结了从配置到镜像生成的完整流程。你可以把它贴在工位旁边,编译时对照着看。

内核编译流程总览 1. 配置内核 make menuconfig / defconfig 2. 编译内核镜像 make zImage / bzImage 3. 编译模块 make modules 4. 安装模块 make modules_install 5. 部署到目标板 ⚠️ 常见错误点 • ARCH / CROSS_COMPILE 未设置 • .config 依赖缺失 • 模块版本与镜像不匹配 💡 加速建议 • 首次编译用 -j1 • 后续用 -jN (N=核数×1.5) • 增量编译用 make

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 而不是原始二进制,烧进去起不来。

嗯,编译这一步其实不难,但细节决定成败。多线程加速能省时间,但别贪心。排查错误时,保持冷静,从第一条错误开始看。做到这些,编译阶段基本不会卡住你太久。