摘要
现场设备升级或重启后,自定义内核模块突然加载失败,这种事并不少见。
这次报错是:
Module sio_gpio not found in directory /lib/modules/6.8.0-111-generic第一眼看,很容易以为sio_gpio.ko被删了。实际查下来,文件还在,只是放在旧内核目录里。
这次机器上旧模块放在:
/lib/modules/6.8.0-107-generic/extra/sio_gpio.ko但系统当前已经启动到:
6.8.0-111-generic而modprobe不会满机器乱找,它默认只查当前运行内核的模块目录:
/lib/modules/$(uname-r)所以这次要确认的不是“机器上到底有没有这个.ko文件”,而是:
这个模块是不是在当前运行内核对应的
/lib/modules/$(uname -r)目录下。
下面按现场排查顺序记录一下:先怎么恢复,再怎么避免下次重启又踩一次。
一、问题现象
现场执行:
sudomodprobe sio_gpio系统提示:
Module sio_gpio not found in directory /lib/modules/6.8.0-111-generic看/lib/modules,机器上其实有好几个内核版本:
6.5.0-18-generic 6.8.0-90-generic 6.8.0-94-generic 6.8.0-107-generic 6.8.0-110-generic 6.8.0-111-generic再去旧内核目录看:
ls/lib/modules/6.8.0-107-generic/extra能看到:
sio_gpio.ko到这里就能先排除一件事:模块文件不是完全丢了。
问题卡在另一边:系统现在跑的已经不是6.8.0-107-generic,而是6.8.0-111-generic。
二、先看当前内核
排这种问题,先别急着复制文件,第一条命令看当前内核:
uname-r如果输出是:
6.8.0-111-generic那modprobe sio_gpio默认只会去这里找:
/lib/modules/6.8.0-111-generic/它不会因为旧目录里存在:
/lib/modules/6.8.0-107-generic/extra/sio_gpio.ko就自动跨版本加载。
所以这句Module not found不能直接理解成“整个系统里没有这个文件”。放到这次现场,它的意思更接近:
当前运行内核对应的模块目录里没有这个模块。
三、为什么换个内核就不行
Linux 内核模块不是普通动态库,不能只看文件名一样不一样。
.ko模块通常跟内核版本、内核配置、符号表和编译参数绑在一起。哪怕都是6.8.0系列,6.8.0-107-generic和6.8.0-111-generic对模块来说也不是同一个目标。
可以看一下这个模块当时是按哪个内核编出来的:
modinfo /lib/modules/6.8.0-107-generic/extra/sio_gpio.ko|grepvermagic如果看到类似:
vermagic: 6.8.0-107-generic SMP preempt mod_unload modversions而当前:
uname-r输出是:
6.8.0-111-generic那就很明确了:这个模块原本是给6.8.0-107-generic编译的。
这时有两种情况:
- 模块刚好还能被当前内核接受,复制到当前目录后可以临时加载
- 模块和当前内核 ABI 不匹配,复制过去也会报
Invalid module format
所以复制.ko只能当现场尝试,别把它当成正式修复。
四、先恢复现场:复制到当前内核目录
如果当前目标是先让设备恢复起来,可以先把旧目录里的模块放到当前内核目录。
sudomkdir-p/lib/modules/$(uname-r)/extrasudocp/lib/modules/6.8.0-107-generic/extra/sio_gpio.ko\/lib/modules/$(uname-r)/extra/sudodepmod-a"$(uname-r)"sudomodprobe sio_gpio这里最容易漏的是这一步:
sudodepmod-a"$(uname-r)"depmod会重新生成模块依赖索引。否则文件虽然已经复制过去,modprobe仍然可能查不到。
加载后再确认:
lsmod|grepsio_gpio如果还想看内核侧日志:
sudodmesg|tail-50这套操作只能算临时恢复。它解决的是“当前内核目录里没有模块”的问题,不保证模块一定适配当前内核。
五、如果提示 Invalid module format
如果复制后执行:
sudomodprobe sio_gpio报:
Invalid module format或者dmesg里出现类似:
version magic '6.8.0-107-generic ...' should be '6.8.0-111-generic ...'这就说明模块和当前内核对不上。
这时继续复制就没意义了,要在当前内核下重新编译。
先装当前内核头文件:
sudoaptupdatesudoaptinstallbuild-essential"linux-headers-$(uname-r)"然后进入sio_gpio源码目录:
makecleanmake安装到当前内核目录:
sudomkdir-p/lib/modules/$(uname-r)/extrasudocpsio_gpio.ko /lib/modules/$(uname-r)/extra/sudodepmod-a"$(uname-r)"sudomodprobe sio_gpio最后再确认:
lsmod|grepsio_gpio这一步才算是针对当前内核处理。
六、排查顺序
这类问题按下面顺序走就行,没必要一上来就重装系统,也别先怀疑驱动源码。
这张图里别跳过前两步:
uname-r和:
ls/lib/modules/$(uname-r)/extra先把“当前内核”和“模块所在目录”对上,后面的判断才不会跑偏。
七、现场设备别让内核随便变
这次问题说到底,是系统升级后启动到了新内核。
如果这台机器是机器人主机、工控机、采集设备,或者任何依赖自定义驱动的现场设备,内核版本就别让它随便变。
可以把 GRUB 默认启动项固定到已验证过的内核,比如:
6.8.0-107-generic先查看 GRUB 菜单项:
grep-E"^[[:space:]]*(submenu|menuentry) '"/boot/grub/grub.cfg找到类似:
submenu 'Advanced options for Ubuntu' ... Ubuntu, with Linux 6.8.0-107-generic编辑:
sudovim/etc/default/grub把:
GRUB_DEFAULT=0改成类似:
GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 6.8.0-107-generic"这里的名称必须和/boot/grub/grub.cfg里的submenu、menuentry名称一致,中间用>连接。
更新 GRUB:
sudoupdate-grub重启后确认:
uname-r如果输出:
6.8.0-107-generic说明启动内核已经回到指定版本。
八、还要控制 apt 升级内核
只固定 GRUB 还不够,这个坑下次还可能被apt upgrade带回来。
只要后面继续执行apt upgrade,系统仍然可能安装新的内核包。现场设备如果没有驱动重编译流程,下次重启还是可能回到同一个问题。
先看本机实际安装的内核相关包:
dpkg-l'linux-generic*''linux-image*''linux-headers*''linux-modules*'|grep'^ii'如果确认用的是 Ubuntu 普通 generic 内核元包,可以锁定:
sudoapt-mark hold linux-generic linux-image-generic linux-headers-generic如果用的是 HWE 内核,元包名称可能是linux-generic-hwe-*、linux-image-generic-hwe-*这一类,不要照抄,要按上一步查到的实际包名替换。
如果要锁定具体内核版本,也可以执行:
sudoapt-mark hold linux-image-6.8.0-107-genericsudoapt-mark hold linux-headers-6.8.0-107-genericsudoapt-mark hold linux-modules-6.8.0-107-genericsudoapt-mark hold linux-modules-extra-6.8.0-107-generic查看已经锁定的包:
apt-mark showhold|greplinux这个做法能稳住现场。代价也很直接:内核安全更新不会再自动跟着走,后面要有人安排升级窗口和验证流程。
九、长期还是上 DKMS
如果sio_gpio要长期用,别一直靠手动复制.ko,也别指望永远不升级内核。把它纳入 DKMS 更省心。
DKMS 干的就是这件事:
安装新内核时,自动为新内核重新编译并安装模块。
也就是说,系统从:
6.8.0-107-generic升级到:
6.8.0-111-genericDKMS 可以把sio_gpio重新编译到新内核对应的目录里。
我一般按这个顺序处理:
DKMS 自动重编译 > 固定内核版本 > 手动复制 .ko手动复制是救急,固定内核是稳住现场,DKMS 才是长期维护自定义驱动该走的路。
小结
这次sio_gpio加载失败,不是模块文件被删了,而是模块还在旧内核目录,系统却已经启动到了新内核。
这几个对应关系别看错:
旧模块位置: /lib/modules/6.8.0-107-generic/extra/sio_gpio.ko 当前运行内核: 6.8.0-111-generic modprobe 查找目录: /lib/modules/6.8.0-111-generic现场先恢复,可以复制到当前内核目录并执行depmod -a "$(uname -r)":
sudomkdir-p/lib/modules/$(uname-r)/extrasudocp/lib/modules/6.8.0-107-generic/extra/sio_gpio.ko\/lib/modules/$(uname-r)/extra/sudodepmod-a"$(uname-r)"sudomodprobe sio_gpio如果提示Invalid module format,就别继续绕了,直接按当前内核重新编译。
以后遇到类似问题,先记住这个判断:
modprobe找的是当前运行内核的模块目录,不是以前放过模块的目录。
对工控机、机器人主机、采集卡、GPIO、串口、CAN 这类依赖内核模块的设备来说,内核升级一定要和驱动验证放在一起做。普通升级看着没什么,落到现场设备上,可能就是驱动直接起不来。
参考
- modprobe(8) - Linux manual page
- depmod(8) - Linux manual page
- modinfo(8) - Linux manual page
- Ubuntu Manpage:apt-mark(8)
- Ubuntu Community Help Wiki:Grub2/Submenus
- Ubuntu Community Help Wiki:Grub2/Setup
- Ubuntu Community Help Wiki:DKMS
- Ubuntu Wiki:Kernel/Dev/DKMSPackaging