第四章: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里找到了一个配置说明,五分钟解决问题。

我的习惯:拿到一个新内核版本,先看arch/和drivers/这两个目录。它们变化最快,也最影响你的嵌入式项目。

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  # 根据配置决定
避坑指南:我曾经在Makefile里写错了依赖关系,结果每次修改一个.c文件,整个内核都要重新编译。后来发现是忘了加正确的依赖规则。记住: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。

注意:别为了省空间把关键功能关了。我有个同事把printk关了,结果系统崩溃时连个日志都看不到,调试了整整一周才发现是内存越界。

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确认当前源码版本。我遇到过因为源码版本搞错,编译出来的内核和根文件系统不匹配,折腾了一整天。

知识体系总览

下面这张图,是我自己总结的内核源码结构关系。你看一遍,心里就有谱了。

Linux内核源码结构知识体系 内核源码顶层目录 Kconfig配置系统 Makefile编译系统 配置选项管理 Kconfig语法 config/tristate/bool depends on / select default / help Makefile规则 obj-y / obj-m / obj-$(CONFIG) ccflags-y / ldflags-y 依赖关系管理 配置方法 menuconfig / xconfig defconfig / oldconfig .config文件管理 编译输出:zImage / uImage / dtb / modules

这张图把内核源码结构、配置系统、编译系统串起来了。你从上往下看,先有目录结构,然后通过Kconfig和Makefile组织代码,最后编译出内核镜像。说白了,整个流程就是:源码 → 配置 → 编译 → 镜像。

我刚开始学的时候,总觉得这些概念是孤立的。后来做项目多了才发现,它们其实是一环扣一环的。你改了一个Kconfig选项,可能影响好几个目录的编译结果。所以,理解这个整体结构,比死记硬背每个目录的作用重要得多。

核心要点:
  • arch/和drivers/是嵌入式开发最常打交道的目录
  • Kconfig决定「要不要」,Makefile决定「怎么编」
  • 配置内核时,只选你需要的功能,别贪多
  • 交叉编译时,别忘了设ARCH和CROSS_COMPILE

好了,这一章的内容就这些。记住我上面说的那些坑,你编译内核时会少走很多弯路。

可可精品资料库