3、Kernel源码结构解析:arch、drivers、fs、include、kernel等核心目录功能

说实话,很多初学者第一次打开Linux内核源码,看到那一堆目录,直接就懵了。我当年也一样,面对几万个文件,完全不知道从哪下手。其实内核的目录结构设计得非常清晰,每个目录都有它明确的职责。今天我就带你走一遍,把几个核心目录的功能讲透。

3.1 顶层目录概览

先看根目录下都有什么。你执行 ls -l /usr/src/linux/,会看到大概这么几个关键目录:

arch/       - 架构相关代码
block/      - 块设备层
crypto/     - 加密子系统
Documentation/ - 文档
drivers/    - 设备驱动
fs/         - 文件系统
include/    - 头文件
init/       - 初始化代码
ipc/        - 进程间通信
kernel/     - 核心内核代码
lib/        - 库函数
mm/         - 内存管理
net/        - 网络协议栈
scripts/    - 编译脚本
security/   - 安全模块
sound/      - 音频子系统
tools/      - 工具
usr/        - 用户空间初始化
virt/       - 虚拟化

嗯,这里要注意,不是所有目录你都需要精通。我个人习惯是,先搞懂 archdriversfsincludekernel 这五个,其他的可以边用边学。

3.2 arch/:架构的灵魂

arch/ 目录,说白了就是内核的「方言」部分。不同CPU架构有不同的指令集、内存布局、中断处理方式。这部分代码是平台相关的,移植内核到新硬件时,主要就是改这里。

你想想看,x86和ARM的启动流程能一样吗?x86有BIOS/UEFI,ARM有BootROM。所以 arch/ 下面按架构分:

arch/x86/    - Intel/AMD 32/64位
arch/arm/    - ARM 32位
arch/arm64/  - ARM 64位 (AArch64)
arch/riscv/  - RISC-V
arch/mips/   - MIPS
arch/powerpc/- PowerPC
...

每个架构目录下,结构又高度相似。以 arch/x86/ 为例:

子目录 功能
boot/ 启动代码,包括实模式下的引导
kernel/ 架构相关的核心代码,如中断、信号、时间
mm/ 内存管理,如页表、TLB、ioremap
lib/ 架构优化的库函数,如memcpy、strlen
pci/ PCI/PCIe总线相关
power/ 电源管理,如CPU idle、cpufreq

我在项目中遇到过一个问题:在ARM64上跑一个x86移植过来的驱动,结果内存映射总是不对。查了半天,发现是 arch/arm64/include/asm/io.h 里的 readl/writel 实现和x86不一样。这就是架构差异的典型坑。

3.3 drivers/:驱动的大杂烩

drivers/ 是内核里最大的目录,没有之一。它占了整个内核代码量的60%以上。你想想看,从键盘鼠标到显卡网卡,从I2C传感器到USB设备,所有驱动都在这里。

它的组织方式是按设备类型分的:

drivers/
├── gpio/       - GPIO控制器驱动
├── i2c/        - I2C总线及设备驱动
├── input/      - 输入设备(键盘、鼠标、触摸屏)
├── media/      - 多媒体设备(摄像头、视频编解码)
├── net/        - 网络设备(以太网、无线网卡)
├── pci/        - PCI设备驱动框架
├── platform/   - 平台设备(非可枚举设备)
├── spi/        - SPI总线驱动
├── tty/        - 终端设备(串口、虚拟终端)
├── usb/        - USB主机/设备/集线器驱动
├── video/      - 显示/帧缓冲驱动
└── ...

每个子目录下,通常又分三层:

  1. 核心层:提供框架和API,比如 i2c-core.c
  2. 总线层:管理总线协议,比如 i2c-bus.c
  3. 设备层:具体硬件驱动,比如 eeprom.c

我曾经调试过一个USB网卡驱动,死活识别不到设备。后来发现是 drivers/usb/core/ 里的 hub.c 在枚举时超时了。嗯,这种问题,不看源码根本想不到。

3.4 fs/:文件系统的百宝箱

fs/ 目录,是内核里最「抽象」也最「具体」的部分。抽象是因为它定义了VFS(虚拟文件系统)层,具体是因为它实现了各种实际的文件系统。

VFS层在 fs/ 根目录下:

fs/
├── super.c     - 超级块操作
├── inode.c     - 索引节点操作
├── dentry.c    - 目录项缓存
├── file.c      - 文件操作
├── open.c      - open/close系统调用
├── read_write.c- read/write系统调用
├── namespace.c - 挂载命名空间
└── ...

具体文件系统在子目录里:

子目录 文件系统
ext4/ Linux标准日志文件系统
btrfs/ 写时复制文件系统
xfs/ 高性能64位日志文件系统
proc/ 进程信息虚拟文件系统
sysfs/ 设备模型虚拟文件系统
nfs/ 网络文件系统
overlayfs/ 联合挂载文件系统(Docker用)

你想想看,为什么 /proc/cpuinfo 能实时显示CPU信息?因为 fs/proc/ 里的代码,每次读文件时都会重新采集数据。这就是虚拟文件系统的精髓——文件内容不是静态的,而是动态生成的。

3.5 include/:头文件的枢纽

include/ 目录,是内核所有头文件的大本营。它分为两部分:

include/
├── linux/      - 通用内核头文件
├── asm-generic/- 架构无关的汇编头文件
├── asm-xxx/    - 架构相关的头文件(编译时链接)
├── uapi/       - 用户空间API头文件
├── net/        - 网络子系统头文件
├── sound/      - 音频子系统头文件
├── video/      - 视频子系统头文件
└── ...

这里有个关键点:asm-xxx/ 目录在编译时会被链接成 asm/。比如你写 #include <asm/io.h>,实际编译时,ARM平台会去 include/asm-arm/io.h,x86平台会去 include/asm-x86/io.h。这个机制是通过Makefile里的符号链接实现的。

我个人习惯是,写驱动时先看 include/linux/ 下的头文件。比如你想用GPIO,就看 include/linux/gpio.h;想用中断,就看 include/linux/interrupt.h。这些头文件里定义了所有核心API的原型。

3.6 kernel/:内核的心脏

kernel/ 目录,是内核最核心的部分。它包含了进程管理、调度、信号、时间、模块、锁等基础机制。

kernel/
├── sched/      - 调度器(CFS、RT、Deadline)
├── locking/    - 锁机制(spinlock、mutex、rwlock)
├── power/      - 电源管理(suspend、resume)
├── irq/        - 中断处理
├── time/       - 时间管理(timer、hrtimer)
├── module.c    - 内核模块加载/卸载
├── fork.c      - 进程创建(fork/vfork/clone)
├── exit.c      - 进程退出
├── signal.c    - 信号处理
├── sys.c       - 系统调用入口
├── workqueue.c - 工作队列
└── ...

举个例子,kernel/sched/core.c 里实现了调度器的主循环。每次时钟中断触发时,都会调用 schedule() 函数,决定下一个该运行哪个进程。这部分代码,是性能优化的重中之重。

我曾经优化过一个实时系统,发现任务切换延迟很高。用ftrace一跟踪,发现是 kernel/sched/core.c 里的 context_switch() 函数在切换MMU页表时花了太多时间。后来通过 CONFIG_SCHED_MCCONFIG_SCHED_SMT 优化了调度域,延迟降了30%。

3.7 知识体系结构图

下面这张SVG图,帮你理清这五个核心目录的关系:

Kernel源码核心目录结构 Linux Kernel arch/ drivers/ fs/ include/ kernel/ 架构相关 • 启动引导 • 中断处理 • 内存页表 • 汇编优化 设备驱动 • 字符/块设备 • 总线协议 • 网络设备 • 平台设备 文件系统 • VFS层 • ext4/btrfs/xfs • proc/sysfs • 网络文件系统 头文件 • 内核API声明 • 架构抽象层 • 用户空间接口 • 数据结构定义 核心内核 • 进程调度 • 锁与同步 • 时间管理 • 系统调用 💡 学习建议 先通读 kernel/ 和 include/ 建立基础认知 再深入 arch/ 理解平台差异,最后啃 drivers/ 和 fs/

核心要点:这五个目录构成了内核的骨架。你写驱动时,主要在 drivers/include/ 之间来回切换;做性能优化时,kernel/sched/arch/ 是重点关注对象;搞存储或容器时,fs/ 是绕不开的。

个人小技巧:我习惯用 find 命令快速定位源码。比如想找 schedule() 函数的定义:find . -name "*.c" -exec grep -l "schedule" {} \;。配合 cscopectags,效率更高。

避坑指南:我曾经在 drivers/ 里改了一个文件,结果编译报错说找不到头文件。查了半天,发现是 include/linux/ 下的某个头文件被 #ifdef 条件编译给屏蔽了。所以,改代码前,一定先看头文件里的宏定义条件。

好了,这五个目录的功能,你应该心里有数了。下次打开内核源码,别再一头雾水了。从 kernel/ 开始,顺着 include/ 的指引,慢慢深入 arch/drivers/fs/,你会发现内核其实没那么神秘。

专注资料整理