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

说到内核源码,很多初学者第一反应就是「好大、好乱、看不懂」。其实不然。内核的目录结构非常清晰,每个文件夹都有它明确的职责。我刚开始接触内核时,也对着几十万个文件发过呆,后来摸清了门道,发现它比想象中有条理得多。

4.1 内核源码目录结构

先看顶层目录。你下载一份 Linux 内核源码,解压后第一眼看到的文件夹,大概有这些:

目录 作用 我常用的场景
arch/ 体系结构相关代码 移植内核时必改,ARM、x86、RISC-V 都在这里
drivers/ 设备驱动 写驱动时 80% 的时间泡在这里
fs/ 文件系统实现 调试存储问题时进来翻过几次
kernel/ 核心调度、进程管理 一般不动,除非改调度策略
mm/ 内存管理 调内存碎片问题时查过
net/ 网络协议栈 做网卡驱动时经常参考
include/ 全局头文件 找宏定义、结构体定义的第一站
scripts/ 编译、配置辅助脚本 很少直接改,但调试构建问题时会看
Documentation/ 内核文档 遇到新特性先翻这里,比网上博客靠谱

嗯,这里有个小技巧。你如果做驱动开发,drivers/ 下面又按子系统分了目录:gpio/、i2c/、spi/、usb/、net/ 等等。每个子目录里,通常都有一个 Kconfig 和一个 Makefile。这两个文件,就是内核配置和编译的「指挥中心」。

我的习惯:拿到一块新板子,先看 arch/arm/boot/dts/ 下的设备树文件,再看对应 drivers/ 下的驱动。这样能快速定位硬件相关的代码。

4.2 Kconfig 与 Makefile 机制

内核的配置系统,说白了就是靠 Kconfig 定义「有什么选项」,靠 Makefile 决定「编译哪些文件」。两者配合,构成了整个内核的构建骨架。

4.2.1 Kconfig:定义配置选项

每个驱动目录下都有一个 Kconfig 文件。它的语法很直观:

config GPIO_SYSFS
    bool "GPIO sysfs interface"
    depends on SYSFS
    help
       Say Y here to enable the GPIO sysfs interface.
       This allows userspace to control GPIO pins via /sys/class/gpio/.

这里 config 定义了一个配置项 GPIO_SYSFS,类型是 bool(要么编进去,要么不编)。depends on 表示依赖关系——如果 SYSFS 没开,这个选项也不会出现。help 就是帮助信息。

除了 bool,还有 tristate(三态:不编、编进内核、编成模块)、int、string 等类型。我写驱动时最常用的是 tristate,因为调试阶段编成模块方便加载卸载。

避坑指南:我曾经在 Kconfig 里写错了 depends on 的依赖项,结果 menuconfig 里死活看不到我的选项。排查了半天才发现是依赖链断了。记住:depends on 是「与」关系,多个依赖用逗号分隔。

4.2.2 Makefile:控制编译行为

对应的 Makefile 写法更简单:

obj-$(CONFIG_GPIO_SYSFS) += gpio-sysfs.o

这行的意思是:如果 CONFIG_GPIO_SYSFS 的值是 y,就把 gpio-sysfs.c 编进内核;如果是 m,就编成模块 gpio-sysfs.ko;如果是空,就不编译。

多文件模块怎么写?

obj-$(CONFIG_MY_DRIVER) += my_driver.o
my_driver-objs := main.o utils.o init.o

这样 main.c、utils.c、init.c 会一起编成 my_driver.ko。

4.3 内核配置方法:menuconfig

配置内核最常用的工具就是 menuconfig。它基于 ncurses 库,在终端里提供一个菜单界面。

使用方法:

make menuconfig

进去之后,你会看到按功能分类的菜单树。上下键移动,回车进入子菜单,空格切换选项状态([ ] 不选、[*] 编入内核、[M] 编成模块)。

我个人习惯先加载默认配置:

make defconfig        # 架构默认配置
make xxx_defconfig    # 特定板子的配置,比如 make imx_v7_defconfig

然后再用 make menuconfig 微调。这样比从头一项项选快得多。

注意:menuconfig 里搜索功能很实用。按 / 键,输入关键词,就能找到选项在哪个菜单下。我曾经为了找一个 CONFIG_I2C_CHARDEV,翻了五分钟菜单,后来才知道有搜索功能……别学我。

4.4 内核编译与烧写

配置完成后,编译就简单了:

make -j4               # 4 线程并行编译
make zImage            # 编译压缩内核镜像
make modules           # 编译模块
make dtbs              # 编译设备树

编译产物通常在 arch/arm/boot/(ARM 平台)下:

  • zImage 或 Image:内核镜像
  • dts/ 或 *.dtb:设备树二进制文件
  • vmlinux:未压缩的内核,调试用

模块编译好后,安装到目标文件系统:

make modules_install INSTALL_MOD_PATH=/path/to/rootfs

烧写方式取决于你的硬件平台。常见的有:

方式 适用场景 命令示例
SD 卡烧写 开发板调试 dd if=zImage of=/dev/sdb1
网络 TFTP 加载 快速迭代 U-Boot 下 tftp 0x80800000 zImage
NAND/NOR Flash 量产产品 通过 U-Boot 的 nand write 命令
JTAG 烧写 裸机调试、变砖恢复 用 OpenOCD 或厂商工具

我最常用的是 TFTP 方式。改一行代码,重新编译,U-Boot 里重启、加载、启动,一分钟搞定。等驱动稳定了,再烧到 Flash 里。

小经验:调试阶段别急着烧 Flash。用 TFTP 或 NFS 挂载根文件系统,改代码、编译、重启测试,效率高得多。我早期做项目时每次都烧写,一天下来大半时间在等烧写完成。

4.5 本章知识结构图

下面这张图把内核源码结构、配置、编译、烧写的流程串起来了。你可以把它当作本章的「导航地图」。

Linux 内核构建流程 内核源码目录结构 arch/ — 体系结构代码 drivers/ — 设备驱动 fs/ — 文件系统 include/ — 头文件 配置系统 Kconfig — 定义配置选项 Makefile — 控制编译行为 menuconfig — 图形化配置 .config — 最终配置文件 编译 make zImage make modules make dtbs make -jN 编译产物 zImage / Image — 内核镜像 *.dtb — 设备树 *.ko — 内核模块 烧写方式 SD 卡 — dd 命令 TFTP — 网络加载 Flash — nand write 调试与验证 串口打印 NFS 根文件系统 KGDB 调试 💡 调试阶段推荐:TFTP 加载内核 + NFS 根文件系统,避免反复烧写 Flash。 📌 核心文件:.config(配置)、Kconfig(选项定义)、Makefile(编译规则) 📌 核心命令:make menuconfig → make zImage → 烧写/加载

这张图从左到右展示了从源码到运行的完整链路。你每次做内核构建,本质上就是在走这条流水线。理解了这个流程,后面写驱动、调系统时,心里就有底了。