3. 内核源码结构:下载、理解与配置

好,咱们正式开始接触内核源码。说实话,很多初学者一看到 Linux 内核那几万个文件就头皮发麻。我当年也一样,第一次解压完源码,ls 了一下,直接愣住了——这从哪儿看起?

别急。这一章我就带你走一遍标准流程:下载源码 → 看懂目录树 → 用 menuconfig 配置内核。这三步走顺了,后面写驱动才有根基。

3.1 下载 Linux 内核源码

先说说怎么拿到源码。我个人习惯直接从 kernel.org 下载,这是官方发布渠道,最干净。

推荐下载方式:

  • 官网:https://www.kernel.org → 选一个长期支持版(LTS)
  • 命令行:wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.76.tar.xz
  • 解压:tar -xf linux-6.1.76.tar.xz

为什么强调 LTS 版本?我在项目里吃过亏——用了一个非稳定版内核,驱动调得好好的,升级内核小版本后 API 变了,编译直接报错。从那以后,我选内核版本就一个原则:不追新,只追稳。

小技巧:如果你在嵌入式板子上开发,板子厂商通常会提供一份 .config 文件。下载对应版本的内核后,直接把这份配置拷过来,能省大量时间。

3.2 理解内核目录树

解压完,咱们看看顶层目录长什么样。你执行 ls -l 会看到类似这样的结构:

arch/       — 体系结构相关代码(ARM、x86、RISC-V 等)
block/      — 块设备层
crypto/     — 加密子系统
Documentation/ — 内核文档(别忽略它)
drivers/    — 驱动代码(咱们的主战场)
fs/         — 文件系统(ext4、btrfs、proc 等)
include/    — 内核头文件
init/       — 内核初始化代码
ipc/        — 进程间通信
kernel/     — 核心内核代码(调度、信号等)
lib/        — 内核工具函数库
mm/         — 内存管理
net/        — 网络协议栈
scripts/    — 编译和配置脚本
security/   — 安全模块
sound/      — 音频子系统
tools/      — 用户态工具
usr/        — 初始 ramfs 相关

嗯,这里我要多说一句:drivers/ 目录是我们驱动工程师待得最久的地方。它下面又按子系统分了无数子目录:drivers/i2c/、drivers/spi/、drivers/gpio/、drivers/net/……你写什么类型的驱动,就去对应的子目录里找参考。

举个例子,我当年写第一个 I2C 触摸屏驱动,就是先跑到 drivers/input/touchscreen/ 里翻了一遍,找到一个类似的芯片驱动,照着它的框架改出来的。说白了,内核源码就是最好的参考手册。

注意:不要试图一次性读完所有目录。内核源码有几千万行,没人能全看完。我的建议是:先搞懂你需要的子系统目录,其他的用到再查。

下面这张图帮你理清内核源码的核心层次关系:

Linux 内核源码核心结构 用户空间(Applications / C Library) 系统调用接口(Syscall) 内核核心层(kernel/、mm/、fs/、net/、ipc/) 驱动框架层(drivers/ 各子系统:I2C、SPI、GPIO、Net…) 硬件抽象层(arch/、include/、设备树 DTS) 硬件(CPU、外设、总线) 说明:驱动工程师主要工作在“驱动框架层”和“硬件抽象层” 写驱动时,你通常需要同时修改 drivers/ 下的代码和设备树文件

3.3 配置内核:menuconfig

源码下载好了,目录结构也了解了,下一步就是配置内核。配置内核说白了就是告诉编译器:哪些功能要编进内核,哪些编成模块,哪些干脆不要。

最常用的配置工具是 menuconfig。执行命令:

make menuconfig

你会看到一个基于 ncurses 的图形菜单。界面虽然简陋,但功能很强大。我来拆解一下常用操作:

操作 按键 说明
上下移动 ↑ / ↓ 在菜单项之间移动
进入子菜单 Enter 选中 ---> 标记的项
选中/取消 空格 / Y / N Y=编入内核,M=编译为模块,N=不编译
搜索 / 按符号名搜索配置项(我最常用的功能)
退出 ESC + ESC 返回上一级或退出

搜索功能一定要会用。比如你想配 I2C 驱动,直接按 / 输入 I2C,所有相关的配置项就列出来了。我曾经花半小时翻菜单找不到一个选项,后来发现搜索一秒搞定——嗯,从那以后我再也不硬翻了。

3.4 配置项的三态与依赖

每个配置项有三种状态,用 <*>、<M>、< > 表示。但这里有个坑:配置项之间有依赖关系。

举个例子,你想把某个 SPI 驱动编成模块,但它的父选项 SPI 子系统本身没打开,那这个驱动选项根本不会出现。这就是所谓的 depends on 机制。

我在调试一块 SPI 屏幕时遇到过这问题——折腾了半天,发现是 SPI 核心没开。所以我的建议是:先确保子系统打开,再去找具体驱动。

避坑指南:我曾经在配置内核时,为了省空间把 CONFIG_FW_LOADER 关了,结果固件加载子系统直接罢工,所有需要固件的驱动都报错。这种基础选项,不确定就别动。

3.5 保存配置与编译

配置完成后,选择 Save,配置会写入 .config 文件。这个文件是纯文本的,你也可以直接编辑它——但我个人不推荐手动改,容易搞乱依赖关系。

保存后,执行编译:

make -j$(nproc)

-j 参数指定并行编译的线程数,$(nproc) 会自动获取你的 CPU 核心数。我第一次编译内核时不知道这个参数,单线程编译等了快两个小时……后来加了 -j8,十几分钟就搞定了。

3.6 本章小结

这一章我们做了三件事:下载内核源码 → 看懂目录树 → 用 menuconfig 配置内核。这三步是驱动开发的起点,也是你后续调试的基础。

记住几个关键点:

  • 源码从 kernel.org 下 LTS 版本
  • drivers/ 目录是驱动主战场
  • menuconfig 搜索功能是效率神器
  • 配置依赖关系搞不清楚时,先查 Kconfig 文件