第四章:Linux内核源码结构
说实话,我第一次接触Linux内核源码时,整个人是懵的。几万个文件,上千万行代码,从哪看起?后来跟着前辈做了几个项目,才慢慢摸出门道。今天我就把这套经验分享给你。
4.1 内核源码目录结构
先说说内核源码的顶层目录。你下载一份内核源码,解压后看到的那些文件夹,每个都有它的使命。
| 目录 | 作用 | 我的经验 |
|---|---|---|
| arch/ | 体系结构相关代码 | 做ARM移植时,我大部分时间泡在arch/arm/里 |
| drivers/ | 设备驱动 | 这是最大的目录,占了内核一半以上的代码 |
| fs/ | 文件系统实现 | ext4、vfat、yaffs2都在这里 |
| kernel/ | 核心调度、进程管理 | 一般不动,除非你在做内核调优 |
| mm/ | 内存管理 | 我调试OOM问题时经常翻这里 |
| net/ | 网络协议栈 | TCP/IP、socket实现 |
| include/ | 头文件 | 找宏定义、结构体定义的好地方 |
| scripts/ | 编译脚本、工具 | Kconfig解析器就在这里 |
| Documentation/ | 内核文档 | 别小看它,很多坑文档里都写了 |
嗯,这里有个小技巧。你如果想知道某个功能在内核里怎么实现的,先看Documentation/目录。我当年调试一个USB摄像头驱动,折腾了两天没搞定,结果在Documentation里找到了一个配置说明,五分钟解决问题。
4.2 Kconfig与Makefile
内核的配置系统,说白了就是Kconfig和Makefile这两兄弟。Kconfig负责「你要不要这个功能」,Makefile负责「怎么编译这个功能」。
4.2.1 Kconfig语法
Kconfig文件定义了内核的配置选项。每个选项都是一个菜单项,你可以选y(编译进内核)、m(编译成模块)或者n(不编译)。
config CONFIG_GPIO_SYSFS
bool "GPIO sysfs interface"
default y
help
这个选项会在/sys/class/gpio下创建接口
方便用户空间操作GPIO
我曾经犯过一个错:把某个驱动设成了y,但它依赖的另一个驱动却是m。结果编译出来内核启动时,驱动加载顺序乱了,系统直接挂掉。所以记住:依赖关系一定要理清楚。
4.2.2 Makefile写法
内核的Makefile比普通项目复杂得多。但核心就一句话:告诉编译器哪些文件要编译,怎么编译。
obj-y += mydriver.o # 编译进内核
obj-m += mydriver.o # 编译成模块
obj-$(CONFIG_MY_DRIVER) += mydriver.o # 根据配置决定
ccflags-y可以加编译选项,ldflags-y可以加链接选项。
4.3 内核配置选项
配置内核有几种方式,我一个个说。
- make menuconfig:我最常用的方式,基于ncurses的图形界面
- make xconfig:基于Qt的图形界面,功能更全
- make defconfig:用默认配置,适合快速上手
- make oldconfig:升级内核时用,保留旧配置
你想想看,一个嵌入式设备,Flash空间就那么几兆。如果内核里塞了一堆用不上的驱动,不仅浪费空间,还拖慢启动速度。所以配置内核时,我的原则是:只选你需要的。
举个例子。我做的一个智能家居网关,只需要GPIO、I2C、SPI和WiFi。其他像SCSI、RAID、光纤通道这些,统统关掉。最后内核镜像从4.2MB压缩到了1.1MB。
4.4 编译内核
编译内核的流程,说白了就三步:配置、编译、安装。
4.4.1 基本编译命令
# 配置
make menuconfig
# 编译内核
make -j4
# 编译设备树
make dtbs
# 编译模块
make modules
# 安装模块
make modules_install INSTALL_MOD_PATH=/path/to/rootfs
-j4这个参数,我建议你根据CPU核心数来设。4核CPU就用-j4,8核就用-j8。设太大反而会因为IO瓶颈变慢,这是我实测过的。
4.4.2 交叉编译
嵌入式开发嘛,肯定是在PC上编译,在目标板上运行。这时候就要用交叉编译工具链。
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
make menuconfig
make -j4
我第一次做交叉编译时,忘了设ARCH变量,结果编译出来的还是x86的内核。放到ARM板上当然跑不起来。嗯,这种低级错误,犯过一次就记住了。
4.4.3 编译优化技巧
这里分享几个我常用的优化方法:
- 使用ccache:第一次编译慢,但后续增量编译快很多
- 只编译修改的部分:用
make而不是make clean; make - 保存配置文件:把
.config备份好,下次直接cp过去
make kernelversion确认当前源码版本。我遇到过因为源码版本搞错,编译出来的内核和根文件系统不匹配,折腾了一整天。
知识体系总览
下面这张图,是我自己总结的内核源码结构关系。你看一遍,心里就有谱了。
这张图把内核源码结构、配置系统、编译系统串起来了。你从上往下看,先有目录结构,然后通过Kconfig和Makefile组织代码,最后编译出内核镜像。说白了,整个流程就是:源码 → 配置 → 编译 → 镜像。
我刚开始学的时候,总觉得这些概念是孤立的。后来做项目多了才发现,它们其实是一环扣一环的。你改了一个Kconfig选项,可能影响好几个目录的编译结果。所以,理解这个整体结构,比死记硬背每个目录的作用重要得多。
- arch/和drivers/是嵌入式开发最常打交道的目录
- Kconfig决定「要不要」,Makefile决定「怎么编」
- 配置内核时,只选你需要的功能,别贪多
- 交叉编译时,别忘了设ARCH和CROSS_COMPILE
好了,这一章的内容就这些。记住我上面说的那些坑,你编译内核时会少走很多弯路。