☰
modprobe报错can‘t open modules.dep?嵌入式Linux下buildroot模块依赖缺失排查与修复
2026/10/2 2:15:50 网站建设 项目流程

做嵌入式Linux开发,尤其是用buildroot从零构建rootfs的时候,总会碰到一些看起来莫名其妙、查起来又很耗时的报错。比如我今天要聊的这个:板子启动正常,串口也能进shell,执行modprobe加载驱动,却给我弹一句modprobe: can't open 'modules.dep': No such file or directory。我第一次看到这行报错是在一次内核版本升级之后,当时第一反应是buildroot缺少modprobe这个命令,后来才发现根本不是,modprobe好好的,缺的是modules.dep这个模块依赖数据库文件。这篇文章我会把这个报错涉及的机制拆开,讲清buildroot里模块是怎么进rootfs的、modules.dep是怎么生成的,以及你遇到这个问题时该按什么顺序排查和修复。不管是刚开始玩buildroot的新手,还是已经踩过坑想找人确认思路的老手,相信都能从里面找到有用的信息。

1. 问题场景与根因分析

1.1 这个报错到底在说什么

先把报错本身的字面意思说透。modprobe这个工具,和insmod有本质区别:insmod是"你给我哪个文件,我就加载哪个文件",完全不关心模块之间的依赖关系;modprobe则是先读一个叫modules.dep的依赖关系数据库,搞清楚模块A是否依赖模块B、模块C,再按依赖顺序把所有需要的模块依次加载。这个modules.dep文件,按Linux的模块加载约定,应该放在/lib/modules/$(uname -r)/这个目录下面,和真正的.ko文件放在一起。

所以当你敲下modprobe,它做的第一件事就是去/lib/modules/$(uname -r)/modules.dep找这个文件。找不到,就直接报"can't open 'modules.dep'"。换句话说,这不是modprobe命令本体缺失,而是它工作所需的"分拣表"丢了。我经常用一个类比来解释:modprobe像一个快递分拣员,modules.dep是分拣表,没有分拣表,分拣员完全不知道该把包裹发给谁。你可能会觉得,那我自己直接指定ko文件路径用insmod不就行了?可以,但modules.dep里除了依赖关系,还有模块别名(alias)和符号(symbols)信息,很多驱动是通过设备ID匹配自动加载的,没有这些文件,自动加载机制就废了。

这类问题典型的触发场景有几种:刚烧写完一个自编译rootfs、刚切换过内核版本、或者手动往板子上拷贝过几个编译好的ko文件。系统本身看起来一切正常,启动、网络、串口全部OK,一执行modprobe就露馅。这正好是嵌入式开发里最难防的一类坑——"表面正常,底层缺件"。

1.2 buildroot里为什么会缺modules.dep

要回答这个问题,得先分清几种不同成因,因为它们对应的修复方式完全不一样。

第一种,目标板文件系统里/lib/modules/目录根本不存在,或者是个空目录。这种情况多半是构建rootfs时没有执行模块安装步骤。buildroot虽说是一套"一键构建"的体系,但内核配置、模块配置这些环节是可以独立开关的,如果某个配置项没打开,模块压根不会被安装进rootfs。最后做出来的镜像里自然找不到modules.dep。

第二种,目录存在,里面也有ko文件,但唯独没有modules.dep和modules.alias这些周边文件。这种情况最常见于手动拷贝模块。比如你从别的工程里拷了一个自己编译的spi驱动ko进板子,或者把某个厂商SDK里的驱动目录整个复制到了/lib/modules下。复制文件这个动作本身不会触发depmod去生成依赖关系文件,于是板子上就只有一堆孤零零的ko,modprobe依然没法工作。

第三种,目录存在,modules.dep也存在,但你运行的内核版本和目录名对不上。buildroot编译内核时如果用的是5.10.x,安装模块时目录就是/lib/modules/5.10.x/。结果板子上跑的rootfs是另一套,uname -r返回5.4.y,modprobe按5.4.y去找目录,发现根本没有,照样报这个错。这种情况在多人协作、拿旧镜像配新内核、或者buildroot输出物混用的场景下非常常见。

