从0开始配置Linux内核:menuconfig不是黑魔法,只是有点绕¶
为什么要写这篇配置教程¶
如果说编译内核是照着菜谱做菜,那配置内核就是自己设计菜谱。你选择哪些功能、不选哪些,直接决定了内核的大小、性能、功能。
但我第一次用menuconfig的时候,完全是懵的状态。几百个菜单项,每个还有子菜单,看得我头晕眼花。网上的教程要么太简单,只告诉你"选这个选那个";要么太复杂,上来就讲Kconfig的语法规则。
我想做的是介于这两者之间:让你理解配置系统是怎么工作的,知道常用配置项的作用,并且能够创建自己的defconfig。到了最后,配置不再是"照抄别人"的机械过程,而是"知道自己在做什么"的理性选择。
Kconfig系统:配置的底层逻辑¶
在进入menuconfig之前,先理解一下Kconfig系统的工作原理。Linux内核的配置系统叫Kconfig,它的核心思想是:
- 用一种专门的语法(Kconfig语言)描述配置选项
- 配置选项之间可以有依赖关系
- 用户通过图形界面(menuconfig等)选择配置
- 生成.config文件,供编译使用
Kconfig文件的格式是这样的:
config DM9000
tristate "DM9000 support"
depends on NET_ETHERNET && ARM
select CRC32
help
DM9000 ethernet driver
To compile this driver as a module, choose M here.
The module will be called dm9000.
这里解释一下各个字段的意思:
config DM9000:定义一个配置选项,名字是CONFIG_DM9000tristate:三种状态(y/m/n),表示可以编译进内核(y)、编译成模块(m)、不编译(n)depends on:依赖关系,只有NET_ETHERNET和ARM都选了,这个选项才可选select:反向依赖,选中DM9000时自动选中CRC32help:帮助文本,在menuconfig里按?可以看到
tristate和bool的区别:tristate是三种状态(y/m/n),用于驱动(可以编译成模块);bool是两种状态(y/n),用于核心功能(不能编译成模块)。
当你运行make menuconfig时,内核会扫描所有Kconfig文件,构建出一颗配置树,然后用ncurses库画出菜单界面。你选择配置项时,Kconfig系统会自动处理依赖关系——比如你选了某个功能,它依赖的其他选项会自动选中;你取消了某个功能,依赖它的选项会变灰或取消。
defconfig到底是什么:默认配置的艺术¶
在02章我们提到了defconfig,现在来深入理解一下。
defconfig是"default configuration"的缩写,字面意思是"默认配置"。但这个"默认"不是内核默认,而是某个平台或板型的默认配置。对于i.MX6ULL,defconfig就是"适合i.MX6ULL的默认配置选择"。
defconfig文件位于arch/arm/configs/目录:
arch/arm/configs/
├── imx_aes_defconfig # NXP i.MX AES引擎支持的配置
├── imx_v6_v7_defconfig # i.MX 6/7系列通用配置
├── imx_v7_defconfig # i.MX 7系列配置
├── multi_v7_defconfig # 多平台v7配置
└── ...
打开一个defconfig看看内容:
CONFIG_SYSVIPC=y
CONFIG_POSIX_MQUEUE=y
CONFIG_NO_HZ=y
CONFIG_HIGH_RES_TIMERS=y
CONFIG_PREEMPT=y
CONFIG_ARM=y
CONFIG_ARCH_MXC=y
...
defconfig只记录与默认值不同的配置项。如果某个选项在Kconfig里默认是n,defconfig里设为y,那defconfig里会记录CONFIG_XXX=y;如果默认就是y,defconfig里就不会出现。
这样的设计有几个好处:
- defconfig文件很小,只包含特定平台的特殊配置
- Kconfig升级时(新增选项、改变默认值),defconfig仍然有效
- 不同平台的defconfig可以共享,差异最小化
当你运行make xxx_defconfig时,内核做的事情是:
- 读取Kconfig文件,建立完整的配置选项树
- 应用指定的defconfig,覆盖部分默认值
- 评估所有依赖关系,自动选中/取消相关选项
- 生成完整的.config文件
所以.config是Kconfig默认值 + defconfig覆盖 + 依赖关系计算的结果,不是简单的复制。
menuconfig实战:图形化配置教程¶
现在我们进入实战环节,打开menuconfig看看。
启动menuconfig¶
你应该看到类似这样的界面:
*** User Mode Linux port ***
General setup --->
[*] Enable loadable module support --->
*** Enable the block layer --->
System Type --->
Bus support --->
Kernel Features --->
Boot options --->
CPU Power Management --->
Floating point emulation --->
Userspace binary formats --->
Power management options --->
Networking support --->
Device Drivers --->
File systems --->
Kernel hacking --->
Security options --->
Cryptographic API --->
Library routines --->
导航操作¶
menuconfig的操作按键如下:
- 方向键:移动光标
- Enter:进入子菜单或选中选项
- Esc Esc:返回上一级菜单
- Space:切换选项状态(y/n/m)
- /:搜索功能
- ?:查看帮助
- :q: 退出(会提示是否保存)
搜索功能¶
搜索功能非常实用,尤其当你不知道某个选项在哪的时候。按/,输入关键词:
Search: DM9000
Symbol: DM9000 [=n]
Type : tristate
Prompt: DM9000 support
Location:
(1) -> Device Drivers
-> Network device support (NETDEVICES [=y])
-> Ethernet driver support (ETHERNET [=y])
Defined at drivers/net/ethernet/davicom/Kconfig:6
Depends on: NETDEVICES [=y] && ETHERNET [=y] && (ARM || MIPS)
Selects: CRC32
搜索结果告诉你选项的位置、类型、依赖关系。你还可以按数字键跳转到该选项(需要支持跳转)。
常用菜单解析¶
我们来看看几个常用的菜单。
System Type:系统类型¶
这个菜单选择CPU和SoC类型:
System Type --->
[*] Support for ARM processor type
ARM system type (NXP i.MX based) --->
( ) Allwinner sunxi SoC
( ) Broadcom BCM2835
(X) NXP i.MX based
...
[*] MXC support
[*] Support for i.MX6ULL
对于i.MX6ULL,确保选中"NXP i.MX based"和"Support for i.MX6ULL"。
Kernel Features:内核特性¶
这里配置内核的基本特性:
Kernel Features --->
Memory split (3G/1G user/kernel split) --->
(1) VMSPLIT_3G
(2) VMSPLIT_2G
(3) VMSPLIT_1G
[*] High Memory Support
[*] Tickless System (Dynamic Ticks)
[*] High Resolution Timer Support
Preemption Model (No Forced Preemption (Server)) --->
(1) No Forced Preemption (Server)
(2) Voluntary Kernel Preemption (Desktop)
(3) Preemptible Kernel (Low-Latency Desktop)
- Memory split:用户空间和内核空间的虚拟内存划分。对于512MB内存的i.MX6ULL,3G/1G足够了
- Tickless System:动态时钟,省电
- Preemption Model:抢占模式。嵌入式系统选"Preemptible Kernel"可以获得更低延迟
Device Drivers:设备驱动¶
这是最大的菜单,包含了所有驱动:
Device Drivers --->
[*] Network device support --->
[*] Ethernet driver support --->
[*] DM9000 support
[*] Character devices --->
[*] Enable TTY
[*] Serial drivers --->
[*] IMX serial port support
[*] I2C support --->
[*] I2C device interface
[*] SPI support --->
[*] MMC/SD/SDIO card support --->
[*] MMC block device driver
这里根据你的硬件需求选择。i.MX6ULL开发板常用的驱动: - 以太网:FEC或DM9000 - 串口:IMX serial port - I2C:I2C device interface - SPI:SPI support - 存储:MMC/SD/SDIO card support
File systems:文件系统¶
选择要支持的文件系统:
File systems --->
[*] Second extended fs support (EXT2)
[*] Ext3 journalling file system support
[*] The Extended 4 (ext4) filesystem
[*] Reiserfs support
[*] Journalling Flash File System v2 (JFFS2) support
[*] UBIFS file system support
DOS/FAT/EXFAT/NT Filesystems --->
[*] MSDOS fs support
[*] VFAT fs support
[*] exFAT fs support
对于嵌入式系统: - EXT4用于SD卡或eMMC的普通分区 - UBIFS用于NAND Flash - VFAT用于与Windows兼容的SD卡分区
保存和加载配置¶
修改完配置后,按Esc退到主菜单,选择"Save"保存:
选择
你也可以把配置保存为另一个文件,以便后续使用。选择"Save configuration to an alternate file",输入文件名。
下次要加载这个配置时,可以:
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=out/linux loadcfg
# 或者在menuconfig里选择"Load configuration from an alternate file"
保存为defconfig¶
如果你想创建自己的defconfig,可以:
这会在输出目录生成defconfig文件,是当前配置的精简版本。你可以把它复制到arch/arm/configs/目录,作为新的defconfig:
下次使用时:
常用内核配置项说明¶
下面列出一些常用且重要的配置项,帮助你理解它们的含义。
系统基本信息¶
| 配置项 | 说明 | 推荐值 |
|---|---|---|
| CONFIG_LOCALVERSION_AUTO | 自动添加版本信息(git commit hash) | y(调试) |
| CONFIG_LOCALVERSION="" | 自定义版本后缀 | 留空 |
| CONFIG_KERNEL_GZIP | 内核压缩方式(gzip) | y |
| CONFIG_KERNEL_XZ | 内核压缩方式(xz,更小但慢) | 可选 |
内存和进程¶
| 配置项 | 说明 | 推荐值 |
|---|---|---|
| CONFIG_SMP | 对称多处理器支持 | y(多核) |
| CONFIG_NR_CPUS=2 | 最大CPU核心数 | 实际核心数 |
| CONFIG_VMSPLIT_3G | 3G用户/1G内核 | y(内存<1GB) |
| CONFIG_PREEMPT | 抢占式内核 | y(低延迟) |
网络相关¶
| 配置项 | 说明 | 推荐值 |
|---|---|---|
| CONFIG_NET | 网络支持 | y |
| CONFIG_INET | TCP/IP协议栈 | y |
| CONFIG_NETFILTER | 防火墙/Netfilter | y(需要时) |
| CONFIG_VLAN_8021Q | VLAN支持 | 需要时 |
| CONFIG_BRIDGE | 网桥支持 | 需要时 |
驱动相关¶
| 配置项 | 说明 | 推荐值 |
|---|---|---|
| CONFIG_SERIAL_IMX | i.MX串口驱动 | y |
| CONFIG_SERIAL_IMX_CONSOLE | 串口控制台 | y |
| CONFIG_FEC | i.MX以太网驱动 | y |
| CONFIG_DM9000 | DM9000以太网驱动 | 需要时 |
| CONFIG_I2C | I2C核心 | y |
| CONFIG_I2C_IMX | i.MX I2C驱动 | y |
| CONFIG_SPI | SPI核心 | y |
| CONFIG_SPI_IMX | i.MX SPI驱动 | y |
| CONFIG_MMC | MMC/SD核心 | y |
| CONFIG_MMC_SDHCI | SDHCI控制器 | 需要时 |
| CONFIG_MMC_SDHCI_ESDHC_IMX | i.MX ESDHC驱动 | y |
文件系统¶
| 配置项 | 说明 | 推荐值 |
|---|---|---|
| CONFIG_EXT4_FS | EXT4文件系统 | y |
| CONFIG_VFAT_FS | VFAT文件系统 | y |
| CONFIG_FAT_FS | FAT文件系统 | y |
| CONFIG_NTFS_FS | NTFS文件系统(只读) | 需要时 |
| CONFIG_JFFS2_FS | JFFS2(NOR Flash) | 需要时 |
| CONFIG_UBIFS_FS | UBIFS(NAND Flash) | 需要时 |
| CONFIG_PROC_FS | proc伪文件系统 | y |
| CONFIG_SYSFS | sysfs伪文件系统 | y |
调试相关¶
| 配置项 | 说明 | 推荐值 |
|---|---|---|
| CONFIG_DEBUG_KERNEL | 调试支持 | n(发布版) |
| CONFIG_DEBUG_INFO | 调试信息(-g) | y(调试) |
| CONFIG_MAGIC_SYSRQ | SysRq键 | y(调试) |
| CONFIG_KALLSYMS | 符号表 | y |
| CONFIG_PRINTK | printk输出 | y |
踩坑笔记:配置中的常见问题¶
在配置内核时,我踩过不少坑,这里分享几个最常见的。
问题1:模块加载失败¶
你编译了一个驱动为模块(m),但加载时失败:
这通常是因为缺少依赖的模块或符号。检查一下:
它会显示依赖关系,确保依赖的模块都加载了。
问题2:内核太大¶
你发现编译出来的zImage有10MB+,太大了。可能原因:
- 编译了太多驱动为y(应该编译为m,模块可以按需求加载,但是编进去了,裁剪可就难了)
- 开启了太多调试选项
- 没有精简不必要的功能
解决方法:使用menuconfig,把不需要的驱动设为m或n。
问题3:配置冲突¶
menuconfig里某个选项是灰色的,无法选择。这是因为依赖关系不满足。按?查看依赖:
Symbol: FOO [=n]
Type : tristate
Prompt: Foo support
Defined at drivers/Kconfig:10
Depends on: BAR && !BAZ
需要先选中BAR,取消BAZ。
问题4:.config被覆盖¶
你编辑了.config,但再次make时被覆盖。因为make会重新评估Kconfig依赖。
解决方法:
1. 修改defconfig,而不是直接改.config
2. 用make oldconfig更新.config而不是重新生成
3. 禁用CONFIG_IKCONFIG(内嵌配置)
实战:创建自己的defconfig¶
现在我们来创建一个适用于i.MX6ULL开发板的defconfig。
方法1:基于现有defconfig修改¶
最简单的方法是基于imx_aes_defconfig修改:
# 加载基础配置
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=out/linux imx_aes_defconfig
# 用menuconfig调整
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=out/linux menuconfig
# 保存为精简defconfig
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=out/linux savedefconfig
# 复制到源码目录
cp out/linux/defconfig arch/arm/configs/my_imx6ull_defconfig
方法2:从头创建¶
如果你想完全控制,可以:
# 清空配置
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=out/linux allnoconfig
# 用menuconfig逐步添加
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=out/linux menuconfig
# 保存defconfig
cp out/linux/.config arch/arm/configs/my_imx6ull_defconfig
方法3:脚本化配置¶
对于重复性配置,可以写一个脚本:
#!/bin/bash
# config_my_board.sh
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=out/linux imx_aes_defconfig
# 使用scripts/config工具修改配置
./scripts/config --file out/linux/.config \
--set-val CONFIG_LOCALVERSION "-myboard" \
--disable CONFIG_DEBUG_KERNEL \
--enable CONFIG_PREEMPT \
--module CONFIG_DM9000
# 更新配置
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=out/linux olddefconfig
# 保存
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=out/linux savedefconfig
cp out/linux/defconfig arch/arm/configs/my_imx6ull_defconfig
配置优化建议¶
根据使用场景,给一些配置建议。
最小化配置(体积优先)¶
# 不编译模块
CONFIG_MODULES=n
# 精简驱动
CONFIG_NETDEVICES=y
# 只选必需的网卡驱动
# 去掉调试
CONFIG_DEBUG_KERNEL=n
CONFIG_DEBUG_INFO=n
低延迟配置(实时性优先)¶
调试配置(开发阶段)¶
# 调试信息
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_KERNEL=y
CONFIG_MAGIC_SYSRQ=y
# 符号表
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
# Oops定位
CONFIG_DEBUG_INFO=y
CONFIG_FRAME_POINTER=y
写在最后¶
到这里,Linux内核配置的核心内容你就掌握了。从Kconfig系统的原理,到menuconfig的使用,到常用配置项的含义,再到创建自己的defconfig,我们走完了配置的完整流程。
配置不是神秘的艺术,而是有规律可循的系统工程。理解了依赖关系、知道常用选项的作用、掌握了创建defconfig的方法,你就可以根据自己的需求定制内核了——无论是最小化体积、优化性能,还是增强调试能力,都在你的掌控之中。
下一篇文章,我们将进入内核模块的世界。你会看到:
- 什么是内核模块,为什么需要它
- 如何编写一个简单的内核模块
- 如何编译和加载模块
- 模块和驱动的区别
准备好了吗?我们来探索内核的动态扩展能力。
延伸阅读
- Kconfig Language Documentation - Kconfig语法文档
- Linux Kernel Module Programming Guide - 内核模块编程指南
- Kernel Newbies: Configuration - 配置教程