尧图精选

Linux内核配置实战:Kconfig、.config与defconfig区别及裁剪迁移

🕒 发布时间:2026/10/1 6:03:51 📁 来源:尧图网络
1. 从一次内核编译说起.config 和 defconfig 到底是什么我最早接触 Linux内核 的时候是在一块开发板上折腾驱动。源码下载完敲下make menuconfig屏幕上跳出一堆密密麻麻的菜单保存退出后源码根目录多出一个.config文件。当时的理解很朴素这就是一份“开关清单”。后来要给别人交付代码同事说“把你的 defconfig 发我一份”我才意识到.config和 defconfig 是两码事而且很多人在这两个文件上反复栽跟头。先把结论摆出来.config是当前编译实际生效的配置结果defconfig 是arch/架构/configs/目录下的一份“配置种子”用于在别人的机器上快速复现出相同的配置。前者是“现在这一刻的答案”后者是“可以反复使用的题面”。这个区别听起来简单但它决定了很多操作该往哪个方向走——比如你想把一份调好的配置交给同事复制的是.config还是 defconfig答案完全取决于你希望对方拿到的是什么。这篇文章面向的人比较明确正在学 Linux内核 配置、准备做嵌入式内核源码裁剪、或者第一次接手别人留下的内核工程的开发者。不管你是只会敲make menuconfig的新手还是已经能独立裁剪一份能跑起来的 defconfig 的老手我都希望你在读完之后对这套配置系统“为什么这么设计、哪一步容易出错、出错了怎么查”有更实在的感觉。下面我不会只讲概念会把命令、文件格式、依赖关系、踩坑记录一起端上来。2. 三层结构搞清楚Kconfig、.config、defconfig 各管什么2.1 Kconfig 是规则书不是配置文件很多人第一次看到 Kconfig 会把它当成配置文件来读这是个误会。Kconfig 描述的是“有哪些选项、选项之间谁依赖谁、默认值是什么”它像一份带条件的问卷本身不回答任何问题。真正的答案写在.config里。内核源码树里散布着成百上千个 Kconfig 文件根目录一个每个子目录一个通过source语句层层引用最终拼成一张完整的配置菜单树。看一个最小可用的 Kconfig 片段这个例子我特意挑了常见的驱动风格config MY_DEMO_DRIVER tristate Demo driver for my board depends on PCI select CRC32 default y if MY_PLATFORM help Say Y here to build the demo driver into the kernel. Say M to build it as a loadable module.逐行说。config MY_DEMO_DRIVER声明了符号名最终在.config里会变成CONFIG_MY_DEMO_DRIVER。tristate表示这个选项有三种取值编进内核y、编成模块m、不编译nbool则只有 y 和 n 两种。depends on PCI是硬门槛只有 PCI 被选中这个选项才会出现在菜单里也才有资格被设为 y。select CRC32是反向强制只要这个驱动被选上CRC32 就会被自动拉起来。default y if MY_PLATFORM给了个条件默认值。help后面那段文字就是 menuconfig 里按?时弹出的说明。这里有个非常关键的细节depends on和select是两种完全不同方向的约束。前者是“我要求别人”后者是“我强迫别人”。select用多了会很难受因为它不检查对方的前置条件容易把某个依赖不满足的符号强行拉成 y进而产生编译报错。所以内核社区对滥用select一直有争议你自己写驱动时能不用select就尽量别用。2.2 .config 里的符号长什么样.config是纯文本但格式很固定。它由 Kconfig 系统自动生成文件头通常有一行醒目的注释意思是“自动生成别手改”。下面是我从手边一份配置里摘的片段格式很典型# # Automatically generated file; DO NOT EDIT. # CONFIG_LOCALVERSION-myboard CONFIG_DEFAULT_HOSTNAMElocalhost CONFIG_SMPy CONFIG_NR_CPUS8 CONFIG_MODULESy CONFIG_MODULE_UNLOADy CONFIG_MODULE_FORCE_UNLOADy CONFIG_DEBUG_INFOy # CONFIG_DEBUG_INFO_REDUCED is not set CONFIG_CC_OPTIMIZE_FOR_PERFORMANCEy可以看到几种取值形态y表示内置m表示模块字符串是字符串型8是整数型0x1000是十六进制型。还有一类很特别的行# CONFIG_DEBUG_INFO_REDUCED is not set。它看起来像注释其实是明确的“这个符号存在当前值为 n”。这和“这个符号根本没出现”是两种状态后者往往意味着它的依赖条件没被满足符号压根没进入候选列表。排查问题时区分这两者能省下大量时间。另外提一句模块这个维度。m的选项在编译后产出一个.ko文件可以在系统运行时按需加载。很多驱动调试阶段会选择编成模块因为改一行代码只需要重新编译单个模块不用整颗内核重来。这就是内核模块动态加载机制在配置层面的入口理解成“把功能做成可插拔的零件”就够了。2.3 defconfig 的两种身份defconfig 平时有两个含义必须分清。第一种是arch/架构/configs/目录下的那些文件比如arch/arm64/configs/defconfig、arch/x86/configs/x86_64_defconfig。它们是各架构维护者长期维护的“官方参考配置”特点是覆盖面广、尽量启用主流硬件支持目的是让内核在一大类机器上都能开机。你在网上看到“先make defconfig再编译”的教程说的就是这一份。第二种是make savedefconfig生成的精简配置。它只记录与 Kconfig 默认值不同的部分体积极小常见几百行很适合放进代码仓库长期维护。嵌入式项目里交付给同事的基本都是这一种。把两种身份区分开很多困惑就自动消失了为什么官方 defconfig 有一万多行而同事发我的只有四百行因为一个是“全量快照”一个是“相对默认值的差异集”两者都能生成.config只是生成路径不同。3. 一次完整的配置实操从源码到编译通过3.1 环境准备与依赖安装开始前先把工具链和依赖装齐。menuconfig 这类图形界面依赖 ncurses缺了会直接报Unable to find the ncurses libraries。Debian/Ubuntu 系sudo apt update sudo apt install -y build-essential bc bison flex libssl-dev \ libncurses-dev libelf-dev如果你想用make xconfigQt 界面或make gconfigGTK 界面还得补 Qt 或 GTK 的开发包。我的建议是老老实实用 menuconfig键盘操作快远程 SSH 下也能用nconfig 是它的现代化版本配色和导航更舒服可以根据个人喜好选。交叉编译的场景下还要确认工具链前缀比如 ARM64 常见的aarch64-linux-gnu-。检查方式很简单aarch64-linux-gnu-gcc --version能打印版本号说明 PATH 没问题。这里一个高频坑是工具链装了但没加进 PATH或者装的是不带-linux-gnu后缀的版本编译时才报错前面配置全白做。3.2 拿到初始配置的三条路内核的做法是先把“种子”读进来生成.config再在这个基础上编译。三种典型入口# 方式一直接用架构官方配置 make ARCHarm64 defconfig # 方式二用自己维护的板级配置 make ARCHarm64 myboard_defconfig # 方式三以现有 .config 为基础补齐新增符号 make ARCHarm64 olddefconfig方式一和方式二本质相同都是把arch/arm64/configs/下的某个文件作为输入。区别只是名字裸defconfig通常指向该架构的默认目标带前缀的xxx_defconfig指向你自定义的那份。方式三值得多说两句。内核升级后新版 Kconfig 里会冒出一些老配置中不存在的符号。这时候make oldconfig会逐个问你“这个新选项要不要开”问几十上百个问题体验很差。make olddefconfig则是不问全部取 Kconfig 里声明的默认值适合自动化脚本和批量构建。两者处理的是同一件事把你手上的旧.config迁移到新版本的规则体系里。我个人的习惯是升级大版本时先跑一次oldconfig把关键选项比如文件系统、网络协议栈、电源管理相关的过一遍剩下的再交给olddefconfig。还有一个特殊场景只想看看当前机器上正在运行的内核用了什么配置。前提是内核开了CONFIG_IKCONFIG_PROC那么zcat /proc/config.gz就能看到完整配置如果没有这个选项可以试试scripts/extract-ikconfig vmlinux从内核镜像里把配置抠出来。这招在接手别人留下的老项目时特别好使能省掉大量猜测。3.3 menuconfig 里值得优先关注的几类选项第一次进 menuconfig 会被菜单吓到其实真正需要动手的地方并不多。我通常按这个顺序过一遍架构与平台相关CPU 类型、多核支持CONFIG_SMP、CPU 数量上限CONFIG_NR_CPUS。CPU 数量上限开太大会浪费一点内存开太小会导致机器只认出部分核心按实际核数往上留一点余量就够。模块机制CONFIG_MODULES、CONFIG_MODULE_UNLOAD、CONFIG_MODULE_FORCE_UNLOAD。调试阶段建议都开方便卸载重装量产固件里可以考虑收紧。文件系统根文件系统类型必须编译进内核只能 y不能 m否则内核启动时找不到根分区直接 panic。这是新手最常踩的坑之一。驱动部分存储控制器、网络芯片、串口这类“启动路径上必须存在”的驱动同样建议内置外设类驱动可以做成模块按需加载。调试与符号信息CONFIG_DEBUG_INFO决定是否生成调试符号开与不开编译产物大小能差出好几倍日常调试开正式发布关。menuconfig 的操作快捷键也有必要记一下/是搜索输入符号名或菜单关键字就能跳过去?看当前项说明和依赖Y/M/N分别是三态切换。搜索功能是我用得最多的尤其是接手陌生配置的时候直接搜关键字比一层层翻菜单高效得多。3.4 保存、备份与生成自己的 defconfig配置调完之后先确认保存然后立刻做两件事备份.config以及生成一份精简 defconfig。命令如下# 保存后确认当前配置 cp .config .config.bak # 生成精简 defconfig默认输出到源码根目录的 defconfig 文件 make ARCHarm64 savedefconfig # 把它收编为板级配置 cp defconfig arch/arm64/configs/myboard_defconfigsavedefconfig的逻辑值得解释清楚它会拿当前的.config和 Kconfig 里声明的默认值做逐项比对只把“偏离默认值”的部分写出来。所以最终产物通常很短几百行搞定。这样做的好处是版本升级时冲突少——官方默认值变了你的差异集还在合并起来轻松很多。反过来如果你把完整的.config直接丢进仓库每次升级都会产生巨大 diffreview 的人根本看不下去。顺带说一个进阶玩法make localmodconfig。它会读当前系统已经加载的模块列表然后自动关掉那些没被用到的模块选项一下就能砍掉大量用不到的东西。适合给同一款硬件批量部署内核的场景。但要注意它只反映“当前机器此刻的状态”如果目标机器硬件更丰富直接用它生成配置会漏掉驱动。4. 裁剪 defconfig从能跑到跑得好4.1 裁剪的正确顺序裁剪不是从官方 defconfig 开始一个个删那样效率极低还容易崩。我推荐的路子是先用官方defconfig生成.config保证能正常编译、能启动。在能跑的基线上通过localmodconfig或者手工关掉明显不需要的模块做第一轮瘦身。逐块验证每关掉一批功能就编译一次并启动测试确认没影响再继续。全部调好后执行savedefconfig产出最终交付用的精简文件。这个顺序的核心是“始终有一个已知可用的基线”。任何一次裁剪导致启动失败都可以退回到上一步对比。最怕的是从头开始拼配置出了问题无从下手。关于体积收益我这里有一组实测感受同一块板子官方 defconfig 出来的内核镜像约 20MB 出头经过一轮裁剪后能降到 8MB 左右再结合压缩和去掉调试符号可以进一步接近 5MB。具体数字跟架构、工具链版本、编译优化选项都有关但量级上的差异是真实的。4.2 依赖陷阱与符号消失裁剪时最常见的现象是你明明在菜单里搜到了CONFIG_FOO但位置上显示“被依赖项屏蔽”根本改不了。这通常有三种原因。第一父级选项没开。比如某个 USB 网卡驱动的父级菜单是 USB 支持CONFIG_USB关了它自然出不来。第二depends on链没满足需要顺着?提示里的依赖表达式一层层往上找。第三存在互斥关系比如某些调度器选项之间只能选一个一个开了另一个就自动隐藏。排查依赖有个很实用的方法用scripts/config直接查询和修改# 查询某个符号当前状态 scripts/config --state CONFIG_MY_DEMO_DRIVER # 直接打开某个符号 scripts/config --enable CONFIG_MY_DEMO_DRIVER # 关掉 scripts/config --disable CONFIG_MY_DEMO_DRIVER注意scripts/config改的是.config文本本身不会检查依赖。改完之后一定要跑一次make olddefconfig让 Kconfig 系统重新校验一遍把不满足依赖的选项自动纠正回去。直接手改.config就编译遇到依赖冲突时会得到很难懂的报错。5. 常见问题与排查实录5.1 报错速查表下面这张表是我这几年攒下来的高频问题基本覆盖了日常九成以上的坑现象可能原因排查与解决Unable to find the ncurses libraries缺少 ncurses 开发包安装 libncurses-dev 后重试提示找不到xxx_defconfig文件不在arch/架构/configs/下用find定位文件确认架构变量正确菜单里搜不到某个符号依赖条件未满足或架构不匹配用/搜索查看依赖表达式逐层确认父选项改完.config编译报依赖错误手工编辑破坏了依赖约束执行make olddefconfig重新校验编译通过但启动时 panic根文件系统类型未内置或存储驱动未编译进内核检查相关选项必须为 y不能是 m内核版本号带奇怪后缀CONFIG_LOCALVERSION残留清空该选项或确认是否符合预期make clean后配置丢了用错清理命令make clean保留.configmake mrproper会删除升级内核后配置混乱新增符号未处理先make oldconfig关键项手工确认关于清理命令这条值得单独强调。make clean清的是编译产物.config和 defconfig 都还在make mrproper会把配置一起删掉等于回到刚解压的状态make distclean比 mrproper 还彻底连编辑器备份文件都清。很多人图省事直接mrproper然后发现辛苦调了两天的配置没了。养成习惯调完配置第一件事先cp .config .config.bak。5.2 配置改了却不生效的隐蔽问题这类问题最让人抓狂。明明改了.config重新编译行为却和没改一样。常见原因有三个。一是改的不是正在用的那份配置。交叉编译时需要同时指定ARCH和CROSS_COMPILE如果编译时带了ARCHarm64而改配置时忘了带可能改的是另一棵树或另一份.config取决于源码目录的组织方式。解决方法很简单所有命令都带上同样的ARCH和CROSS_COMPILE写成 shell 脚本或 Makefile 变量别靠脑子记。二是增量编译没有识别到配置变化。正常情况下配置变更会触发相应文件的重新编译但如果手工编辑.config后没走olddefconfig生成的include/generated/autoconf.h可能和.config不一致。这时候可以删掉include/generated/和include/config/目录再编译强制重新生成。三是选项本身被别的配置覆盖。select是强制拉起的如果你的某个选项被另一个已开启的功能select了你手动关掉也没用下一次olddefconfig它又回来。这种情况只能找到那个select它的选项从源头处理。5.3 编译顺利但运行异常怎么查编译能过不代表配置没问题。启动阶段的异常往往和几个固定位置相关我按排查顺序列一下。先看串口有没有输出。如果连启动日志都没有大概率是串口驱动或控制台参数配置问题检查CONFIG_SERIAL_*相关选项是否内置。如果日志停在挂载根文件系统这一步回去确认根文件系统类型和存储控制器驱动是不是 y。如果日志跑到一半提示某个硬件初始化失败那就针对性看那个驱动的配置和时钟、引脚相关的依赖项。有个效率很高的手段是做配置对比。找一份已知能正常启动的.config和你手上的做逐行比较diff -u .config.bak .config | head -100配合scripts/diffconfig工具会更清晰它专门用于两个配置文件之间的差异展示scripts/diffconfig .config.bak .config输出会按“新增”“删除”“变更”分类一眼就能看出你关掉的哪些选项可能和当前故障有关。6. 我踩过的坑和几条实用经验6.1 版本升级时怎么迁移配置内核大版本升级配置迁移是必修课。我的固定动作是三步第一步把旧.config备份好第二步在旧配置基础上跑make olddefconfig让新符号取默认值第三步用savedefconfig生成新版的精简差异集和旧版差异集对比一遍。第三步的对比特别有价值。如果 diff 里突然多出一大片新增项说明新版默认值变了需要评估是否影响你的功能如果少了某些项可能是相关功能被重构或移除了。这个过程花不了十分钟但能提前发现很多升级后的诡异问题。我印象最深的一次是升级后某块网卡不工作了。查了半天驱动源码没头绪最后用配置对比发现新版里这个驱动的依赖条件从“默认开启”变成了“需要显式打开某个 PHY 支持”旧配置迁移时它被自动关掉了。这类问题不看 diff 几乎不可能找到。6.2 团队协作中 defconfig 的管理约定如果内核工程是多人协作defconfig 的管理必须有约定否则迟早出乱子。我们团队的做法是仓库里只放savedefconfig生成的精简文件不放完整.config。每份板级配置单独一个文件放在arch/架构/configs/下命名带上产品代号。提交前必须跑一遍make xxx_defconfig make -j$(nproc)确认能编译通过。涉及配置变更的提交必须在 commit message 里说明改了哪几个选项、为什么改。最后一条看起来繁琐但非常值。配置变更不像代码变更那样有明显的 diff 上下文一个CONFIG_FOOn背后可能是硬件改版、成本优化、也可能是误操作。写清楚原因半年后回头看还能看懂。另外.config一定要写进.gitignore。它是构建产物不该进仓库。我见过有团队把.config提交上去结果每次编译后 git status 都一堆变更实时代码评审时噪声极大。6.3 两条比较个人的小技巧第一条善用KCONFIG_CONFIG环境变量。默认.config必须叫这个名字但如果你需要同时维护多份配置做对比实验可以这样KCONFIG_CONFIG.config.profile_a make menuconfig KCONFIG_CONFIG.config.profile_a make -j$(nproc)这样两份配置互不干扰切换靠环境变量比改文件名再改回来省事得多。第二条把常用命令固化成一个脚本。我自己的项目里有个build.sh内容大致如下#!/bin/bash export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- make myboard_defconfig || exit 1 make -j$(nproc) || exit 1 echo build done别看它简单能避免九成的“忘了带 ARCH 参数”类问题。配置这种活儿靠记忆不如靠脚本把容易忘的东西焊死在流程里比反复提醒自己靠谱。内核配置这套系统刚上手时会觉得绕但它的设计逻辑其实很统一Kconfig 定规则defconfig 定种子.config定当下。把这层关系理顺再配上“先保证基线可用、再逐步裁剪、每一步都验证”的工作节奏剩下的就只是熟练度问题了。至于那些具体的选项名和依赖关系真不用背搜索加依赖提示能解决绝大部分疑问我在实际项目里也是这么干的。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →