第三章 Linux内核基础:内核源码结构、内核配置系统与编译
好,咱们进入正题。这一章讲的是Linux内核的基础,说白了就是你要先搞清楚内核这玩意儿长什么样,怎么配置,怎么编译。我在高通平台上调驱动这么多年,发现很多新人上来就写代码,结果连内核目录是干嘛的都不知道,编译报错更是两眼一抹黑。嗯,咱们先把地基打牢。
3.1 内核源码结构:别迷路
你下载完Linux内核源码,解压后第一眼看到一堆目录,别慌。我个人的习惯是,先看顶层目录,记住几个关键的:
- arch/:架构相关代码。比如arch/arm、arch/arm64、arch/x86。高通芯片基本都是arm64,所以你大部分时间会泡在arch/arm64里。
- drivers/:驱动代码。这是咱们的主战场。里面按设备类型分,比如i2c、spi、gpio、usb、net等等。高通平台很多外设驱动都在这里。
- include/:头文件。内核API、数据结构定义都在这里。include/linux/是通用头文件,include/asm-arm64/是架构相关的。
- kernel/:内核核心代码,比如进程调度、中断管理。这部分咱们一般不动,但得知道它在哪。
- mm/:内存管理。写驱动时偶尔会涉及,比如dma内存分配。
- Documentation/:文档。说实话,有些文档写得比代码还难懂,但遇到不懂的宏或者API,先来这里翻翻。
3.2 内核配置系统:Kconfig与Makefile
配置内核,说白了就是告诉编译器:我要哪些功能,不要哪些功能。高通平台的内核配置尤其复杂,因为芯片集成的外设太多了。
3.2.1 Kconfig:菜单是怎么来的
Kconfig文件定义了配置选项。比如你在drivers/i2c/Kconfig里会看到:
config I2C_QUP
tristate "Qualcomm Universal Peripheral (QUP) I2C controller"
depends on ARCH_QCOM
help
Say Y here to enable the QUP I2C controller driver.
This driver can also be built as a module.
这里有几个关键点:
- tristate:三态,Y(编译进内核)、M(编译成模块)、N(不编译)。
- depends on:依赖关系。比如这个驱动依赖ARCH_QCOM,如果你没选高通平台,这个选项就不会出现。
- help:帮助信息。我建议你养成看help的习惯,里面经常有硬件配置的提示。
3.2.2 Makefile:编译规则
Makefile告诉编译器怎么编译。还是拿I2C驱动举例,在drivers/i2c/Makefile里:
obj-$(CONFIG_I2C_QUP) += i2c-qup.o
这行代码的意思是:如果CONFIG_I2C_QUP被配置为Y或M,就编译i2c-qup.c。如果配置为N,就不编译。
你可能会问:为什么是obj-y、obj-m?
- obj-y:编译进内核镜像。
- obj-m:编译成独立的.ko模块。
- obj-n:不编译。
高通平台经常会有多级目录,比如:
obj-$(CONFIG_MSM_VIDC) += msm/
obj-$(CONFIG_MSM_VIDC) += msm/vidc/
这种写法表示如果配置了MSM_VIDC,就递归进入msm/和msm/vidc/目录编译。
3.3 内核编译与模块编译
好了,配置写完了,怎么编译?这里我分两种情况讲:编译整个内核,和单独编译一个模块。
3.3.1 编译整个内核
高通平台通常用这个流程:
# 1. 配置内核
make ARCH=arm64 defconfig # 使用默认配置
# 或者
make ARCH=arm64 menuconfig # 图形化配置界面
# 2. 编译内核
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8
# 3. 编译设备树
make ARCH=arm64 dtbs
这里要注意:
- CROSS_COMPILE:交叉编译工具链前缀。高通平台一般用aarch64-linux-gnu-或者arm-linux-gnueabihf-。
- -j8:并行编译,8个线程。根据你电脑CPU核数来设,别设太大,否则电脑会卡死。我吃过这个亏,一次设了-j32,结果编译到一半内存爆了。
3.3.2 单独编译模块
开发驱动时,你不可能每次都编译整个内核。太慢了。单独编译模块的方法:
# 进入驱动目录
cd drivers/i2c/
# 编译单个模块
make -C /path/to/kernel M=$(pwd) modules
# 或者指定模块
make -C /path/to/kernel M=$(pwd) modules CONFIG_I2C_QUP=m
这里-C指定内核源码路径,M指定模块源码路径。编译出来的.ko文件就在当前目录。
#!/bin/bash
make -C ~/kernel/msm-4.19 M=$(pwd) modules ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-这样每次改完代码,直接./build.sh就行,省得敲长命令。
3.3.3 模块加载与卸载
编译出.ko文件后,怎么用?
# 加载模块
insmod i2c-qup.ko
# 查看模块信息
modinfo i2c-qup.ko
# 卸载模块
rmmod i2c-qup
# 查看已加载模块
lsmod | grep i2c
这里有个坑:insmod和modprobe的区别。insmod是直接加载,不处理依赖。modprobe会自动加载依赖模块。高通平台驱动依赖关系复杂,我建议你用modprobe。
3.4 高通平台的特殊之处
高通的内核和主线Linux内核有些区别。我总结几点:
- 设备树覆盖:高通平台经常用设备树覆盖(DT overlay)机制。比如一个芯片支持多个硬件版本,通过不同的.dts overlay文件来适配。
- 私有模块:drivers/soc/qcom/目录下有很多高通私有的驱动,比如smem、smp2p、glink。这些是芯片间通信的基础,写驱动时经常要调用它们的API。
- 编译脚本:高通有自己的编译脚本,比如build/目录下的脚本。我建议你直接用高通的编译脚本,别自己写Makefile,否则很容易漏掉一些私有头文件路径。
嗯,这一章的内容就这些。说白了,内核源码结构就是让你知道东西在哪,Kconfig/Makefile是配置和编译的规则,编译命令是实际操作。把这些搞明白,后面写驱动时就不会一头雾水了。
下一章咱们开始讲真正的驱动开发——字符设备驱动。到时候我会拿一个高通平台的GPIO驱动做例子,手把手带你走一遍。