4、内核配置基础:Kconfig语法、menuconfig使用、defconfig与dts配置

内核配置,说白了就是告诉编译器:哪些功能要编译进内核,哪些编译成模块,哪些干脆不要。我刚开始接触RK3568时,觉得这步就是跑个make menuconfig随便选选。后来发现,配置不对,驱动加载不起来,外设不工作,连系统都起不来。嗯,这里面的门道不少。

这一章,我们就把内核配置的四个核心东西讲透:Kconfig语法、menuconfig操作、defconfig管理、dts配置。它们之间的关系,我画了张图,你先有个整体印象。

内核配置知识体系 内核配置体系 Kconfig 语法 menuconfig 使用 defconfig 管理 dts 设备树配置 核心逻辑: Kconfig 定义菜单项 → menuconfig 提供交互界面 → 生成 .config → defconfig 保存默认配置 → dts 描述硬件拓扑 四者配合,才能让内核知道「跑在什么硬件上、要带哪些功能」。

4.1 Kconfig语法:菜单项是怎么定义的

Kconfig文件是内核配置菜单的「源代码」。每个内核子目录下都有一个Kconfig,定义了该目录下的配置选项。我最早看Kconfig文件时,觉得它像一门小语言,其实规则很简单。

一个典型的配置项长这样:

config CONFIG_ROCKCHIP_I2S
    tristate "Rockchip I2S controller driver"
    depends on ARCH_ROCKCHIP
    select SND_SOC_GENERIC_DMAENGINE_PCM
    help
      Say Y here to include support for Rockchip I2S controller.
      If unsure, say N.

几个关键字段:

  • config:定义配置项名称,最终会变成CONFIG_ROCKCHIP_I2S。
  • tristate:三态选项——Y(编译进内核)、M(编译成模块)、N(不编译)。驱动类常用tristate。
  • bool:布尔选项,只有Y/N,没有M。功能开关类常用bool。
  • depends on:依赖关系。比如这个驱动依赖ARCH_ROCKCHIP平台。
  • select:反向依赖。选中这个选项时,自动选中另一个选项。我习惯把select理解为「我帮你选上」。
  • help:帮助信息,menuconfig里按?能看到。
我的习惯:写Kconfig时,depends on和select别乱用。depends on多了,菜单会灰一大片;select多了,可能引入循环依赖。我一般只在驱动依赖某个核心框架时才用select。

4.2 menuconfig使用:图形化配置实战

执行make menuconfig,你会看到一个蓝底菜单界面。操作很简单:方向键移动,回车进入子菜单,空格切换Y/M/N,?查看帮助,/搜索。

我重点说几个实用技巧:

  • 搜索功能(/):想找某个配置项,直接按/输入关键词。比如搜I2S,会列出所有包含I2S的选项,还能看到依赖关系和当前状态。这个功能我几乎天天用。
  • 快速定位:按/搜到后,按数字键直接跳转到该选项位置,不用一层层翻菜单。
  • 保存与加载:退出时选Save,默认保存为.config。也可以加载已有的配置文件。
注意:menuconfig里改完配置后,一定要保存退出。直接关终端或按Ctrl+C,配置不会生效。我曾经有一次改了半天,忘了保存,重新编译时发现还是旧配置……白忙活。

4.3 defconfig管理:默认配置的保存与使用

每次手动配一遍太麻烦。所以内核引入了defconfig机制——把常用配置保存成一个文件,下次直接加载。

RK3568的默认配置文件在arch/arm64/configs/下,叫rockchip_linux_defconfig。使用方式:

# 加载默认配置
make ARCH=arm64 rockchip_linux_defconfig

# 基于当前 .config 生成新的 defconfig
make ARCH=arm64 savedefconfig

# 生成的 defconfig 会保存在当前目录,手动复制到 arch/arm64/configs/ 下

savedefconfig这个命令很聪明。它会把.config里非默认值的选项提取出来,生成一个精简的defconfig。我每次调完配置,都会跑一下savedefconfig,把改动保存下来。

命令 作用 常用场景
make defconfig 加载架构默认配置 首次配置
make xxx_defconfig 加载指定板级配置 切换开发板
make savedefconfig 生成精简默认配置 保存自定义配置
make olddefconfig 用默认值更新旧配置 内核版本升级后

避坑指南:我曾经在RK3568上调试一个音频驱动,改了menuconfig后直接编译,没保存defconfig。后来同事拉我的代码,编译出来音频就是坏的。从那以后,我每次改完配置,一定执行make savedefconfig,然后把生成的defconfig提交到git里。

4.4 dts配置:设备树描述硬件

Kconfig和defconfig管的是「软件功能开不开」,而dts(设备树)管的是「硬件长什么样」。RK3568的设备树文件在arch/arm64/boot/dts/rockchip/下。

一个典型的dts节点:

&i2s1 {
    status = "okay";
    rockchip,clk-trcm = <1>;
    pinctrl-names = "default";
    pinctrl-0 = <&i2s1_lrck &i2s1_sclk &i2s1_sdi &i2s1_sdo>;
};

关键点:

  • &i2s1:引用I2S1节点,在.dtsi文件里定义。
  • status = "okay":使能该外设。默认可能是disabled。
  • pinctrl:引脚复用配置,指定用哪些GPIO引脚。
  • clock/regulator:时钟和电源配置,驱动依赖这些资源。

dts和内核配置的关系:dts里的节点,必须有对应的内核驱动配置才能工作。比如你在dts里使能了I2S1,但内核没配CONFIG_ROCKCHIP_I2S,驱动根本不会编译进去,设备树配得再好也没用。

我的经验:调试新硬件时,我一般先确认dts节点是否使能(status = "okay"),再确认内核配置是否打开。两个都对了,驱动还不起,才去看代码逻辑。这个排查顺序帮我省了不少时间。

4.5 四者配合:一个完整的配置流程

假设我们要在RK3568上添加一个SPI设备,完整的配置流程是这样的:

  1. 写Kconfig:在驱动目录下添加配置项,定义依赖关系。
  2. 写Makefile:根据配置项决定是否编译该驱动。
  3. 写dts:在设备树里添加SPI控制器和设备的节点。
  4. 配置内核:执行make menuconfig,选中SPI驱动。
  5. 保存defconfig:执行make savedefconfig,保存配置。
  6. 编译:执行make,生成内核镜像。

你看,Kconfig、menuconfig、defconfig、dts,四者缺一不可。Kconfig定义选项,menuconfig提供界面,defconfig保存默认值,dts描述硬件。理解了它们的分工,内核配置就不再是玄学。

一句话总结:Kconfig是菜单源码,menuconfig是点菜界面,defconfig是套餐菜单,dts是食材清单。四者配合,才能做出一盘能跑的内核。