本文约1800字,今天在移植sdk中遇到驱动加载问题,原因是修改了内核配置,重新编译了内核,但是原厂提供的sdk相关的驱动没有源码,没有重新编译导致加载失败。后续确认好配置是需要将配置发给原厂帮忙重新编译驱动的。本文借此梳理Linux下驱动加载失败的各种原因以及修改内核后的哪些场景必须重新编译驱动。
关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具,文末有文档清单。
引言:嵌入式工程师必备:定位 ko 加载失败、避免重复踩坑
在嵌入式 Linux 开发中,驱动 .ko 加载失败是最常见、最头疼的问题之一。明明编译过了、路径对了、权限给了,但就是:
insmod: ERROR: could notinsertmodule xxx.ko: Invalid module formatinsmod: ERROR: could notinsertmodule xxx.ko: Unknown symbol inmodule
或 dmesg 里出现:
version magic 'x.x.x ...' should be 'x.x.x ...'module layout mismatch
绝大部分问题都来自:驱动与内核不匹配、编译环境不匹配、内核配置被修改、依赖缺失、硬件 / 设备树不匹配。
本文分为两部分:
[1].驱动加载失败的所有常见原因(可直接排查)
[2].内核做哪些修改 → 必须重新编译驱动(最容易被忽略)
第一部分:Linux 驱动加载失败的所有常见原因
[1]. 内核版本 /vermagic 不匹配(最常见)
表现:
version magic '4.9.88' should be '4.9.88-perf'原因:
驱动用 Kernel A 编译
板子跑的是 Kernel B
版本、编译配置、LOCALVERSION 不一样都不行
运行如下命令排查:
modinfo xxx.ko | grep vermagicuname -r
[2]. 编译器不匹配(ARM 用了 x86 编译器,或工具链变了)
表现:
Invalidmodule formatUnknownsymbol 乱跳
原因:
驱动用 arm-linux-gnueabihf-gcc 编
内核用 arm-linux-uclibcgnueabihf-gcc 编
ABI 不一致,直接拒载。
规则:驱动必须与内核使用同一个工具链编译
[3]. 内核配置(.config)被修改(典型坑)
表现:
Unknownsymbolmodule layout mismatch
加载直接卡死
典型修改:
CONFIG_MODVERSIONS 开 / 关CONFIG_CPU_xxx 切换CONFIG_FPU / CONFIG_SMP 开 / 关CONFIG_DEBUG_INFO、CONFIG_KASAN、CONFIG_PROF 开 / 关
只要内核 .config 变了,所有驱动都要重编。
[4]. 符号版本(CONFIG_MODVERSIONS)不匹配
表现:
Unknown symbol xxx (err 0)
原因:
内核开了 CONFIG_MODVERSIONS
驱动没在该内核下编译 → 符号校验失败
解决:在内核开启 MODVERSIONS 的情况下,驱动必须重新编译。
[5]. 依赖模块未加载(最容易忽略)
例如:
SPI 驱动依赖 spi-bitbang
I2C 驱动依赖 i2c-core
声卡依赖 soundcore
网络驱动依赖 phy-core
表现:
Unknown symbol spi_register_controller
解决:先加载依赖,再加载当前驱动。
[6]. 设备树(DTS)不匹配 / 节点不存在
表现:
probe 不执行
加载成功但无设备
直接 kernel panic
原因:
DTS 中无对应 compatible
地址 / 中断冲突
时钟 / 电源未使能
[7]. 硬件不存在、地址错误、引脚冲突
表现:
读寄存器为 0xFFFFFFFF
probe 失败 return -ENODEV
加载后系统卡死
原因常见于:
>>地址写错
>>引脚被其它外设复用
>>时钟未开
>>电源未使能
[8]. 模块签名 / 安全启动限制
表现:
Loading of unsigned module is rejected
原因:
内核开启 CONFIG_MODULE_SIG
安全启动禁止未签名驱动
[9]. 32bit / 64bit 不匹配
ARM64 内核 ← 用 ARM32 驱动 → 直接拒载。
[10]. 内核被篡改、zImage 与 Module.symvers 不匹配
表现:
各种 Unknown symbol
昨天能用今天突然不能用
原因:
重新编译内核但只更新 zImage,没更新 Module.symvers
驱动编译时使用旧符号表
第二部分(核心):内核做哪些修改,必须重新编译驱动?
下面这些操作,只要做任意一个,所有外部驱动(.ko)必须重新编译,否则 100% 加载失败。
[1]. 内核版本号改变(包括 SUBLEVEL、LOCALVERSION)
例:
4.9.88 → 4.9.89
4.9.88 → 4.9.88-rc1
所有驱动重编
[2]. 内核 .config 发生任何以下修改
必须重编驱动的 CONFIG 项:
CONFIG_MODVERSIONS 开 / 关CONFIG_SMP 开 / 关(多核 / 单核切换)CONFIG_PREEMPT 抢占开 / 关CONFIG_CPU_XXX(CPU 型号切换)CONFIG_CPU_V7 / CONFIG_CPU_V8CONFIG_FPU / CONFIG_VFPCONFIG_ARM_ASM_UNIFIEDCONFIG_DEBUG_CREDENTIALS、CONFIG_KASAN、CONFIG_PROFCONFIG_COMPATCONFIG_CC_OPTIMIZE 优化等级变化
只要变,驱动必须重编。
[3]. 内核工具链改变
例:
arm-linux-gcc → arm-linux-uclibcgnueabihf-gccgcc 7.5 → gcc 8.3
驱动必须重编
[4]. 内核头文件(include/linux/xxx)被修改
包括:
结构体增减字段
struct file_operations 变化struct platform_driver 变化
宏定义增减
函数原型变化
所有依赖该头文件的驱动必须重编
[5]. 导出符号(EXPORT_SYMBOL)变化
内核新增 / 删除 EXPORT_SYMBOL(xxx)
函数参数改变
依赖该符号的驱动必须重编
[6]. 设备树(DTS)的 compatible 改变
虽然不影响 ko 加载,但会导致:
probe 不匹配
驱动加载成功但不工作
总结
驱动加载失败,按这个顺序查:
[1].uname -r 和 modinfo ko 的 vermagic 是否一致
[2].内核与驱动是否同一工具链、同一 .config
[3].内核是否开了 MODVERSIONS
[4].依赖模块是否加载
[5].设备树是否匹配
[6].硬件地址 / 引脚 / 时钟 / 电源是否正常
内核一动,驱动必重编的情况:
[1].版本变
[2].工具链变
[3]..config 变(尤其 SMP/FPU/MODVERSIONS/CPU)
[4].内核头文件变
[5].结构体/函数/符号变
只要变一个,驱动全部重编译!
以上为全文内容。

这里是女程序员的笔记本
15年+嵌入式软件工程师兼二胎宝妈
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助