第三章:Linux内核源码结构——内核源码目录树、Kconfig与Makefile、Exynos平台代码位置
各位同学,大家好。这一章我们聊聊Linux内核源码的结构。说实话,很多初学者一看到内核源码树就头大——几万个文件,几十个目录,从哪看起?我当年刚接触内核时也是这样,对着linux-3.5的目录发呆了好久。
但别怕。内核源码虽然庞大,但它的组织逻辑其实很清晰。说白了,就是一套「分层 + 模块化」的架构。你只要抓住几个关键目录,就能快速定位到你关心的代码。
3.1 内核源码目录树:从根目录说起
我们先从顶层看起。你下载一份内核源码,解压后第一眼看到的就是这些目录:
linux-5.10/
├── arch/ # 体系结构相关代码
├── block/ # 块设备子系统
├── crypto/ # 加密API
├── Documentation/ # 内核文档
├── drivers/ # 设备驱动
├── fs/ # 文件系统
├── include/ # 头文件
├── init/ # 内核初始化
├── ipc/ # 进程间通信
├── kernel/ # 内核核心
├── lib/ # 库函数
├── mm/ # 内存管理
├── net/ # 网络协议栈
├── scripts/ # 编译脚本
├── security/ # 安全模块
├── sound/ # 音频子系统
├── tools/ # 用户空间工具
└── usr/ # initramfs相关
嗯,这里我要强调一下:arch/ 和 drivers/ 这两个目录,是我们嵌入式驱动开发人员最常打交道的。为什么呢?
- arch/:存放与CPU架构相关的代码。ARM、x86、RISC-V、MIPS等都在这里。Exynos平台属于ARM架构,所以它的平台代码在
arch/arm/下。 - drivers/:存放各种外设驱动。I2C、SPI、GPIO、DMA、时钟、电源管理等,都在这里按子系统分类。
- include/:内核头文件。你写驱动时经常要包含
#include <linux/xxx.h>,这些头文件就来自这里。
我个人习惯是,拿到一个新内核版本,先看 arch/ 和 drivers/ 这两个目录。其他的如 kernel/、mm/、fs/ 等,除非你要做内核核心开发,否则平时接触不多。
find 命令搜索:find . -name "*exynos*" -type f。这样能快速定位所有相关文件。
3.2 Kconfig与Makefile:内核的「配置-编译」体系
内核的配置和编译,靠的就是 Kconfig 和 Makefile 这两兄弟。我刚开始写驱动时,经常搞不清它们的关系。后来踩过几次坑,才真正理解。
Kconfig 负责「配置」——它定义了哪些功能可以开关,哪些驱动可以选。你运行 make menuconfig 时看到的菜单,就是由各个目录下的 Kconfig 文件生成的。
Makefile 负责「编译」——它根据 Kconfig 的配置结果(保存在 .config 文件中),决定哪些文件要编译进内核,哪些编译成模块。
举个例子,假设我们要在 Exynos 平台上添加一个 GPIO 按键驱动。Kconfig 和 Makefile 的写法是这样的:
Kconfig 示例
# drivers/input/keyboard/Kconfig
config KEYBOARD_EXYNOS_GPIO
tristate "Exynos GPIO Keypad support"
depends on ARCH_EXYNOS
help
Say Y here to enable GPIO-based keypad support for Exynos SoCs.
This driver supports up to 8x8 matrix keypad.
这里 tristate 表示该驱动有三种状态:Y(编译进内核)、M(编译成模块)、N(不编译)。depends on ARCH_EXYNOS 表示这个驱动只对 Exynos 平台可见——你选别的平台,这个选项根本不会出现。
Makefile 示例
# drivers/input/keyboard/Makefile
obj-$(CONFIG_KEYBOARD_EXYNOS_GPIO) += exynos_gpio_keys.o
这一行的意思是:如果 CONFIG_KEYBOARD_EXYNOS_GPIO 被配置为 y,就把 exynos_gpio_keys.o 编译进内核;如果配置为 m,就编译成模块 exynos_gpio_keys.ko。
obj-$(CONFIG_XXX) 这一行。结果配置菜单里能看到选项,编译时却找不到源文件。嗯,这种问题排查起来还挺费时间的。
3.3 Exynos平台代码位置:arch/arm/mach-exynos
好了,终于到了我们最关心的部分。Exynos 平台的代码主要分布在两个地方:
| 目录 | 内容 | 说明 |
|---|---|---|
arch/arm/mach-exynos/ |
平台初始化代码 | 时钟、中断、GPIO、DMA等外设的初始化 |
arch/arm/boot/dts/ |
设备树文件 | 硬件描述,如 exynos4412.dtsi |
drivers/ 下各子系统 |
具体外设驱动 | 如 drivers/clk/samsung/ 下的时钟驱动 |
我们重点看 arch/arm/mach-exynos/ 这个目录。它里面通常包含以下文件:
arch/arm/mach-exynos/
├── common.c # 通用初始化代码
├── pm.c # 电源管理
├── sleep.S # 休眠/唤醒汇编代码
├── platsmp.c # 多核启动
├── headsmp.S # 多核启动汇编
├── hotplug.c # CPU热插拔
└── Kconfig # 平台配置选项
我记得第一次看 common.c 时,里面一堆 map_io、init_early、init_irq 之类的回调函数。说白了,这些就是内核在启动过程中,针对 Exynos 平台做的「定制化初始化」。
举个例子,Exynos 4412 的时钟初始化通常在 common.c 中调用:
// arch/arm/mach-exynos/common.c
static void __init exynos_init_early(void)
{
exynos_map_io();
exynos_init_clocks();
exynos_init_uarts();
}
这里 exynos_init_clocks() 会去配置 PLL、设置各外设的时钟频率。你想想看,如果时钟配错了,整个系统可能连串口都打不开——我就在调试一个 I2C 驱动时遇到过,时钟频率不对导致通信时序全乱,折腾了两天才发现是平台初始化代码里一个宏定义错了。
mach-exynos 中的大量板级代码。如果你用的是较新版本的内核,mach-exynos/ 目录下的内容会少很多,大部分硬件描述都移到了 .dts 文件中。但 mach-exynos/ 仍然负责平台级别的初始化,比如 SMP 启动、电源管理等。
3.4 实战:如何找到Exynos 4412的GPIO控制代码
光说不练假把式。我们来做个小实战——找到 Exynos 4412 的 GPIO 控制代码。
- 先找设备树:
arch/arm/boot/dts/exynos4412.dtsi。这里面定义了 GPIO 控制器的寄存器基地址、中断号等。 - 再找驱动:GPIO 驱动在
drivers/gpio/gpio-samsung.c。这个文件包含了 Exynos 全系列 SoC 的 GPIO 操作函数。 - 最后看平台初始化:
arch/arm/mach-exynos/common.c中会调用samsung_gpio_init()来注册 GPIO 控制器。
你看,通过这三个步骤,就能把「硬件描述 → 驱动实现 → 平台注册」这条链路串起来。我建议你拿到一块开发板后,也按这个思路去梳理——先找设备树,再找驱动,最后看平台初始化。这样整个代码脉络就清晰了。
arch/arm/mach-exynos/ 和 drivers/ 之间来回跳转。比如发现某个外设不工作,先看设备树里寄存器地址对不对,再看驱动里操作流程对不对,最后看平台初始化有没有遗漏。这三者缺一不可。
好了,这一章的内容就到这里。下一章我们会深入 Exynos 的时钟框架,看看 PLL 是怎么配置的,以及如何为你的外设驱动提供正确的时钟。到时候我会分享一个我调试 HDMI 时钟时遇到的坑——嗯,那个故事还挺有意思的。