第四章:Linux内核编译与裁剪——为ADAS域控量身定制

各位同学,今天我们来聊聊内核编译与裁剪。说实话,这可能是整个课程里最“硬核”的部分之一。我在ADAS域控项目里,至少亲手编译过上百次内核。为什么?因为车载系统对实时性、安全性和资源占用要求极高,一个“大而全”的通用内核,根本跑不动。

你想想看,ADAS域控的算力是有限的。摄像头数据、雷达点云、决策规划,哪个不吃资源?内核本身如果太臃肿,光调度开销就能吃掉10%的CPU。所以,裁剪不是锦上添花,是必须做的事。

4.1 内核源码获取:从哪开始?

获取源码,我建议直接从kernel.org拉取LTS版本。为什么?因为车载产品生命周期长,LTS版本有长期维护,安全补丁跟得上。

# 以5.10 LTS为例
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.210.tar.xz
tar -xf linux-5.10.210.tar.xz
cd linux-5.10.210

嗯,这里要注意:别用最新的主线版本。我在一个项目里吃过亏,用了当时最新的5.15,结果某个GPU驱动不兼容,调试了整整三天。后来老老实实切回5.10 LTS,问题全没了。

我的习惯: 每次拉源码后,先git init做个本地仓库。这样万一改乱了,git checkout -- . 就能恢复,不用重新解压。

4.2 内核配置(menuconfig):别怕,一步步来

配置内核,最直观的方式就是 make menuconfig。它基于ncurses,在终端里就能操作。我第一次用的时候,看着满屏的选项也懵。但后来发现,其实就三类东西:

  • Y:编译进内核(built-in)
  • M:编译成模块(module)
  • N:不编译

对于ADAS域控,我的原则是:能M不Y,能N不M。为什么?因为模块可以按需加载,减少内核镜像体积,也降低启动时间。

举个例子,摄像头驱动。如果你用Y编译进内核,那每次启动都会加载。但ADAS系统里,摄像头初始化是有时序的,可能需要在应用层控制加载时机。这时候用M就灵活多了。

# 进入配置界面
make menuconfig

# 常用快捷键
# / 搜索某个配置项
# ? 查看帮助
# Space 切换 Y/M/N

避坑指南: 我曾经在配置时,不小心把EXT4文件系统支持设成了M。结果根文件系统在EXT4分区上,内核启动时根本挂载不了,直接panic。后来我学乖了:根文件系统所在分区的驱动,必须设为Y。

4.3 内核编译与模块管理

配置好了,就该编译了。我一般用多核编译,省时间。

# 编译内核镜像
make -j$(nproc) Image

# 编译模块
make -j$(nproc) modules

# 安装模块到目标目录
make modules_install INSTALL_MOD_PATH=/path/to/rootfs

这里有个细节:make -j后面的数字,不是越大越好。我试过在8核机器上开16线程,结果内存爆了,编译到一半卡死。建议用 nproc 获取核心数,或者稍微减1。

编译完成后,你会得到几个关键文件:

文件 说明
arch/arm64/boot/Image 内核镜像,用于启动
arch/arm64/boot/dts/xxx.dtb 设备树文件,描述硬件
System.map 符号表,调试用
.config 当前配置,备份用

模块管理也很重要。我习惯把模块打包成一个压缩包,部署时直接解压到目标板。

# 打包模块
cd /path/to/rootfs/lib/modules/
tar -czf modules.tar.gz 5.10.210/

# 在目标板上解压
tar -xzf modules.tar.gz -C /lib/modules/
depmod -a
注意: 模块版本必须和内核版本严格一致。我见过有人把5.10的模块放到5.15的内核上,结果insmod时报错“Invalid module format”。别问我怎么知道的。

4.4 针对ADAS的内核裁剪:砍掉不需要的

好了,重头戏来了。ADAS域控需要什么?我来列个清单:

  • 必须保留: 实时抢占(PREEMPT_RT)、内存管理(CMA)、DMA-BUF、V4L2、GPU/ISP驱动、网络协议栈(用于CAN/Ethernet)
  • 可以砍掉: 蓝牙、Wi-Fi(除非需要)、USB Gadget、音频子系统(除非有语音交互)、大量文件系统(只留EXT4和F2FS就够了)
  • 必须砍掉: 所有非必要的硬件驱动(比如触摸屏、键盘、鼠标)、虚拟化支持(KVM)、大量网络协议(IPX、AppleTalk等)

我举个例子。有一次,我负责的域控项目,内核镜像从12MB裁剪到了4.8MB。怎么做到的?

  1. 关掉了所有调试选项(CONFIG_DEBUG_KERNEL=n)
  2. 只保留ARM64架构,砍掉ARM32支持
  3. 文件系统只留EXT4和F2FS
  4. 网络只留IPv4和IPv6,其他全关
  5. 驱动只保留I2C、SPI、GPIO、PCIe、GPU、ISP、以太网
# 查看当前内核大小
ls -lh arch/arm64/boot/Image

# 裁剪后对比
# 裁剪前:12M
# 裁剪后:4.8M

你想想看,少了7MB,对于启动时间来说,就是几百毫秒的差距。在ADAS里,这几百毫秒可能意味着能不能及时响应一个紧急刹车。

我的经验: 裁剪不是一次完成的。我通常先做一个最小配置,能启动系统。然后逐步添加需要的功能,每加一个就编译测试一次。这样出了问题,很容易定位是哪个配置项导致的。

最后,别忘了保存你的配置文件。我习惯把最终的.config重命名为 defconfig_adas,放到arch/arm64/configs/下。这样下次直接 make defconfig_adas 就能复用。

嗯,内核编译与裁剪,说白了就是“做减法”。去掉你不需要的,留下最核心的。对于ADAS域控来说,稳定、高效、安全,比什么都重要。下一章,我们会聊聊设备树和驱动开发,到时候你会更理解为什么裁剪这么关键。