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/ - 虚拟化
嗯,这里要注意,不是所有目录你都需要精通。我个人习惯是,先搞懂 arch、drivers、fs、include、kernel 这五个,其他的可以边用边学。
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/ - 显示/帧缓冲驱动
└── ...
每个子目录下,通常又分三层:
- 核心层:提供框架和API,比如
i2c-core.c - 总线层:管理总线协议,比如
i2c-bus.c - 设备层:具体硬件驱动,比如
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_MC 和 CONFIG_SCHED_SMT 优化了调度域,延迟降了30%。
3.7 知识体系结构图
下面这张SVG图,帮你理清这五个核心目录的关系:
核心要点:这五个目录构成了内核的骨架。你写驱动时,主要在 drivers/ 和 include/ 之间来回切换;做性能优化时,kernel/sched/ 和 arch/ 是重点关注对象;搞存储或容器时,fs/ 是绕不开的。
个人小技巧:我习惯用 find 命令快速定位源码。比如想找 schedule() 函数的定义:find . -name "*.c" -exec grep -l "schedule" {} \;。配合 cscope 或 ctags,效率更高。
避坑指南:我曾经在 drivers/ 里改了一个文件,结果编译报错说找不到头文件。查了半天,发现是 include/linux/ 下的某个头文件被 #ifdef 条件编译给屏蔽了。所以,改代码前,一定先看头文件里的宏定义条件。
好了,这五个目录的功能,你应该心里有数了。下次打开内核源码,别再一头雾水了。从 kernel/ 开始,顺着 include/ 的指引,慢慢深入 arch/、drivers/ 和 fs/,你会发现内核其实没那么神秘。