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 pull、git rebase 到新版本):
- 根目录的
Module.symvers会在下次完整构建时重新生成 - 但子目录下的旧
Module.symvers不会被自动清理 - 使用
make M=<subdir>单独编译模块时,会读取父目录的Module.symvers - 如果父目录存在旧的
Module.symvers,模块会引用过时的符号 CRC
目录结构示例:
linux/
├── Module.symvers # 6月8日 (新)
├── drivers/
│ └── gpu/
│ └── drm/
│ ├── Module.symvers # 4月8日 (旧!) ← 问题根源
│ └── my_driver/
│ └── Module.symvers # 编译时生成
场景2:单独编译模块后,未重新安装依赖模块
- 只编译并安装了
my_driver.ko - 但其依赖的
drm.ko、drm_kms_helper.ko等没有更新 - 导致 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