3. 内核编译初体验:配置单板默认defconfig、编译zImage与dtb、编译内核模块、编译输出产物分析、烧录与启动验证
说实话,第一次接触内核编译的人,很容易被那一长串命令吓到。我当年也是这样,对着终端愣了半天,生怕敲错一个字母把电脑搞炸了。其实内核编译没那么玄乎,说白了就是一套固定的流程:选配置、编译、拿产物、烧进去跑。今天我们就拿全志T113这颗芯片,把这条路走一遍。
本章核心目标:从零开始,完成一次完整的内核编译与烧录验证。你不需要理解每一行代码,但要知道每一步在干什么。
3.1 配置单板默认defconfig
内核源码树里,每个芯片厂商都会提供一套默认配置。全志T113的默认配置藏在 arch/arm/configs/ 目录下。我习惯先看一眼这个目录里有哪些文件:
ls arch/arm/configs/ | grep t113
你会看到类似 sun8iw20p1_defconfig 这样的文件。这就是T113的默认配置。为什么叫这个名?sun8i是全志芯片的代号,w20p1是具体的型号版本。嗯,这里要注意,不同开发板可能对应不同的defconfig,别搞混了。
加载默认配置的命令很简单:
make ARCH=arm sun8iw20p1_defconfig
执行完之后,内核会自动生成一个 .config 文件。这个文件就是后续编译的依据。我曾经遇到过一个问题:加载完defconfig之后,直接编译发现网卡驱动没编进去。后来排查发现,默认配置里把某些模块关掉了。所以我的习惯是,加载完defconfig之后,先看一眼 .config 里关键选项的状态:
grep -E "SPI|I2C|SUNXI" .config
小技巧:如果你不确定某个驱动是否被选中,可以用 make menuconfig 图形化界面去查。按 / 键搜索驱动名,它会告诉你这个选项依赖什么、被谁选中。
3.2 编译zImage与dtb
配置搞定之后,就是编译环节。T113是ARM架构,所以我们要编译的是 zImage(压缩后的内核镜像)和 dtb(设备树二进制文件)。
我个人习惯把编译命令拆开执行,这样出了问题好定位:
# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage -j4
# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
-j4 表示用4个线程并行编译。你机器核多的话可以调大,比如 -j8。我第一次编译的时候用了 -j1,等了快半小时才完。后来发现多线程编译能快好几倍,但要注意,如果编译过程中报错,多线程输出的错误信息可能会乱序,不太好定位。所以第一次编译我建议先用 -j1 跑一遍,确认没问题了再开多线程。
编译完成后,产物在以下位置:
| 产物 | 路径 | 说明 |
|---|---|---|
| zImage | arch/arm/boot/zImage |
压缩后的内核镜像,烧录时用这个 |
| dtb | arch/arm/boot/dts/sun8iw20p1-*.dtb |
设备树文件,描述硬件资源 |
注意:设备树文件名要和你的板子匹配。全志T113的评估板通常对应 sun8iw20p1-evb.dtb。如果你用的是第三方板子,记得确认dtb文件名。
3.3 编译内核模块
内核里有些驱动是以模块(.ko)形式存在的。这些模块不会编进zImage,而是单独编译成文件,系统启动后再动态加载。编译模块的命令:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j4
编译完成后,模块文件散布在整个内核源码树里。为了方便后续安装,我们需要把它们统一安装到一个目录:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=./output modules_install
这条命令会把所有.ko文件复制到 ./output/lib/modules/ 下,并按内核版本分好目录。我建议你把这个output目录打包带走,烧录系统的时候直接解压到根文件系统里就行。
为什么要单独编译模块?我在项目里遇到过这种情况:某个SPI设备的驱动在默认配置里是模块形式,如果我不编模块,这个设备就没法用。所以每次编译完内核,我都会顺手把模块也编了,省得后面再补。
3.4 编译输出产物分析
编译完了,我们来看看都生成了些什么东西。除了前面说的zImage、dtb和模块,还有一些文件值得关注:
- vmlinux:未压缩的内核镜像,带调试信息,一般不用来烧录,但可以用来分析内核符号。
- System.map:内核符号表,调试内核崩溃时很有用。
- .config:最终的编译配置,建议备份一份,方便后续复现。
我习惯用 file 命令确认一下产物类型:
file arch/arm/boot/zImage
# 输出:zImage: Linux kernel ARM boot executable zImage (little-endian)
看到这个输出,基本就稳了。如果显示 data 或者 unknown,那说明编译可能出了问题。
经验之谈:我曾经有一次编译出来的zImage只有几百字节,烧进去板子死活起不来。后来发现是交叉编译器没装对,编译出来的其实是空壳。所以每次编译完,看一眼文件大小是个好习惯。正常的zImage一般在3-5MB左右。
3.5 烧录与启动验证
产物到手,接下来就是烧录验证。全志T113的烧录方式有很多种,这里我介绍最常用的一种——通过TF卡启动。
首先,准备一张TF卡,分成两个区:
- 第一个区:FAT32格式,放zImage和dtb
- 第二个区:ext4格式,放根文件系统
然后把编译好的文件拷进去:
# 假设TF卡第一个分区挂载在 /media/boot
cp arch/arm/boot/zImage /media/boot/
cp arch/arm/boot/dts/sun8iw20p1-evb.dtb /media/boot/
# 假设第二个分区挂载在 /media/rootfs
sudo tar -xzf rootfs.tar.gz -C /media/rootfs/
sudo cp -r output/lib/modules /media/rootfs/lib/
拔卡,插到T113板子上,上电。如果一切正常,你会看到串口输出类似这样的信息:
Starting kernel ...
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 5.4.61 ...
[ 1.234567] sunxi-spi sunxi-spi.0: SPI controller registered
...
Welcome to Buildroot login:
看到登录提示符,说明内核已经跑起来了。你可以输入 lsmod 查看已加载的模块,或者 cat /proc/cpuinfo 确认CPU信息。
调试建议:如果板子起不来,先检查串口有没有输出。完全没有输出,大概率是zImage或dtb没烧对。有输出但卡在某个地方,可以用 earlyprintk 内核参数打开早期调试信息,定位具体卡在哪一步。
3.6 本章知识结构图
下面这张图把整个编译流程串起来了,你可以对照着看,每一步的输入输出是什么:
这张图从左到右展示了从源码到最终验证的完整链路。你可以把它当成一个检查清单,每完成一步就打个勾。
3.7 常见问题与避坑
最后,分享几个我在项目中踩过的坑:
- 交叉编译器版本不对:全志T113是ARM Cortex-A7内核,必须用
arm-linux-gnueabihf系列编译器。用错了编译出来的内核跑不起来。 - 设备树和内核版本不匹配:不同版本的内核,设备树绑定的格式可能有变化。我建议内核源码和设备树源码保持同一套代码,不要混搭。
- 烧录后没反应:先检查TF卡的分区表是不是MBR格式,全志的启动ROM只认MBR,不认GPT。
重要提醒:第一次烧录前,务必确认你的板子供电正常。T113的启动电流大概在200mA左右,如果电源不够,板子可能会反复重启,让你误以为是内核的问题。
好了,这一章的内容就到这里。你现在应该能独立完成一次完整的内核编译和烧录验证了。记住,内核编译不是一次性的工作,后面每次修改驱动或配置,都要重复这个流程。熟练之后,你会发现整个过程也就几分钟的事。