2. 内核源码结构解析:Linux内核源码目录结构、Kconfig与Makefile机制、内核配置系统(menuconfig)

说实话,很多做嵌入式Linux的朋友,拿到内核源码第一反应就是直接 make menuconfig,然后一顿操作猛如虎。但你要问他某个配置项到底在哪层目录、Kconfig 和 Makefile 是怎么配合的,他可能就含糊了。

我当年刚入行时也踩过这个坑。有一次给某款车机裁剪内核,明明把某个驱动模块在 menuconfig 里勾掉了,编译时却还是报符号冲突。折腾了大半天,最后发现是 Kconfig 的依赖关系没理清,那个模块被别的子系统强制选上了。从那以后,我养成了一个习惯:改配置之前,先看 Kconfig。

这一章,我们就来把内核源码的骨架摸清楚。你想想看,内核几万文件,要是没有一套清晰的目录结构和配置机制,根本没法维护。Linux 社区能搞定,靠的就是 Kconfig + Makefile 这套组合拳。

2.1 内核源码顶层目录结构

先看顶层。你解压一个 Linux 内核源码包,会看到这些目录:

目录/文件 作用 车载裁剪关注点
arch/ 体系结构相关代码,如 x86、arm64、riscv 车载常用 arm64,重点关注 arch/arm64/
block/ 块设备层核心代码 eMMC、UFS 相关调度器可裁剪
crypto/ 加密算法库 若不用磁盘加密或安全启动,可大幅裁剪
drivers/ 设备驱动,最大目录 车载重点:i2c、spi、gpio、pinctrl、drm、net、mmc、usb
fs/ 文件系统实现 保留 ext4、squashfs、proc、sysfs,其余可砍
kernel/ 核心调度、进程管理、irq 基本不动,但可关掉 cgroup 等非必要特性
net/ 网络协议栈 车载保留 can、tcp/ip、ipv6(可选),砍掉无线、bridge 等
sound/ 音频子系统 ALSA 核心保留,codec 驱动按硬件选配
include/ 全局头文件 一般不动,但裁剪时注意 include/config/ 自动生成
scripts/ 编译脚本、Kconfig 解析工具 不用动,但理解其逻辑有助于调试
init/ 内核初始化流程 可定制启动参数默认值
mm/ 内存管理 车载建议保留 CMA、SLUB,关掉不用的页面回收策略
我的习惯:每次接手一个新平台,我会先 ls -d */ 扫一遍顶层目录,然后重点关注 arch/arm64/ 和 drivers/ 下的子目录。这两个地方占了裁剪工作量的 70% 以上。

2.2 Kconfig 机制:配置项的骨架

Kconfig 是什么?说白了,它就是内核配置项的「说明书」。每个目录下都有一个 Kconfig 文件,定义了该目录下有哪些配置选项、它们的依赖关系、默认值等。

来看一个车载场景下常见的例子。假设我们要裁剪掉蓝牙驱动,先找到 drivers/bluetooth/Kconfig:

config BT_HCIUART
    tristate "UART (HCI) protocol support"
    depends on BT
    select SERIAL_DEV_BUS
    help
      This driver provides support for Bluetooth HCI UART transport layer.
      Say Y here to compile it into the kernel, or M to compile as module.

这里有几个关键字段:

  • tristate:三态,Y(编入内核)、M(模块)、N(不选)。车载裁剪时,能用模块就别编入内核。
  • depends on:依赖关系。如果 BT 没选,这个选项根本不会出现。
  • select:反向选择。选了它,会自动选上 SERIAL_DEV_BUS。这个要小心,容易引入不必要的依赖。
我曾经踩过的坑:有一次裁剪网络子系统,我把 NET 核心选项改成了 N,结果整个 drivers/ 下大半驱动都消失了——因为它们都 depends on NET。所以,改核心选项前,先用 grep -r "depends on" 查一下影响面。

2.3 Makefile 机制:编译的指挥棒

