# 为什么编译 Linux 内核模块前,通常要先编译一下 Linux 内核
在学习 Linux 驱动开发时,很多教程都会强调:
> 在编译内核模块之前,最好先把 Linux 内核先配置并编译一遍。
很多初学者会疑惑:
– 我只是想编一个 `.ko` 模块,为什么还要先编内核?
– 模块不是一个单独的源文件吗,直接 `gcc` 不行吗?
– 为什么不先编内核,模块有时就编不过,或者能编过但加载失败?
本文就系统说明这个问题。
—
## 1. 内核模块不是普通程序
Linux 内核模块(`.ko`)并不是普通的用户态程序,它不能像下面这样简单编译:
“`bash
gcc hello.c -o hello
“`
原因在于:
1. **内核模块运行在内核空间**,不是用户空间
2. **模块依赖 Linux 内核自己的头文件、配置和构建规则**
3. **模块最终要被加载进某个特定版本的内核中运行**
也就是说,内核模块本质上是:
> **某个特定 Linux 内核的二进制扩展**
所以它不能脱离目标内核独立编译。
—
## 2. 编译内核模块为什么要依赖内核源码树
编译外部模块时,通常会写这样的 Makefile:
“`makefile
obj-m := hello.o
KDIR := /path/to/linux-kernel
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
“`
这里最关键的是:
“`makefile
$(MAKE) -C $(KDIR) M=$(PWD) modules
“`
意思不是“直接编译当前目录的 `hello.c`”,而是:
> **进入 Linux 内核源码树,借助内核自己的 Kbuild 系统来编译这个模块。**
所以,编译模块本质上是“借助内核的构建系统进行编译”,而不是模块自己单独完成编译。
—
## 3. 为什么通常要先编译一下内核
### 3.1 需要内核配置文件 `.config`
Linux 内核是高度可配置的,很多功能是否开启,都由 `.config` 决定。
例如:
– 某个驱动子系统是否启用
– 某些结构体成员是否存在
– 某些宏是否被定义
– 某些接口是否可用
这些内容都会影响模块编译。
如果内核还没有经过:
“`bash
make xxx_defconfig
“`
那么 `.config` 可能不存在,模块编译环境就不完整。
所以模块编译首先依赖:
> **目标内核必须先有正确的配置。**
—
### 3.2 需要自动生成的头文件
内核源码里有些头文件不是源码自带的,而是在配置或编译过程中自动生成的,比如:
– `include/generated/autoconf.h`
– `include/generated/utsrelease.h`
– `include/generated/uapi/linux/version.h`
这些文件里包含:
– 内核配置宏
– 内核版本信息
– 编译相关定义
模块在编译时会间接依赖这些文件。
如果内核还没有先配置、先准备,这些文件可能根本不存在,于是就会出现类似报错:
“`bash
fatal error: include/generated/autoconf.h: No such file or directory
“`
因此,先编译或至少先准备内核,是为了让这些**自动生成头文件**先就绪。
—
### 3.3 需要 Kbuild 和 scripts 工具链环境
Linux 内核模块不是普通 Makefile 能完全处理的,它依赖内核自己的构建系统,也就是 **Kbuild**。
在模块编译过程中,往往会使用到内核源码树里的:
– `scripts/` 目录下的工具
– `Makefile` 和 `Kbuild` 规则
– `modpost` 等辅助程序
这些工具和规则很多都需要先经过内核配置或编译准备。
也就是说,先编内核的一个重要原因是:
> **把内核模块构建所需要的脚本和构建环境准备好。**
—
### 3.4 需要 `Module.symvers` 符号版本信息
这是一个非常关键的原因。
内核模块在编译和加载时,经常要引用内核已经导出的符号,比如:
– `printk`
– `kmalloc`
– `gpio_request`
– `platform_driver_register`
这些符号不只是“名字存在”就够了,还可能涉及**符号版本匹配**。
内核编译过程中会生成一个重要文件:
“`bash
Module.symvers
“`
它记录了内核导出符号以及对应的版本信息。
如果没有这个文件,或者它和目标内核不一致,常见问题包括:
– `modpost` 报错
– 模块编译警告很多
– `insmod` 时失败
– 报 `invalid module format`
– 报符号版本不匹配
所以很多教程让你先编一下内核,核心目的之一就是生成并同步:
> **`Module.symvers` 和内核导出符号信息**
—
## 4. 为什么模块必须和目标内核严格匹配
内核模块最终是要插入到“正在运行的内核”中的,因此它必须和目标内核匹配。
这种匹配包括:
– **内核版本匹配**
– **架构匹配**
– **配置匹配**
– **编译器和 ABI 匹配**
– **导出符号版本匹配**
例如:
– 你的开发板运行的是 `Linux 4.1.15`
– 你的模块却拿 `Linux 5.x` 的头文件去编
– 或者虽然都是 `4.1.15`,但 `.config` 不同
那么即使模块能编出来,也可能在加载时失败:
“`bash
insmod xxx.ko
insmod: ERROR: could not insert module xxx.ko: Invalid module format
“`
所以先准备或先编译内核,本质上是为了保证:
> **模块是针对这个目标内核编译出来的。**
—
## 5. 是不是一定要把整个内核完整编译完?
不一定。
这是一个很容易被误解的地方。
很多人以为“先编译内核”就是一定要先把:
– `zImage`
– `uImage`
– `dtbs`
– 所有内建驱动
全部编译完成之后,才能编模块。
其实不完全是这样。
更准确的说法应该是:
> **在编译模块之前,必须先把内核源码树准备到可用于模块构建的状态。**
这个“准备到位”通常至少包括:
1. 已经有正确的 `.config`
2. 已经生成了 `include/generated/` 下的自动头文件
3. 已经具备 Kbuild 所需的脚本和构建工具
4. 最好已经有 `Module.symvers`
—
## 6. `make modules_prepare` 的作用
如果你只是为了编译外部模块,而不是完整编整个内核,那么常见做法是:
“`bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- xxx_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_prepare
“`
其中:
### `make xxx_defconfig`
用于生成目标平台默认配置,即生成 `.config`
### `make modules_prepare`
用于为“外部模块编译”准备环境,主要包括:
– 生成必要的头文件
– 准备 Kbuild 所需环境
– 准备 `scripts/` 里的相关工具
所以,从严格意义上说:
> **编译模块前不一定非要完整编译整个内核,但至少要先执行内核配置,并通常要执行 `modules_prepare`。**
—
## 7. 为什么很多教程还是建议“先完整编一下内核”
虽然理论上 `modules_prepare` 就能做很多准备工作,但教程里仍然经常建议:
> **先把内核完整编一遍,再去编模块。**
原因主要有以下几点。
### 7.1 最省事,最稳
完整编一次内核之后,通常以下内容都会准备齐全:
– `.config`
– `include/generated/` 目录
– `scripts/` 工具
– `Module.symvers`
– 其他构建依赖
对初学者来说,最不容易踩坑。
### 7.2 保证内核与模块环境一致
尤其是在开发板内核移植、设备树修改、驱动调试过程中,完整编一次内核,可以确认整个源码树处于一致状态。
### 7.3 方便后续切换内建驱动和模块驱动
有些驱动既可以编进内核,也可以编成模块:
– `y`:编进内核
– `m`:编成模块
如果你之后经常切换配置方式,完整编过一次内核会更方便。
—
## 8. 如果不先准备内核,常见会出现什么问题
### 8.1 编译时报缺少头文件
例如:
“`bash
fatal error: include/generated/autoconf.h: No such file or directory
“`
说明内核自动生成头文件还没准备好。
—
### 8.2 `modpost` 阶段报错
可能出现:
– undefined symbol
– Module.symvers missing
– symbol version mismatch
说明模块依赖的内核符号信息不完整。
—
### 8.3 模块能编出来,但 `insmod` 失败
典型报错:
“`bash
invalid module format
“`
这通常说明:
– 内核版本不一致
– 架构不一致
– 配置不一致
– `vermagic` 不一致
– 符号版本不一致
这也是为什么很多时候“编译成功”并不代表“模块一定能加载成功”。
—
## 9. 从本质上怎么理解这个问题
这个问题最本质的一句话是:
> **内核模块不是独立程序,而是依附于某个特定内核的扩展,因此它必须依赖该内核已经准备好的构建环境。**
你可以做一个类比:
– 普通应用程序像“独立软件”
– 内核模块像“宿主程序的插件”
插件要想正常工作,就必须按照宿主程序的接口、版本和规则来构建。
Linux 内核模块也是一样。
—
## 10. 对驱动开发者的实际建议
对于嵌入式 Linux 驱动开发,推荐按下面思路来做。
### 第一步:先准备目标内核源码树
例如:
“`bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_emmc_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_prepare
“`
如果你已经完整编译过一次内核,也可以直接使用。
### 第二步:再编译外部模块
例如:
“`bash
make -C /path/to/kernel M=$PWD ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
“`
这样可以保证:
– 用的是目标内核的配置
– 用的是目标内核的头文件
– 用的是目标内核的 Kbuild 规则
– 生成的 `.ko` 更有机会和目标板当前内核匹配
—
## 11. 总结
编译 Linux 内核模块前,通常要先编译一下内核,原因并不是“形式上必须这样”,而是因为模块编译依赖目标内核已经准备好的构建环境。
具体来说,先准备或先编译内核,是为了获得:
1. **正确的内核配置 `.config`**
2. **自动生成的头文件**
3. **Kbuild 和 scripts 构建环境**
4. **`Module.symvers` 符号版本信息**
5. **与目标内核一致的版本、配置和 ABI 环境**
所以更准确的理解应该是:
> **不是模块一定要等整个内核完全编译结束后才能编,而是模块必须依赖一个已经准备好的目标内核构建环境。**
对于初学者来说,最稳妥的方法通常就是:
> **先把目标 Linux 内核配置并编译一遍,再去编写和编译自己的内核模块。**
这样能减少大量由于版本、配置、符号不一致导致的问题。