第四种,比较隐蔽。buildroot构建流程里,内核的.config中CONFIG_MODULES没有打开,或者buildroot侧没有使能内核模块相关选项。内核编译时压根不生成ko文件,后面modules_install和depmod这两步自然被跳过。你到output/target目录里看,/lib/modules/下面干干净净,好像一切正常,但你根本没法用modprobe。这类问题光看报错很难想到要回buildroot配置里查,所以我每次遇到modprobe相关的问题,都会先怀疑构建配置,再怀疑文件系统内容。

把这几种成因对照一下实际报错:modprobe报"can't open modules.dep"时,它并不在乎你的ko文件在不在,它只认modules.dep这个文件。所以这个报错本质上就是"模块依赖数据库缺失"的代名词。搞清楚这点,排障方向就不会跑偏。

2. buildroot中模块安装的完整链路

2.1 内核模块从编译到rootfs的流转过程

在buildroot里,一个内核模块要最终出现在板子的/lib/modules/下,经历的流程比很多人想象的要长。先从内核源码编译出.ko文件,然后执行modules_install,把ko文件按内核源码树中的目录结构复制到指定根目录下,最后运行depmod生成依赖关系文件。buildroot把这几个步骤全部封装进了它的Kernel构建流程里,对应到具体动作就是:先configure、再compile、然后install modules、最后跑depmod。完成之后,成果全部落在output/target/这个目录下,output/target最终会被打包成rootfs.tar或ext2/squashfs等镜像文件。

这里有个关键点:buildroot的output/target目录就是板子上根文件系统的"影子",你在这个目录里看到的lib/modules/目录结构,最终会原封不动地进入烧写镜像。所以排障时有个很有效的招:先在构建服务器的output/target里直接检查,如果这里就没有modules.dep,说明是构建阶段的问题,烧写多少遍都没用;如果这里有、板子上没有,那就要怀疑镜像烧写、分区挂载这些事情了。

日常开发中,我们改完内核配置后通常不会整个来一遍全量make,而是用make linux-rebuild来单独重建内核。这个target会重新编译内核、重新安装模块、重新生成依赖文件,然后把结果同步到output/target。用熟之后,整个迭代周期能控制在几分钟以内,比全量构建省太多时间。

2.2 modules.dep是怎么生成的

modules.dep文件由depmod工具扫描/lib/modules/$(uname -r)/目录下所有ko文件后生成。扫描时会解析每个ko文件里的符号引用,判断它依赖哪些其他模块,然后把依赖关系按固定格式写进modules.dep。同时还会生成modules.alias(设备别名)、modules.symbols(符号所属模块)等辅助文件。

拿一段真实的modules.dep内容举例:

/lib/modules/5.10.0/kernel/drivers/spi/spi-dev.ko: /lib/modules/5.10.0/kernel/drivers/usb/serial/ftdi_sio.ko: /lib/modules/5.10.0/kernel/drivers/usb/serial/usbserial.ko

第一行表示spi-dev.ko没有依赖任何其他模块,冒号后面是空的;第二行表示ftdi_sio.ko依赖usbserial.ko,加载时必须先加载usbserial.ko。modprobe读取这个文件后,就会按这个先后顺序来加载。如果modules.dep缺失,modprobe完全没有可用的依赖信息,自然直接退出。

实际生成命令很简单,在目标板上执行:

depmod -a

-a表示扫描/lib/modules/下所有内核版本目录。在构建端,也可以指定-b参数和版本号,针对特定rootfs目录生成,这在交叉编译环境里尤其有用。别忘了depmod不是内核的一部分,它属于kmod工具集,虽然几乎所有发行版都默认带,但在裁剪过的嵌入式系统里不一定是标配。

2.3 busybox modprobe和kmod modprobe的差别

buildroot生成的系统里,modprobe这个命令有两个来源:一个是busybox自带的applet,一个是由BR2_PACKAGE_KMOD引入的完整版kmod工具。很多人没意识到这两个modprobe的行为是有差异的。

busybox的modprobe实现比较精简,它只做最基本的依赖解析和模块加载,支持的参数也少。好处是省空间、不依赖额外库,坏处是遇到复杂场景容易"不按常理出牌",诊断信息也不够详细。kmod的modprobe则跟桌面Linux发行版上用的完全是同一个项目,功能齐全,报错信息更人性化,比如模块格式错误、vermagic不匹配这些,它都能给出相对明确的提示。

我建议在开发阶段,如果空间允许,直接在buildroot里打开kmod包,这能省掉很多"莫名其妙"的调试时间。特别是在排查模块加载问题时,kmod modprobe输出的错误往往直接指向根因,而busybox版可能只会回一句"not found",让你无从下手。这一点在嵌入式开发里属于性价比很高的一个选择。

3. 排查与修复实操步骤

3.1 先看目标板的目录结构

到了板子上,别急着重新烧镜像,先按顺序跑几条命令,把现状摸清楚。

uname -r ls -la /lib/modules/ ls -la /lib/modules/$(uname -r)/ find /lib/modules/ -name '*.ko' | head -20

第一条看当前运行内核版本,第二条看系统里有哪些模块目录,第三条看当前版本目录里的具体内容,第四条确认ko文件到底有没有。这三条命令一跑,绝大多数情况就能定性了。

我遇到过最典型的情况是:uname -r返回5.15.0,但/lib/modules/下面只有5.10.0一个目录。这种情况不用想,就是内核和rootfs不是同一套来源,可能烧错了镜像,也可能是构建时内核版本配置不一致。另外也有一种情况是,目录名和版本对上了,但目录里只有ko文件,没有任何modules.dep相关文件,这说明构建流程漏了depmod步骤,或者有人手动往系统里拷了模块但没跑depmod。

如果/lib/modules/整个目录都不存在,问题更直接,基本上就是构建时模块安装没执行,需要回到buildroot配置里查。

3.2 buildroot侧配置排查与重新构建

在构建服务器上,先用menuconfig检查配置:

make menuconfig

进入Kernel配置菜单,确认Linux Kernel相关选项已经使能,再检查内核自身的.config文件,确认CONFIG_MODULES=y。这个选项如果没打开,后面所有模块相关操作都是空谈。在buildroot的Kernel配置项里,还要确认和模块安装相关的开关是打开状态。不同buildroot版本界面文案略有差异,但逻辑一致:模块只有被勾选安装,才会在modules_install阶段被复制到output/target。

确认配置无误后,重新构建内核相关部分:

make linux-rebuild make

make linux-rebuild会重新运行内核的编译并重复模块安装、depmod的流程,后面的make则保证rootfs整体打包时把最新内容打进去。构建完成后,直接检查output/target/lib/modules/目录:

ls -la output/target/lib/modules/$(grep -m1 '^BR2_LINUX_KERNEL_CUSTOM_VERSION' .config 2>/dev/null | cut -d= -f2 2>/dev/null)/

如果这里能看到modules.dep,说明构建端已经正常,剩下就是重新生成rootfs镜像并烧写。

3.3 快速修复:手动生成modules.dep

有些场景下不想重新烧整个镜像,或者手头没有构建环境,只有一根串口线和一个shell,那可以尝试在板端直接生成缺失的依赖文件。

先把根文件系统重新挂成可写:

mount -o remount,rw /

然后执行depmod:

depmod -a sync

这里有一个前提:板子上必须有depmod这个命令。如果是busybox系统,depmod未必被编译进去;如果没有,可以临时拷贝一个静态编译的depmod二进制上板,或者干脆用kmod。如果板子有网络和包管理系统,直接装kmod包也行。

另外还有一种更"正规"的做法,从构建端处理:在buildroot根目录里,用交叉编译环境自带(或kmod编译生成)的depmod工具,直接针对rootfs目录生成依赖文件:

output/host/bin/depmod -b output/target 5.10.0

这条命令的意思是:把output/target当作根文件系统根目录,扫描其中lib/modules/5.10.0/下的所有模块,生成依赖文件并写回。生成的modules.dep会出现在output/target/lib/modules/5.10.0/modules.dep,之后重新打包rootfs镜像烧写即可。这个方式的优势是不依赖板端环境,适合批量处理多个同构板子。

3.4 复现验证与自启动配置

修复完成之后,不能只看modprobe不报错就认为完事,得完整验证一遍模块确实加载成功。

modprobe <module_name> lsmod dmesg | tail -50

modprobe无输出通常只是第一步,lsmod可以看到模块是否真的在内存里,dmesg则能看到驱动初始化过程中有没有打印错误。很多时候modprobe加载模块本身没报错,但驱动probe失败,问题出在硬件初始化环节,那是另一个层面的问题。验证时别漏了这一层。

