4. Linux内核源码结构与配置系统

说实话,我刚接触内核源码那会儿,第一反应就是——这目录也太多了吧?几十个文件夹,上千个文件,根本不知道从哪下手。后来做项目多了,慢慢摸清了门道。今天我就带你把这套结构拆开看看,再聊聊Kconfig和Makefile是怎么配合的,最后说说那几个配置工具怎么用顺手。

4.1 内核源码目录树解析

先看顶层目录。你下载完内核源码,ls一下,大概能看到这些:

arch/       block/      crypto/     Documentation/
drivers/    fs/         include/    init/
ipc/        kernel/     lib/        mm/
net/        samples/    scripts/    security/
sound/      tools/      usr/        virt/

每个目录都有明确分工。我列个表,你一看就明白:

目录作用我常关注的点
arch/体系结构相关代码ARM、x86、RISC-V都在这里,裁剪移植主要动它
drivers/设备驱动占源码一半以上,裁剪时重点砍这里
fs/文件系统实现ext4、proc、sysfs等,嵌入式常用只读文件系统
kernel/核心调度、进程管理一般不动,除非做实时优化
mm/内存管理小内存设备要关注SLAB/SLUB配置
net/网络协议栈物联网设备可以砍掉IPv6等不用的协议
include/头文件ARM相关在include/linux/和arch/arm/include/

我个人习惯,拿到一个新板子,先看arch/arm/下面的mach-*和plat-*目录。这些是板级支持包,里面定义了内存映射、时钟、中断这些底层东西。嗯,这里要注意——新版本内核已经逐步用设备树(DT)替代了板级文件,但老项目里还是经常见到。

我的经验:裁剪内核时,先看arch/arm/configs/目录。里面有很多现成的默认配置,比如vexpress_defconfig、imx_v7_defconfig。拿一个接近你硬件的配置做起点,比从零开始省事太多。

4.2 Kconfig与Makefile机制

这两个文件是内核配置系统的核心。说白了,Kconfig负责“有什么选项可以选”,Makefile负责“选了之后怎么编译”。

4.2.1 Kconfig——配置选项的定义

每个子目录下基本都有Kconfig文件。举个例子,drivers/i2c/Kconfig里可能有这样的内容:

config I2C_CHARDEV
    tristate "I2C character device"
    depends on I2C
    help
      Say Y here to enable I2C character device interface.
      This is needed for i2c-dev userspace access.

这里tristate表示三种状态:不编译、编译进内核(Y)、编译成模块(M)。depends on是依赖关系——如果I2C没选,这个选项根本不会出现。

我曾经在一个项目里踩过坑:选了一个驱动,编译报错说某个函数未定义。查了半天,发现是依赖的另一个模块没选。从那以后,我每次改配置都会用make menuconfig的搜索功能(按/键),看看依赖链是否完整。

4.2.2 Makefile——编译的执行者

Kconfig定义完选项,Makefile根据配置结果决定编译哪些文件。还是拿I2C举例:

obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o

如果CONFIG_I2C_CHARDEV被设为y,就编译进内核;设为m,就编译成模块;设为n,就不编译。

你想想看,整个内核的编译系统就是这么一层层拼起来的。顶层Makefile递归进入各个子目录,每个子目录的Makefile根据Kconfig生成的.config文件决定编译什么。

核心流程:
  1. Kconfig定义选项 → 生成菜单界面
  2. 用户选择 → 生成.config文件
  3. Makefile读取.config → 决定编译对象
  4. 编译链接 → 生成内核镜像和模块

4.3 内核配置工具的使用

配置工具有好几种,我逐个说说。

4.3.1 menuconfig——最常用的文本界面

这是我最常用的工具。在源码根目录执行:

make menuconfig

界面是字符终端里的菜单,用方向键导航,空格键切换选择。几个快捷键要记住:

  • /:搜索选项,输入关键字就能找到位置
  • ?:查看选项的帮助信息
  • Z:显示所有选项(包括被依赖隐藏的)
  • Esc+Esc:返回上一级

我个人习惯,每次改完配置都会按Shift+/查看帮助,确认依赖关系没问题。特别是depends on和select这两个关键字,搞反了会导致配置不一致。

避坑指南:我曾经在配置USB驱动时,选了CONFIG_USB_SUPPORT却没选CONFIG_USB,结果编译出来的内核根本没有USB功能。后来养成习惯:改完配置先make savedefconfig生成精简配置,再检查一遍。

4.3.2 xconfig——图形化配置

如果你有图形桌面环境,可以用:

make xconfig

这个工具基于Qt,界面更直观。左侧是树形菜单,右侧是选项详情。我个人觉得xconfig在查看依赖关系时比menuconfig方便——它会把被依赖的选项高亮显示。

不过说实话,我大部分时间还是用menuconfig。为什么?因为嵌入式开发经常在服务器上做,没有图形界面。menuconfig在任何终端都能跑,而且用熟了效率很高。

4.3.3 其他配置工具

工具命令适用场景
nconfigmake nconfig基于ncurses,比menuconfig操作更流畅
gconfigmake gconfig基于GTK+,和xconfig类似
oldconfigmake oldconfig基于已有.config更新,只问新增选项
defconfigmake defconfig生成架构默认配置

嗯,这里要提一下oldconfig。我每次升级内核版本,都会先用旧版本的.config跑一遍make oldconfig。它会自动保留旧配置,只让你确认新版本新增的选项。这样升级起来平滑很多。

4.4 内核配置系统核心逻辑图

下面这张图展示了Kconfig、Makefile、.config三者之间的关系,以及配置工具在整个流程中的位置:

内核配置系统核心逻辑 Kconfig 定义选项 Makefile 编译规则 .config 配置文件 配置工具:menuconfig / xconfig / nconfig 读取选项定义 写入选择结果 读取配置 编译输出:vmlinux / .ko 执行编译 说明: 1. Kconfig 定义每个选项的类型、依赖、帮助信息 2. 配置工具读取 Kconfig 生成菜单,用户选择后写入 .config 3. Makefile 根据 .config 中的 CONFIG_* 变量决定编译哪些文件

4.5 实战建议

说了这么多,最后给几条实在的建议:

  • 从默认配置开始:别自己从头配。用make <board>_defconfig生成基础配置,再按需裁剪。
  • 善用搜索:menuconfig里按/,输入关键字就能找到选项位置。我配驱动时经常用这个功能。
  • 保存精简配置:配完后执行make savedefconfig,生成只包含非默认选项的精简配置,方便版本管理。
  • 对比配置差异:用diff比较两个.config文件,能快速看出改了哪些选项。
小技巧:如果你不确定某个选项要不要选,先把它编成模块(M)。内核启动后通过modprobe加载试试,不行就卸掉,不影响内核启动。等确定需要了,再改成编译进内核(Y)。

内核配置系统说白了就是一套“选择题+编译规则”的组合。Kconfig出题,你作答,Makefile根据答案干活。搞懂这个逻辑,后面裁剪移植就顺了。