Kconfig 决定了「选什么」,Makefile 决定了「怎么编译」。每个目录下的 Makefile 会根据 Kconfig 生成的 .config 文件,决定哪些 .c 文件参与编译。

看一个典型的 Makefile 片段:

# drivers/bluetooth/Makefile
obj-$(CONFIG_BT_HCIUART)    += hci_uart.o
obj-$(CONFIG_BT_HCIVHCI)    += hci_vhci.o
obj-$(CONFIG_BT_HCIBCM2038) += bcm2038.o

这里的 obj-$(CONFIG_BT_HCIUART) 会被展开为:

  • 如果 CONFIG_BT_HCIUART=y,则展开为 obj-y,编译进内核
  • 如果 CONFIG_BT_HCIUART=m,则展开为 obj-m,编译为模块
  • 如果 CONFIG_BT_HCIUART 未定义,则整行消失,不编译

嗯,这里要注意:obj-y 和 obj-m 的编译顺序。内核链接时,obj-y 会按目录顺序链接,如果两个驱动有依赖关系,必须保证先编译被依赖的一方。车载平台上,我见过因为 Makefile 顺序问题导致驱动 probe 失败的情况。

2.4 内核配置系统:menuconfig 实战

menuconfig 是基于 ncurses 的图形化配置界面。你执行 make menuconfig 后,它会:

  1. 读取 arch/arm64/Kconfig(或其他架构),递归解析所有子目录的 Kconfig
  2. 生成一个树形菜单,让你勾选
  3. 保存结果到 .config 文件

车载裁剪时,我常用的几个操作:

  • / 搜索:按 / 键,输入关键字,快速定位配置项。比如搜 "CAN",就能找到所有 CAN 相关选项。
  • Z 查看依赖:按 Z 键,显示当前选项的完整依赖链。这个在排查「为什么选项灰掉」时特别有用。
  • 差异对比:用 make savedefconfig 生成精简配置,再用 diff 对比两个版本的差异。
核心原则:车载裁剪不是「能删就删」,而是「够用就好」。保留 CAN、I2C、SPI、GPIO、Pinctrl、DRM(显示)、ALSA(音频)、eMMC/UFS 驱动,其余按需添加。每加一个选项,都要问自己:这个功能在车上真的需要吗?

2.5 知识结构图:Kconfig + Makefile 协作流程

下面这张图,把 Kconfig 和 Makefile 的协作关系画清楚了。我建议你把它存下来,每次裁剪时对照着看。

Kconfig + Makefile 协作流程图 Kconfig + Makefile 协作流程 内核源码目录 Kconfig 定义配置项 Makefile 定义编译规则 menuconfig 读取 Kconfig 生成 .config 文件 Makefile 读取 .config 编译生成 vmlinux / .ko 说明: 1. Kconfig 定义「有什么选项」,menuconfig 提供交互界面,.config 记录「选了哪些」。 2. Makefile 根据 .config 中的 CONFIG_* 变量,决定编译哪些源文件。

2.6 车载裁剪实战:从 Kconfig 入手

最后,分享一个我实际用过的裁剪流程。假设我们要为某款车机裁剪内核:

  1. 先跑默认配置:make defconfig 或使用厂商提供的 defconfig 文件
  2. 搜索关键功能:用 make menuconfig 的 / 搜索 CAN、I2C、SPI、DRM、ALSA、MMC 等
  3. 逐层关闭:从 Device Drivers 开始,关掉不用的硬件驱动(如 WiFi、Bluetooth、NFC、TV tuner 等)
  4. 检查依赖:每次关闭后,用 make olddefconfig 自动处理依赖,再用 diff 对比变化
  5. 验证编译:make -j$(nproc),确保无编译错误
  6. 启动测试:在目标板上启动,检查 dmesg 是否有驱动加载失败
小技巧:用 make listnewconfig 可以列出所有新增的配置项,适合在升级内核版本时快速了解变化。用 make help 能看到所有配置相关的目标。