如果把模块做成开机自动加载,buildroot里可以往/etc/init.d/放一个启动脚本,也可以用target/overlay机制往rootfs里预置内容。我常用的做法是建一个board/<我的板子>/rootfs-overlay/etc/init.d/S99modules脚本,内容很简单:

#!/bin/sh case "$1" in start) echo "Loading kernel modules..." modprobe spi-dev modprobe ftdi_sio ;; stop) rmmod spi-dev rmmod ftdi_sio ;; esac

在buildroot配置里指定overlay目录路径,重新make后这个脚本就会自动进入镜像。比每次烧写后手动敲命令要省心得多。

4. 常见问题与独家经验

4.1 这类报错的衍生问题速查表

在开发过程中,围绕modprobe和模块加载,我还遇到过不少连带问题,整理成一张速查表,方便你遇到类似情况时快速定位:

现象可能原因常规处理
can't open 'modules.dep'依赖数据库缺失检查目录结构,重新depmod或重新构建
module 'xxx' not found模块名/路径不匹配,别名缺失find查找ko文件位置,用modinfo核对名称
Exec format errorko架构或内核版本与运行内核不一致用当前内核源码重新编译模块
Invalid module formatvermagic不匹配检查uname -r和模块编译版本,需重编
Operation not permitted核内安全模块拦截或权限问题检查kernel lockdown、selinux等设置

这表里的前两类和今天说的modules.dep问题经常一起出现,后三类则更多是模块编译环境配置导致。排查的时候我建议顺序固定:先看版本、再看目录、再看文件、最后看权限。按这个顺序来,基本都能在十分钟内给出结论。

4.2 手动拷模块的翻车现场

我曾经为了图省事,直接把构建服务器上output/target/lib/modules/里的某个厂商WiFi驱动ko整个目录拷到板子上,想省去重新烧镜像的麻烦。结果modprobe报错不说,操作系统日志里还多了一串firmware加载失败的信息。查到最后发现,这个驱动不光要ko文件,还依赖一套firmware二进制,firmware没拷对,模块即使加载起来也跑不起来。从那以后我就学乖了:手动拷模块不是不能做,但必须先弄清楚模块的依赖、固件路径、vermagic这些附加条件,最好用modinfo查看一下模块信息。

另一个常见的坑是权限问题。嵌入式系统的/lib/modules目录通常是root用户的,普通用户执行modprobe时,如果没有正确配置udev或者权限,也可能出现类似"Operation not permitted"的报错,虽然这个和modules.dep缺失不是同一个原因,但排障时容易混淆。我习惯在确认版本和目录都正常后,顺手用id看一下当前用户,别让权限问题干扰判断。

4.3 我在开发中沉淀的几条模块管理习惯

最后分享几个我自己的实践习惯,都是我踩过坑之后沉淀下来的。

第一,模块尽量走buildroot流程,不要手工往镜像里塞ko文件。这不是说手工操作一定不行,而是手工操作容易漏掉依赖、漏掉版本匹配、漏掉depmod这几步。把模块添加变成buildroot的一部分,整个流程可重复、可追溯,对多人协作尤其重要。

第二,每次构建完,习惯性检查output/target/lib/modules下的内容,确认modules.dep存在、内核版本目录对得上。这个检查只需要十秒钟,但能避免烧写完镜像才发现问题的尴尬。我现在基本上已经形成肌肉记忆了。

第三,用make linux-rebuild进行迭代。改一个驱动模块或者调内核配置,可以快速验证,不用动不动全量make。但要注意,如果是改了buildroot的packages配置,还是要用完整make来保证依赖关系不会错乱。

第四,对于复杂的模块依赖,直接用modinfo在目标板上查模块信息:

modinfo <module_name>

modinfo会输出模块的文件路径、vermagic、依赖模块列表等关键信息,能帮你快速判断模块是否适用于当前内核。

最后说点我自己的体会吧。这个报错本身不复杂,但它背后涉及的是整个嵌入式Linux模块加载机制的运作逻辑。modprobe依赖modules.dep,而modules.dep又来源于构建流程中的depmod步骤,任意一环断开,用户端呈现的就是一行冷冰冰的"No such file or directory"。我后来养成了一个习惯,每次构建完rootfs先别急着烧,先在output/target/lib/modules/下面看一眼,确认该有的文件都在。这个习惯帮我省了很多次返工。如果你也被这个报错卡过,希望这篇文章能帮你少走一些弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询