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 的配置结果来决定是否编译。

核心逻辑:Kconfig 定义「要不要」,Makefile 定义「怎么编」。两者通过 CONFIG_ 前缀的宏来联动。

2.3.3 配置传播路径

配置是怎么从顶层传到每个子目录的?我画了一张图,你看完就明白了:

Kconfig / Makefile 配置传播流程 顶层 Kconfig / Makefile arch/arm/Kconfig / drivers/Kconfig drivers/i2c/Kconfig / drivers/spi/Makefile drivers/i2c/busses/i2c-sunxi.c (obj-$(CONFIG_I2C_SUNXI)) vmlinux / .ko 模块 make menuconfig 生成 .config make 递归解析 Makefile

流程其实很简单:make menuconfig 读取各层 Kconfig,生成 .config 文件。然后 make 根据 .config 里的 CONFIG_* 变量,逐层解析 Makefile,决定哪些 .c 文件参与编译。

2.4 实战:如何快速定位一个驱动

说了这么多理论,来点实际的。假设你要找全志 R329 的 I2C 控制器驱动,怎么找?

  1. 先看芯片手册,确认 I2C 控制器挂在哪条总线上,寄存器基址多少。
  2. 进 drivers/i2c/busses/,搜 sunxi 或 sun8i:
    ls drivers/i2c/busses/ | grep -i sun
  3. 看 Kconfig,确认依赖和选项名:
    grep -A 10 "I2C_SUNXI" drivers/i2c/busses/Kconfig
  4. 看 Makefile,确认编译方式:
    grep "I2C_SUNXI" drivers/i2c/busses/Makefile
  5. 打开 .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 的联动逻辑搞清楚了,后面裁剪内核就像庖丁解牛一样,知道刀往哪下。