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 微调。这样比从头一项项选快得多。
/ 键,输入关键词,就能找到选项在哪个菜单下。我曾经为了找一个 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 里。
4.5 本章知识结构图
下面这张图把内核源码结构、配置、编译、烧写的流程串起来了。你可以把它当作本章的「导航地图」。
这张图从左到右展示了从源码到运行的完整链路。你每次做内核构建,本质上就是在走这条流水线。理解了这个流程,后面写驱动、调系统时,心里就有底了。