4. 内核编译与启动:从配置到跑起来
各位同学,今天我们来聊聊嵌入式Linux开发中最核心的一环——内核编译与启动。说实话,我见过太多工程师在这块栽跟头了。我自己刚入行那会儿,也曾经因为一个配置选项没选对,折腾了整整三天。
嗯,咱们今天就把这块彻底讲透。从内核配置开始,一直到U-Boot引导,最后看启动日志。你跟着我走一遍,以后遇到类似问题心里就有底了。
4.1 内核配置(menuconfig)—— 别怕,就是个图形界面
很多人第一次看到menuconfig,觉得这东西很神秘。其实说白了,它就是Linux内核的“开关面板”。你想让内核支持什么功能,就在这里勾选。
我个人习惯的做法是:先找一个接近的默认配置,然后在此基础上修改。比如我们做车规级芯片,一般会从 defconfig 开始。
# 以i.MX8为例
make ARCH=arm64 defconfig
make ARCH=arm64 menuconfig
进入menuconfig后,你会看到这样的界面:
- General setup — 通用设置,比如内核版本号、压缩方式
- Enable loadable module support — 模块支持,建议选上
- Device Drivers — 设备驱动,我们最常打交道的地方
- File systems — 文件系统支持
这里有个避坑指南:我曾经在某个项目中,因为没选 CONFIG_EXT4_FS,导致根文件系统挂载失败。你想想看,内核都跑起来了,结果找不到文件系统,多尴尬。
.config 文件备份一下。我习惯命名为 config_$(date +%Y%m%d),方便回退。
4.2 编译内核与设备树 —— 两条腿走路
配置好了,接下来就是编译。这里我要强调一点:内核镜像和设备树是分开编译的。很多人以为只编一个 zImage 就完事了,其实不对。
# 编译内核
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j4
# 编译设备树
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs
编译完成后,你会得到两个关键文件:
| 文件 | 路径 | 说明 |
|---|---|---|
| Image / zImage | arch/arm64/boot/ | 内核镜像本体 |
| .dtb 文件 | arch/arm64/boot/dts/ | 设备树二进制文件 |
我记得有一次,客户反馈说网卡不工作。我查了半天,最后发现是设备树里GPIO的pinmux配置错了。嗯,设备树这东西,错一个引脚,整个外设就废了。
dts 源文件里如果引用了 dtsi 头文件,一定要确保路径正确。我见过有人把 #include 写成了 #include "../soc.dtsi",结果编译出来的dtb完全不对。
4.3 U-Boot引导流程分析 —— 内核是怎么被叫醒的
U-Boot,说白了就是内核的“闹钟”。它负责把内核从Flash里读到内存,然后跳转过去执行。整个过程分几步:
- 初始化硬件 — 时钟、DDR、串口等
- 加载内核和设备树 — 从存储介质读到内存
- 设置启动参数 — 比如
bootargs - 跳转到内核入口 — 把控制权交给内核
这里我贴一段典型的U-Boot启动脚本:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
load mmc 1:1 0x80000000 Image
load mmc 1:1 0x83000000 imx8mp-evk.dtb
booti 0x80000000 - 0x83000000
你可能会问:为什么内核地址是 0x80000000?这个地址其实是芯片厂商规定的。对于i.MX8MP来说,DDR的起始地址就是 0x80000000。嗯,这个你得看芯片手册。
关键点: U-Boot的 booti 命令用于启动ARM64内核。如果是32位ARM,用 bootz 或 bootm。别搞混了,我见过有人用 bootz 启动64位内核,结果卡死在启动阶段。
4.4 内核启动日志解读 —— 学会看“天书”
内核启动日志,很多人觉得密密麻麻看不懂。其实它是有规律的。我一般按时间线来读:
- 早期启动 — 打印内核版本、编译信息
- CPU和内存初始化 — 检测CPU核数、内存大小
- 设备驱动加载 — 每个驱动probe时打印的信息
- 文件系统挂载 — 最后一步,挂载根文件系统
举个例子,这是我从实际项目中截取的一段日志:
[ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd034]
[ 0.000000] Linux version 5.15.32 (user@host) (gcc 9.3.0) #1 SMP PREEMPT
[ 0.000000] Memory: 2048MB available
[ 0.500000] mmc1: new high speed SD card at address 0x1234
[ 0.800000] EXT4-fs (mmcblk1p2): mounted filesystem with ordered data mode
看到 [ 0.000000] 了吗?这是内核启动后的时间戳,单位是秒。如果某个驱动卡住了,你看时间戳就能定位到问题。
我曾经遇到一个案例:日志停在 [ 1.200000] 就不动了。后来发现是某个I2C设备在probe时没响应,导致内核超时等待。嗯,这种问题,看日志比猜快多了。
earlyprintk 参数。这样在串口驱动还没初始化之前,就能看到早期打印信息。我靠这招救过好几次急。
4.5 常见问题与排查思路
最后,我总结几个我实际工作中遇到的高频问题:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 内核启动后无控制台 | console参数错误或串口驱动未编译 | 检查bootargs中的console=ttyXXX |
| 挂载根文件系统失败 | 文件系统类型不支持或设备节点错误 | 确认CONFIG_EXT4_FS等选项已开启 |
| 设备树加载后外设不工作 | 设备树中地址或中断号配置错误 | 对比芯片手册,检查reg和interrupts属性 |
好了,这一章的内容就到这里。内核编译与启动这块,说白了就是“配置-编译-引导-调试”四个步骤。你多练几次,自然就熟了。下一章我们聊聊设备树的具体写法,到时候我会拿一个真实的车规级外设来举例。