Linux驱动 2026年4月20日 20 分钟

编译内核模块时为什么要先编译内核

# 为什么编译 Linux 内核模块前,通常要先编译一下 Linux 内核 在学习 Linux 驱动开发时,很多教程都会…

# 为什么编译 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 内核配置并编译一遍,再去编写和编译自己的内核模块。**

这样能减少大量由于版本、配置、符号不一致导致的问题。

上一篇 Linux环境变量相关知识

# Linux 环境变量与交叉编译 ## 1. 什么是环境变量 环境变量本质上就是一组 **键=值** 的配置,例如: ...

下一篇 uboot的编译烧写以及源码工程分析