4、内核定制与编译:从配置到烧录的完整链路
内核定制与编译,说白了就是把 Linux 内核源码变成能在你目标板上跑起来的二进制文件。这个过程我做了不下上百次,每次换平台都得走一遍。今天咱们就把内核配置裁剪、设备树编写、镜像生成到烧录这条线彻底捋清楚。
4.1 内核配置裁剪——别让内核“虚胖”
嵌入式设备资源有限,Flash 可能就 64MB,RAM 才 128MB。如果直接把 PC 上的完整内核搬过去,启动都成问题。所以配置裁剪是第一步,也是最重要的一步。
4.1.1 配置的三种方式
| 方式 | 命令 | 适用场景 |
|---|---|---|
| 基于默认配置 | make defconfig |
快速开始,适合架构通用配置 |
| 基于现有配置 | make menuconfig |
交互式裁剪,最常用 |
| 基于厂商配置 | make xxx_defconfig |
厂商提供的基础配置 |
我个人习惯先用 make menuconfig 做第一轮裁剪。为什么?因为它有图形界面,能直观看到每个模块的依赖关系。你想想看,如果直接改 .config 文件,很容易漏掉依赖项,编译时一堆报错。
CONFIG_WLAN 整个关掉。我曾经接手一个项目,内核镜像 8MB,裁剪后只剩 2.3MB,启动时间从 12 秒降到 4 秒。
4.1.2 裁剪的核心原则
- 关掉不用的硬件驱动:声卡、显卡、网卡,板子上没有的就关掉。
- 关掉调试选项:
CONFIG_DEBUG_KERNEL、CONFIG_PRINTK在发布版里可以关掉。 - 关掉不用的文件系统:只用 ext4 就别编译 btrfs、xfs。
- 模块化 vs 内建:频繁改动的驱动做成模块,基础驱动内建。
4.2 设备树(DTS)编写——硬件描述的艺术
设备树是内核和硬件之间的“翻译官”。它告诉内核:CPU 是什么、内存多大、外设挂在哪、中断号是多少。没有设备树,内核就是个瞎子。
4.2.1 DTS 文件结构
一个典型的 DTS 文件长这样:
/dts-v1/;
#include "imx6ul.dtsi"
/ {
model = "MyBoard i.MX6UL";
compatible = "myboard,imx6ul", "fsl,imx6ul";
memory {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB RAM
};
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
};
};
这里有几个关键点:
dtsi是 SoC 级描述文件,一般由芯片厂商提供。model和compatible是板级标识,内核靠它匹配驱动。reg描述地址和大小,格式是<起始地址 长度>。
4.2.2 编写 DTS 的常见坑
我在项目中遇到过最头疼的问题,是中断号写错了。板子上的按键中断一直不触发,查了三天,最后发现 DTS 里的中断号比实际硬件少了一个。为什么会这样?因为有些 SoC 的中断号是从 0 开始编号,有些是从 1 开始,一定要看芯片手册确认。
reg、interrupts、clocks 这三个属性确认清楚。这三个最容易出错。
4.3 设备树编译——把文本变成二进制
DTS 是文本文件,内核不认识。需要编译成 DTB(Device Tree Blob)二进制文件。命令很简单:
# 编译单个 DTS
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 在内核源码树中编译
make dtbs
我个人习惯用 make dtbs,因为它会自动处理依赖关系。如果你改了某个 .dtsi 文件,它会把所有引用它的 DTS 都重新编译。
dtc -I dtb -O dts myboard.dtb 反编译回来,检查一下有没有丢失信息。有时候编译过程会报 warning,但 DTB 还是生成了,反编译能帮你确认内容是否正确。
4.4 内核镜像生成——从源码到二进制
配置和 DTS 都准备好了,接下来就是编译内核。嵌入式 Linux 常用的内核镜像格式有三种:
| 格式 | 生成命令 | 特点 |
|---|---|---|
| zImage | make zImage |
自解压,体积小,适合 ARM |
| uImage | make uImage |
带 U-Boot 头,用于 U-Boot 引导 |
| Image | make Image |
原始 ELF 格式,调试用 |
我一般用 make uImage LOADADDR=0x80008000。为什么?因为 U-Boot 是嵌入式最常用的 bootloader,它需要 uImage 格式才能识别。LOADADDR 是内核的加载地址,不同 SoC 不一样,一定要查手册。
4.4.1 编译命令示例
# 设置交叉编译工具链
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 配置内核
make myboard_defconfig
# 编译内核镜像
make uImage LOADADDR=0x80008000 -j4
# 编译设备树
make dtbs
# 编译内核模块
make modules
-j4 表示用 4 个线程并行编译。别设太大,否则容易把电脑搞死机。我一般设成 CPU 核心数减 1。
4.5 烧录——把镜像写进板子
镜像生成后,最后一步是烧录。烧录方式取决于你的板子支持什么。常见的有三种:
- SD 卡烧录:把镜像拷到 SD 卡,板子从 SD 卡启动。
- 网络烧录:通过 TFTP 或 NFS 加载镜像。
- JTAG 烧录:用调试器直接写 Flash。
我最常用的是 SD 卡烧录,因为简单。步骤大致如下:
- 把 SD 卡插到电脑,用
dd命令把 uImage 和 DTB 写到指定分区。 - 设置板子的启动拨码开关为 SD 卡启动。
- 上电,看串口输出。
# 烧录 uImage 到 SD 卡第一个分区
sudo dd if=arch/arm/boot/uImage of=/dev/sdb1 bs=1M
# 烧录 DTB
sudo dd if=arch/arm/boot/dts/myboard.dtb of=/dev/sdb2 bs=1M
4.6 本章知识结构图
下面这张图把内核定制与编译的完整流程串起来了,你可以对照着看:
嗯,到这里内核定制与编译的完整流程就讲完了。从配置裁剪到 DTS 编写,再到编译和烧录,每一步都有它的门道。你只要按这个流程走一遍,基本就能把内核跑起来。遇到问题别慌,先看串口日志,八成问题都能从日志里找到线索。