第四章 Linux内核移植基础:内核源码获取、Kconfig与设备树、内核配置裁剪

各位同学,欢迎来到内核移植实战的第一站。说实话,很多做ADAS的工程师一听到「移植内核」就觉得头大,觉得那是BSP团队的事。但我在做域控制器项目时发现,不懂内核裁剪,你连Flash都塞不下;不懂设备树,外设死活点不亮。今天我们就来把这层窗户纸捅破。

4.1 内核源码获取:从哪来,怎么选

先说说源码的事。我习惯直接从kernel.org拉主线代码,但做ADAS产品可不能这么干。为什么?因为车规级芯片的BSP,通常由芯片厂商提供定制分支。比如NXP的S32G系列,或者TI的TDA4系列,它们都有对应的官方内核仓库。

我的建议: 优先使用芯片厂商提供的LTS(长期支持)内核。比如NXP的Linux BSP,通常基于5.10或5.15 LTS。别追新,稳定第一。

获取方式无非三种:

  • git clone:最常用,适合持续跟踪更新
  • tar包下载:适合固定版本,比如量产锁定
  • 厂商SDK:比如TI的Processor SDK,里面直接打包好了

嗯,这里要注意:拿到源码后第一件事,不是急着编译,而是先看READMEDocumentation/process/changes.rst。我吃过亏——有一次直接make,结果报了一堆工具链版本错误,折腾半天才发现是gcc太新了。

4.2 Kconfig:内核的「菜单系统」

Kconfig是什么?说白了就是内核的配置系统。你运行make menuconfig时看到的那个图形界面,背后就是Kconfig在驱动。

每个驱动目录下都有一个Kconfig文件,里面定义了配置项。比如:

config SENSOR_OV10640
    tristate "OmniVision OV10640 camera sensor support"
    depends on I2C
    help
      This driver supports OV10640 camera sensor used in ADAS systems.

这里tristate表示三种状态:编译进内核(y)、编译成模块(m)、不编译(n)。我在项目中遇到过,很多人把depends on写错,导致配置项死活不显示。你想想看,依赖链断了,菜单项就消失了,多坑。

小技巧:make menuconfig时,按/键可以搜索配置项。比如搜OV10640,直接定位到位置,省得一层层翻菜单。

4.3 设备树:硬件描述的「语言」

设备树(Device Tree)是ARM架构下替代ACPI的东西。它用文本文件描述硬件,内核启动时解析它来知道「我有哪些外设、它们怎么连接」。

一个典型的设备树节点长这样:

i2c@48000000 {
    compatible = "ti,omap4-i2c";
    reg = <0x48000000 0x100>;
    interrupts = <56>;
    clock-frequency = <400000>;

    camera_sensor: sensor@30 {
        compatible = "ovti,ov10640";
        reg = <0x30>;
        reset-gpios = <&gpio3 15 GPIO_ACTIVE_LOW>;
    };
};

这里compatible是匹配驱动的关键。内核驱动通过of_match_table来匹配这个字符串。我曾经调试过一个摄像头驱动,死活不加载,最后发现是compatible字符串写错了一个字母——ovti写成了ovtI。这种坑,你查一天都不一定找得到。

注意: 设备树编译用dtc工具,生成.dtb文件。千万别直接修改.dtb,那是二进制文件。改.dts源文件,然后重新编译。

4.4 内核配置裁剪:给ADAS域控制器「瘦身」

ADAS域控制器的Flash通常不大,比如256MB或512MB。而标准内核动辄几十MB,不裁剪根本放不下。裁剪的核心思路就一句话:用不到的,统统关掉

我一般按这个步骤来:

  1. 先做最小配置:只保留CPU架构、内存管理、中断控制器、定时器等基础项
  2. 加外设驱动:根据硬件原理图,逐个使能I2C、SPI、CAN、以太网、摄像头接口等
  3. 关掉调试选项:比如CONFIG_DEBUG_KERNELCONFIG_PRINTK(量产时关掉)
  4. 文件系统支持:只保留你用的文件系统,比如ext4或squashfs

举个例子,一个典型的ADAS域控制器配置裁剪前后对比:

配置项 裁剪前 裁剪后
内核镜像大小 28MB 8.5MB
驱动数量 1200+ 180+
启动时间 8秒 2.3秒

你看,裁剪的效果非常明显。但要注意,别裁过头了。我曾经把CONFIG_HIGHMEM关掉,结果系统只能识别不到1GB内存,而我们的域控制器有4GB——嗯,那次排查花了我整整一个下午。

核心原则: 裁剪不是越少越好,而是「刚好够用」。保留必要的调试接口,比如串口打印,否则出了问题你连日志都看不到。

4.5 实战:从零开始配置一个ADAS内核

好了,理论讲完了,我们动手走一遍。假设你拿到了一个基于TI TDA4VM的域控制器板子:

# 1. 设置环境变量
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

# 2. 加载厂商默认配置
make defconfig
# 或者用厂商提供的配置
make tda4vm_evm_defconfig

# 3. 进入菜单配置
make menuconfig

# 4. 裁剪步骤(举例)
# 关闭蓝牙、WiFi、USB HID等不用的驱动
# 使能CAN、I2C、CSI摄像头、以太网AVB等

# 5. 编译内核
make -j8 Image dtbs

# 6. 查看生成的文件
ls arch/arm64/boot/Image
ls arch/arm64/boot/dts/ti/k3-tda4vm.dtb

这里有个坑:make defconfig生成的是通用配置,很多外设没开。我建议先找厂商要一份defconfig文件,在此基础上改。比如TI的SDK里就有现成的tda4vm_evm_defconfig,直接拿来用,省事不少。

保存配置: 改完后用make savedefconfig,会生成一个精简的defconfig文件。下次移植到新板子时,直接加载这个文件,再微调即可。

4.6 避坑指南:我踩过的那些雷

最后,分享几个我亲身经历过的坑,希望能帮你少走弯路:

  • 设备树地址冲突:有一次两个外设用了相同的内存地址,内核启动时直接panic。排查方法:看/proc/iomem,检查地址范围是否重叠。
  • 中断号不对:设备树里写的中断号,和芯片手册对不上。我建议对照手册逐个核对,别偷懒。
  • 时钟频率设置错误:I2C时钟设太快,传感器不响应。设太慢,带宽不够。这个只能试,没有捷径。
  • 裁剪后功能异常:比如关了CONFIG_PREEMPT,导致实时性变差。ADAS对延迟敏感,这个选项一般要保留。

嗯,今天就先讲到这里。下一章我们会深入设备树的具体编写,包括GPIO、中断、时钟的绑定规则,以及如何调试设备树。到时候我会拿一个真实的摄像头驱动来拆解,保证干货满满。