2、内核源码结构解析:Linux内核源码目录结构、Tina Linux特有目录、Kconfig与Makefile基础
好,咱们正式开始动手之前,得先把战场看清楚。内核源码的目录结构,说白了就是一套庞大的积木系统。你如果不熟悉每块积木在哪、怎么拼,后面裁剪和适配根本无从下手。
我个人习惯,拿到一个新内核源码包,第一件事不是急着改代码,而是先跑一遍 tree -d -L 1,看看顶层目录长什么样。今天我就带你把这个目录结构彻底捋一遍。
2.1 标准Linux内核顶层目录
先看标准主线内核的顶层目录。这些目录几乎每个版本都有,是内核的骨架。
| 目录名 | 作用 | 我的一点经验 |
|---|---|---|
arch/ |
体系结构相关代码,如arm、arm64、x86、riscv | 裁剪时90%的改动都在这里 |
block/ |
块设备层核心代码 | 嵌入式一般不动,除非调IO调度 |
crypto/ |
加密API和算法实现 | 如果不用硬件加密,可以全关 |
drivers/ |
设备驱动,这是最大的目录 | 裁剪的主战场,没有之一 |
fs/ |
文件系统实现,如ext4、squashfs、overlayfs | 嵌入式常用squashfs和ubifs |
include/ |
内核头文件,分架构相关和通用 | 改这里要非常小心 |
init/ |
内核初始化流程,main.c就在这 | 启动流程调试时经常看 |
ipc/ |
进程间通信,如共享内存、信号量 | 轻量级系统可以精简 |
kernel/ |
核心调度、进程管理、中断 | 基本不动,除非调HZ值 |
lib/ |
内核通用库函数,如crc、rbtree | 裁剪时注意依赖关系 |
mm/ |
内存管理,页分配、slab、vmalloc | 小内存设备要关注 |
net/ |
网络协议栈,TCP/IP、无线、蓝牙 | 物联网设备裁剪重点 |
scripts/ |
编译脚本、Kconfig解析工具 | 很少改,但经常用 |
security/ |
安全模块,SELinux、AppArmor | 嵌入式一般直接关掉 |
sound/ |
音频子系统,ALSA、OSS | 按需保留codec驱动 |
usr/ |
initramfs打包相关 | 启动ramdisk时用到 |
arch/arm/boot/dts/ 和 drivers/ 下的 Makefile,基本就能摸清这个板子用了哪些外设。
2.2 Tina Linux 特有目录
Tina Linux 是全志基于 Linux 主线内核定制的 SDK。它除了标准内核目录外,还加了一些自己的东西。我第一次接触 Tina 时,就被它多出来的目录搞晕过。
下面这几个目录,是 Tina 的特色,也是你裁剪时经常要打交道的地方:
target/:存放各芯片平台的配置文件、分区表、打包脚本。裁剪内核前,先看这里有没有现成的 defconfig。package/:用户态软件包,类似 OpenWrt 的 feeds。内核模块的 .ko 文件有时也会以 package 形式编译。dl/:下载的源码包缓存目录。如果你离线编译,这里的东西别乱删。build/:编译中间产物和临时文件。我经常在这里找编译错误日志。out/:最终镜像输出目录。内核镜像、dtb、rootfs 都在这。lichee/:全志的 BSP 包,里面包含 bootloader、内核源码、工具链。裁剪内核时,你主要改lichee/linux-*/下的内容。
dl/ 目录下的某个包缓存,结果重新编译时它去网上重新下载,而我的开发机没联网…… 嗯,卡了我半天。所以离线环境千万别手贱。
2.3 Kconfig 与 Makefile 基础
内核的配置系统,核心就两个文件:Kconfig 和 Makefile。你想想看,整个内核几千个驱动,怎么决定哪些编译、哪些不编译?全靠这两个文件配合。
2.3.1 Kconfig:定义配置选项
Kconfig 文件定义了菜单和选项。每个驱动、每个子系统,都会有一个 Kconfig 文件。语法其实不复杂,我挑最常用的几种说:
# 最简单的布尔选项
config MY_DRIVER
bool "Support for My Driver"
default n
help
This is a custom driver for my hardware.
# 带依赖的选项
config MY_FEATURE
bool "Enable My Feature"
depends on MY_DRIVER
default y
# 三态选项(编译进内核/编译为模块/不编译)
config MY_MODULE
tristate "My Module Support"
depends on MY_DRIVER
default m
# 字符串选项
config MY_PARAM
string "My Parameter"
default "default_value"
这里有个坑:depends on 是硬依赖,如果父选项没选,子选项根本不会出现。我遇到过有人配了半天找不到选项,结果发现是依赖链断了。
2.3.2 Makefile:控制编译行为
Makefile 负责告诉编译器,哪些文件要编译,怎么编译。内核的 Makefile 有一套约定俗成的写法:
# 编译为 built-in(直接链接进内核)
obj-y += my_driver.o
# 编译为模块(生成 .ko 文件)
obj-m += my_module.o
# 条件编译
obj-$(CONFIG_MY_DRIVER) += my_driver.o
obj-$(CONFIG_MY_MODULE) += my_module.o
# 多文件模块
my_module-objs := file1.o file2.o file3.o
obj-$(CONFIG_MY_MODULE) += my_module.o
你看,obj-y 是强制编译进内核,obj-m 是编译成模块。而 obj-$(CONFIG_XXX) 这种写法,就是根据 Kconfig 的配置结果来决定是否编译。
CONFIG_ 前缀的宏来联动。
2.3.3 配置传播路径
配置是怎么从顶层传到每个子目录的?我画了一张图,你看完就明白了:
流程其实很简单:make menuconfig 读取各层 Kconfig,生成 .config 文件。然后 make 根据 .config 里的 CONFIG_* 变量,逐层解析 Makefile,决定哪些 .c 文件参与编译。
2.4 实战:如何快速定位一个驱动
说了这么多理论,来点实际的。假设你要找全志 R329 的 I2C 控制器驱动,怎么找?
- 先看芯片手册,确认 I2C 控制器挂在哪条总线上,寄存器基址多少。
- 进
drivers/i2c/busses/,搜sunxi或sun8i:
ls drivers/i2c/busses/ | grep -i sun - 看 Kconfig,确认依赖和选项名:
grep -A 10 "I2C_SUNXI" drivers/i2c/busses/Kconfig - 看 Makefile,确认编译方式:
grep "I2C_SUNXI" drivers/i2c/busses/Makefile - 打开 .config,确认当前是否已选中:
grep "CONFIG_I2C_SUNXI" .config
make menuconfig 的搜索功能(按 / 键),输入驱动名或芯片名,直接定位到 Kconfig 选项的位置。比手动翻目录快多了。
2.5 避坑指南
最后,分享几个我踩过的坑:
- Kconfig 缩进问题:Kconfig 用 Tab 缩进,但有些编辑器会把 Tab 转成空格。我曾经因为这个,一个菜单项死活显示不出来。排查了半天,最后发现是缩进不对。
- Makefile 的空格陷阱:
obj-y后面必须跟 Tab,不能是空格。否则编译时直接报错,而且错误信息很不直观。 - 依赖链断裂:你选了一个驱动,但它依赖的某个子系统没开,编译时会报 undefined reference。这时候别慌,回 Kconfig 里看
depends on链,一层层补上就行。 - .config 被覆盖:执行
make menuconfig时,如果不小心选了「Load an Alternate Configuration File」,可能会把之前的配置覆盖掉。我建议每次改配置前,先备份.config文件。
嗯,目录结构这块就讲这么多。你只要把顶层目录、Tina 特有目录、Kconfig/Makefile 的联动逻辑搞清楚了,后面裁剪内核就像庖丁解牛一样,知道刀往哪下。