4、Kconfig语法基础:从config到help,一个都不能少
说实话,Kconfig 这玩意儿,刚接触的时候我也觉得挺绕的。不就是个菜单配置嘛,能有多复杂?结果第一次自己写内核模块的 Kconfig,就被 depends on 和 select 给坑了一把——依赖关系写反了,编译出来一堆莫名其妙的符号错误。嗯,从那以后我就老老实实把语法啃了一遍。
今天咱们就把 Kconfig 的核心语法过一遍。你想想看,内核里几千个配置项,全靠这套语法组织起来。搞懂了它,你就能随心所欲地裁剪内核,想加什么加什么,想砍什么砍什么。
4.1 config:最基础的配置项
config 是 Kconfig 里最常用的关键字。说白了,它就是定义一个开关,让用户决定「这个功能要不要编译进内核」。
config MY_DRIVER
bool "Enable My Driver"
default y
help
This is a sample driver for demonstration.
Say Y here if you want to use My Driver.
这里 bool 表示二选一:Y 或 N。我个人习惯把常用的驱动默认设为 y,省得每次都要手动勾选。但要注意,默认 y 的选项多了,内核会变得臃肿——我在一个嵌入式项目里就吃过这个亏,默认全开,结果编译出来的内核镜像大了 30%。
除了 bool,还有 tristate(三态:Y/M/N)、int(整数)、string(字符串)等类型。其中 tristate 最常用,因为它支持模块化编译:Y 编进内核,M 编成模块,N 不编译。
避坑指南:我曾经把驱动写成 bool,结果用户想编译成模块都不行。后来改成了 tristate,灵活性就大多了。所以,除非你确定这个功能必须内置,否则优先用 tristate。
4.2 menuconfig:带子菜单的配置项
menuconfig 和 config 的区别在于:它本身是一个开关,同时还能包含子选项。比如:
menuconfig MY_SUBSYSTEM
bool "My Subsystem Support"
help
Enable My Subsystem.
if MY_SUBSYSTEM
config MY_FEATURE_A
bool "Feature A"
config MY_FEATURE_B
bool "Feature B"
endif
当 MY_SUBSYSTEM 被选中时,下面的 MY_FEATURE_A 和 MY_FEATURE_B 才会显示出来。这种层级结构在大型驱动里特别有用——我维护的一个网卡驱动,光子选项就有十几个,用 menuconfig 组织起来,用户配置起来一目了然。
4.3 choice:单选组
有时候你需要让用户从多个选项中选一个,比如选择调度算法、选择内存分配策略。这时候 choice 就派上用场了。
choice
prompt "Scheduler Algorithm"
default SCHED_CFS
config SCHED_CFS
bool "Completely Fair Scheduler"
config SCHED_RT
bool "Real-Time Scheduler"
config SCHED_IDLE
bool "Idle Scheduler"
endchoice
注意,choice 里的选项只能用 bool,而且只能选一个。默认值用 default 指定。我记得有一次写错了,把两个选项都设了 default y,结果编译报错——嗯,choice 里只能有一个默认值。
4.4 menu:纯菜单分组
menu 和 menuconfig 不同,它本身不是一个配置项,只是一个视觉上的分组。比如:
menu "Network Drivers"
config DRV_A
tristate "Driver A"
config DRV_B
tristate "Driver B"
endmenu
在 make menuconfig 界面里,你会看到一个叫 "Network Drivers" 的菜单项,点进去才能看到具体的驱动。这种纯分组的方式,适合把逻辑上相关的配置项放在一起,但又不需要一个总开关来控制它们。
4.5 if:条件判断
if 用来控制某些配置项是否可见。比如:
config DEBUG_MODE
bool "Enable Debug Mode"
if DEBUG_MODE
config DEBUG_LEVEL
int "Debug Level (0-3)"
default 1
config DEBUG_LOG
bool "Enable Debug Log"
endif
只有 DEBUG_MODE 被选中时,DEBUG_LEVEL 和 DEBUG_LOG 才会出现。这种用法在调试功能里很常见——我一般会把调试相关的选项都包在 if DEBUG 里,发布时关掉 DEBUG,所有调试选项就自动隐藏了。
4.6 depends on:依赖关系
depends on 是 Kconfig 里最核心的依赖机制。它表示:当前配置项依赖于另一个配置项被选中。
config USB_SUPPORT
bool "USB Support"
config USB_KEYBOARD
tristate "USB Keyboard"
depends on USB_SUPPORT
如果 USB_SUPPORT 没被选,USB_KEYBOARD 就不会显示,也无法被选中。我曾经在项目里遇到一个情况:A 依赖 B,B 依赖 C,C 又依赖 A——形成了循环依赖。结果 make menuconfig 直接报错,卡住了。所以写 depends on 的时候,一定要检查依赖链有没有环。
注意:depends on 只是控制「可见性」,不会自动选中依赖项。如果你希望选中 A 时自动选中 B,需要用 select。
4.7 select:反向选择
select 和 depends on 正好相反。它是说:当我被选中时,我强制选中另一个配置项。
config USE_FPU
bool "Use FPU"
config MATH_EMULATION
bool "Math Emulation"
select USE_FPU
当用户选中 MATH_EMULATION 时,USE_FPU 会被自动勾选。这个机制在硬件依赖场景下特别有用——比如某个驱动必须依赖 DMA 引擎,用 select 就能保证 DMA 被自动开启。
但要注意,select 不能滥用。我见过一个内核版本,因为 select 链太长,导致配置界面卡顿。而且 select 会绕过用户的意愿——有时候用户可能不想开某个功能,但被 select 强制开了,这会引起困惑。
4.8 default:默认值
default 用来指定配置项的默认值。可以是 y、n、m,也可以是数字或字符串。
config BUFFER_SIZE
int "Buffer Size (bytes)"
default 4096
config ENABLE_CACHE
bool "Enable Cache"
default y
我个人习惯把常用的、稳定的功能默认设为 y,把实验性的功能默认设为 n。这样用户第一次配置时,得到的是一个「安全」的配置。等用户熟悉了,再自己去调整。
4.9 help:帮助信息
help 是给用户看的说明文字。写得好不好,直接影响用户体验。
config MY_DRIVER
tristate "My Driver"
help
This driver supports MyDevice series chips.
It provides high-speed data transfer via PCIe.
To compile this driver as a module, say M here.
The module will be called my_driver.ko.
写 help 的时候,我建议包含三点:这个功能是干什么的、什么时候该选 Y/M/N、编译出来的模块叫什么名字。我曾经接手过一个老驱动,help 里只写了 "Enable support",看完等于没看——嗯,这种帮助信息还不如不写。
知识体系总览
下面这张图,把 Kconfig 的核心语法和它们之间的关系梳理了一下。你可以把它当作一个速查表:
小结
Kconfig 的语法其实不复杂,核心就是几个关键字。但真正用好它,需要理解每个关键字的「语义」——比如 depends on 和 select 的区别,menu 和 menuconfig 的适用场景。我在实际项目中,一般会先用 menu 把功能分组,然后用 config 定义具体选项,最后用 depends on 和 select 梳理依赖关系。这套流程走下来,配置界面清晰,编译也不会出幺蛾子。
小技巧:写 Kconfig 的时候,多用 make menuconfig 预览效果。我每次改完都会跑一遍,看看界面是不是符合预期——毕竟,用户第一眼看到的就是这个菜单。