3. 内核编译与启动流程:交叉编译内核、生成zImage/Image与dtb、QEMU启动内核、内核启动log分析
这一章,我们直接动手。把源码变成能跑的内核,再用QEMU把它点起来。整个过程我走了不下百遍,每次都能发现新坑。今天我把这些经验拆开揉碎,你跟着走一遍,以后自己搞板子心里就有底了。
3.1 交叉编译环境准备
交叉编译,说白了就是在一台电脑上编译出另一台电脑能跑的程序。我们用的是x86的PC,目标却是ARM开发板。这个“翻译官”就是交叉编译工具链。
设置环境变量,我建议你这样写:
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
export PATH=$PATH:/opt/gcc-arm-9.2-2019.12-x86_64-arm-linux-gnueabihf/bin
注意那个CROSS_COMPILE变量,后面带个短横线。少了它,编译会报找不到命令。我曾经在这个细节上浪费过半小时,后来每次配环境都先echo $CROSS_COMPILE确认一下。
3.2 内核配置与编译
配置内核,我推荐两步走:先用默认配置打底,再用menuconfig微调。
make vexpress_defconfig
make menuconfig
vexpress_defconfig是ARM Versatile Express开发板的默认配置。QEMU模拟的就是这个板子,所以用它最省事。你想想看,如果选错了配置,编译出来的内核可能连串口都打不开,那启动log都看不到。
编译命令很简单:
make -j4 zImage
make -j4 dtbs
-j4表示用4个线程并行编译。我建议别超过CPU核心数的两倍,否则编译反而变慢。
arch/arm/boot/zImage— 压缩后的内核镜像,QEMU直接加载这个arch/arm/boot/dts/vexpress-v2p-ca9.dtb— 设备树二进制文件,描述硬件信息
zImage和Image有什么区别?Image是未压缩的原始内核,zImage是自解压版本。QEMU启动时,zImage会自己解压然后跳转到入口。我一般只用zImage,省空间。
3.3 QEMU启动内核
编译好了,怎么验证?用QEMU模拟。这是我最喜欢的环节,不用烧写、不用接串口线,一条命令就能看到内核跑起来。
qemu-system-arm -M vexpress-a9 \
-kernel arch/arm/boot/zImage \
-dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-nographic \
-append "console=ttyAMA0,115200 earlycon"
参数说明:
| 参数 | 含义 |
|---|---|
-M vexpress-a9 |
指定模拟的机器类型,必须与dtb匹配 |
-kernel |
指定zImage路径 |
-dtb |
指定设备树文件 |
-nographic |
不使用图形界面,串口输出到终端 |
-append |
传递内核启动参数 |
console=ttyAMA0指定了串口设备。不同板子的串口名不一样,vexpress用的是ttyAMA0,有些板子用ttyS0。搞错了,启动log就看不到。
执行后,你会看到内核开始滚动输出。如果一切正常,最后会停在VFS: Cannot open root device。别慌,这是正常的,因为我们没挂文件系统。能看到这一行,说明内核已经成功启动了。
3.4 内核启动流程与log分析
内核启动,我把它分成四个阶段。每个阶段都有对应的log输出,看懂这些log,你就能定位大部分启动问题。
3.5 earlycon与printk调试
启动参数里我加了earlycon,这个参数很关键。它让内核在早期初始化阶段就能输出log,比正常的console要早得多。
为什么会需要earlycon?因为内核在初始化串口驱动之前,console设备还没注册。如果这时候出问题,你什么都看不到。earlycon绕过驱动,直接操作串口寄存器,把log吐出来。
earlycon。有一次客户说他们的板子启动到一半就黑了,我让他们加上这个参数,log显示是GPIO驱动初始化时访问了非法地址。没有earlycon,这种问题根本没法查。
printk的日志级别也值得一说。内核用printk(KERN_ERR "...")这样的方式输出,级别从高到低:
| 宏 | 级别 | 含义 |
|---|---|---|
| KERN_EMERG | 0 | 紧急情况,系统不可用 |
| KERN_ALERT | 1 | 需要立即处理 |
| KERN_CRIT | 2 | 严重错误 |
| KERN_ERR | 3 | 错误 |
| KERN_WARNING | 4 | 警告 |
| KERN_NOTICE | 5 | 正常但重要 |
| KERN_INFO | 6 | 信息 |
| KERN_DEBUG | 7 | 调试信息 |
默认情况下,控制台只显示级别<=4(KERN_WARNING及以上)的log。如果你想看到更多,可以在启动参数里加loglevel=8,或者直接ignore_loglevel。我调试驱动时经常用后者,虽然log会刷屏,但不会漏掉任何线索。
3.6 常见问题与避坑指南
- 编译报错找不到头文件 — 检查
CROSS_COMPILE变量是否设置正确,路径里有没有短横线 - QEMU启动后黑屏 — 确认
-M参数与dtb匹配,vexpress-a9必须用vexpress-v2p-ca9.dtb - 看不到启动log — 检查
console=参数,vexpress用ttyAMA0,其他板子可能不同 - 内核卡在"Starting kernel ..." — 很可能是dtb有问题,或者内核配置里没开启对应机器的支持
make dtbs,结果用的还是旧版dtb。QEMU启动后内核直接崩溃,log显示"unable to handle kernel paging request"。折腾了半天才发现是dtb没更新。从那以后,我每次编译都写个脚本,把make zImage && make dtbs一起执行。
嗯,这一章的内容就到这里。你跟着走一遍,把zImage和dtb编译出来,再用QEMU跑起来,看看log。下次遇到启动问题,你就知道从哪里下手了。