Linux 内核模块符号版本不匹配问题排查指南

1. 问题现象

启动自定义内核时,模块加载失败,dmesg/var/log/kern.log 显示类似错误:

复制代码
<module_name>: disagrees about version of symbol <symbol_name>
<module_name>: Unknown symbol <symbol_name> (err -22)

例如:

复制代码
my_driver: disagrees about version of symbol drm_atomic_helper_commit_hw_done
my_driver: Unknown symbol drm_atomic_helper_commit_hw_done (err -22)

2. 问题原因

2.1 根本原因

Linux 内核启用了 CONFIG_MODVERSIONS(符号版本控制),每个导出符号都有一个 CRC 校验值。当模块引用的符号 CRC 与内核/依赖模块导出的 CRC 不匹配时,加载失败。

2.2 常见触发场景

场景1:在同一目录下频繁更新内核源码

当你在同一个工作目录下更新内核(如 git pullgit rebase 到新版本):

  1. 根目录的 Module.symvers 会在下次完整构建时重新生成
  2. 但子目录下的旧 Module.symvers 不会被自动清理
  3. 使用 make M=<subdir> 单独编译模块时,会读取父目录的 Module.symvers
  4. 如果父目录存在旧的 Module.symvers,模块会引用过时的符号 CRC

目录结构示例:

复制代码
linux/
├── Module.symvers              # 6月8日 (新)
├── drivers/
│   └── gpu/
│       └── drm/
│           ├── Module.symvers  # 4月8日 (旧!) ← 问题根源
│           └── my_driver/
│               └── Module.symvers  # 编译时生成

场景2:单独编译模块后,未重新安装依赖模块

  1. 只编译并安装了 my_driver.ko
  2. 但其依赖的 drm.kodrm_kms_helper.ko 等没有更新
  3. 导致 initramfs 中的依赖模块与新编译的模块版本不一致

3. 诊断方法

3.1 步骤1:检查是否存在多个 Module.symvers

bash 复制代码
cd /path/to/linux
find . -name 'Module.symvers' -exec ls -la {} \;

输出示例(存在问题):

复制代码
-rw-r--r-- 1 user user 2129123  6月  9 19:12 ./Module.symvers
-rw-r--r-- 1 user user   93634  4月  8 16:27 ./drivers/gpu/drm/Module.symvers  # 旧!
-rw-r--r-- 1 user user    1741  6月  9 14:56 ./drivers/gpu/drm/my_driver/Module.symvers

3.2 步骤2:比较符号 CRC

bash 复制代码
# 查看根 Module.symvers 中的 CRC
grep <symbol_name> Module.symvers

# 查看模块期望的 CRC
modprobe --dump-modversions drivers/path/to/my_driver.ko | grep <symbol_name>

如果两者 CRC 不同,说明存在版本不匹配。

3.3 步骤3:检查已安装模块

bash 复制代码
# 查看已安装模块的时间戳
ls -la /lib/modules/$(uname -r)/kernel/drivers/path/to/

# 比较源码树和已安装模块
sha256sum drivers/path/to/my_driver.ko /lib/modules/$(uname -r)/kernel/drivers/path/to/my_driver.ko

4. 解决方案

4.1 方案1:清理旧的 Module.symvers(推荐)

bash 复制代码
cd /path/to/linux

# 删除子目录中的旧 Module.symvers
find ./drivers -name 'Module.symvers' -delete

# 重新编译模块
make M=drivers/path/to/my_driver modules

# 安装模块
sudo make M=drivers/path/to/my_driver modules_install

# 更新 initramfs
sudo update-initramfs -u -k $(make -s kernelrelease)

4.2 方案2:完整重新构建

bash 复制代码
cd /path/to/linux

# 清理所有构建产物
make clean

# 重新构建内核和所有模块
make -j$(nproc)

# 安装模块和内核
sudo make modules_install
sudo make install

# 更新 initramfs
sudo update-initramfs -u -k $(make -s kernelrelease)

4.3 方案3:更新构建脚本

在模块构建脚本中添加清理步骤:

bash 复制代码
#!/bin/bash
KDIR="/path/to/linux"
MODULE_DIR="drivers/path/to/my_driver"

# 清理旧的 Module.symvers 防止 CRC 不匹配
find "$KDIR/$(dirname $MODULE_DIR)" -name 'Module.symvers' -delete 2>/dev/null || true

# 编译模块
make -C "$KDIR" M="$MODULE_DIR" modules

5. 预防措施

5.1 更新内核源码后执行清理

bash 复制代码
# 更新内核后,清理构建残留
git pull origin main
make clean
# 然后重新构建

5.2 添加 .gitignore 规则

在内核源码根目录的 .gitignore 中添加:

gitignore 复制代码
# 防止子目录 Module.symvers 残留
drivers/*/Module.symvers
drivers/*/*/Module.symvers
drivers/*/*/*/Module.symvers

5.3 使用独立的构建目录(可选)

bash 复制代码
# 使用 O= 参数指定独立的输出目录
make O=/path/to/build_output -j$(nproc)

这样源码目录保持干净,切换内核版本时不会有残留。

6. 相关配置

检查内核是否启用了符号版本控制:

bash 复制代码
grep CONFIG_MODVERSIONS /boot/config-$(uname -r)
# 输出: CONFIG_MODVERSIONS=y

如果仅用于开发调试,可以考虑在内核配置中禁用 CONFIG_MODVERSIONS(不推荐用于生产环境)。

7. 参考

  • Linux 内核文档: Documentation/kbuild/modules.rst
  • 内核模块签名: Documentation/admin-guide/module-signing.rst
相关推荐
qq_163135751 小时前
Linux查看组名
linux
烁3471 小时前
Linux简单脚本
linux·运维·服务器
hanbr2 小时前
Linux 基础入门笔记:从零开始理解核心概念与常用操作
linux
南岸的水2 小时前
ubuntu里面SDK编译指令及报错处理
linux·运维·ubuntu
Dlrb12112 小时前
Linux系统编程-进程间通信(管道、共享内存)
linux·共享内存·进程间通信·ipc·无名管道·有名管道
爱网络爱Linux2 小时前
Linux 服务器开机慢?启动链路优化实战
linux·运维·redhat·rhce·rhca·红帽认证
buhuizhiyuci2 小时前
【Linux篇】数字世界的底层认识, 它是底层的地基——进程概念的认识
linux·运维·服务器
A_humble_scholar2 小时前
Linux(六)深入理解 Linux 进程管理:从硬件到调度
linux·网络
曦月合一2 小时前
在 Linux 服务器上执行这些命令来导入 SSL 证书
linux·服务器·ssl