1. 认识depmod:内核模块系统里那个“劳碌命”的幕后管家
1.1 先搞明白它在Linux系统里到底做什么
经常折腾内核模块、驱动编译或者嵌入式Linux的朋友,肯定绕不开这么一条命令:
depmod -a看名字就知道,depmod = dependency + module,核心工作就是帮内核模块(.ko文件)生成依赖关系。Linux的模块系统并不是简单地把所有.ko文件丢进/lib/modules/$(uname -r)/就完事了,模块之间经常互相引用符号,加载时存在先后顺序,内核需要知道“谁先加载、谁后加载、谁依赖谁”。
depmod干的事情,就是扫描指定内核版本目录下的所有模块,分析它们导出的符号和使用到的外部符号,然后生成一份关系清单,存成这些文件:
modules.dep—— 纯文本依赖关系表,一行一个模块,冒号后面跟着它依赖的其他模块;modules.dep.bin—— 上面这份文件的二进制压缩版,快速读取用;modules.symbols—— 模块导出的符号表,modprobe通过它才能按符号名找到对应模块;modules.alias—— 设备别名表,硬件设备识别和自动加载时靠它;modules.builtin/modules.builtin.modinfo—— 编译进内核的模块记录,用于避免重复加载。
这套文件其实就是模块加载器的“索引”和“导航图”。没有它们,modprobe基本就是瞎的。你手动insmod一个模块或许还能勉强跑起来,但一旦遇到依赖链,立刻原形毕露。
1.2 为什么内核模块机制不能没有依赖分析
内核模块之间是“符号引用”的关系。举个很直白的例子:你写了一个字符设备驱动mydev.ko,它调用了kernel里某个EXPORT_SYMBOL导出的函数;而另一个驱动other.ko又依赖mydev.ko里的一个函数。这种情况下,加载顺序必须是:
内核基础符号 → mydev.ko → other.kodepmod把这种链条扫出来,记录进modules.dep。之后你只要执行modprobe other,它就会自动把mydev.ko先拉起来,根本不需要你操心先后顺序。
常见的热搜词里能看到大量linux驱动、嵌入式linux、linux系统管理、linux运维故障案例相关的搜索需求,其实都和模块加载脱不了干系。很多运维同学在服务器上编译安装驱动,或者嵌入式工程师交叉编译完驱动模块拷到板子上,第一步就是insmod然后报错:Unknown symbol。那个报错的本质,就是内核符号表里找不到模块依赖的符号——要么模块没加载,要么依赖关系没建立。
2. 命令语法和常用参数拆解
2.1 先看完整语法格式
depmod的语法并不复杂,通用格式如下:
depmod [选项] [内核版本]如果不指定内核版本,它默认操作当前正在运行的内核对应的模块目录/lib/modules/$(uname -r)。如果在嵌入式开发环境里交叉分析,你可以用-b指定根目录。
执行depmod -a时会经历这几个过程:
- 确定目标内核目录;
- 递归遍历目录下全部
.ko文件; - 打开每个模块文件,读取ELF结构里的
.modinfo段和符号表; - 构建符号依赖图,生成依赖文件;
- 将结果写入目录顶部(或指定目录)。
2.2 必知必会的常用参数
表格里整理了我实际使用中频率最高的几个参数:
| 参数 | 作用 | 典型使用场景 |
|---|---|---|
-a/--all | 扫描目标目录下所有模块 | 新装驱动模块后刷新依赖 |
-A/--quick | 只对比文件修改时间,有变化才重新生成 | 脚本里快速检查更新 |
-b <目录>/--basedir | 指定根目录(常用于交叉编译环境) | 嵌入式开发,分析目标板rootfs |
-F <System.map> | 指定内核符号表文件 | 分析外部内核符号依赖时必须用 |
-n/--dry-run | 只打印结果不写文件 | 检查依赖关系是否正确 |
-w | 扫描时同时输出警告信息 | 排查未知符号告警 |
这里特别强调-F System.map。很多人交叉编译内核模块到ARM板子时,直接执行depmod -b /path/to/rootfs,结果模块依赖信息不正确,或者出现大量Unknown symbol警告。原因就是depmod没有拿到目标内核的System.map符号表,无法判断哪些符号是内核本身提供的。正确姿势是加-F:
depmod -b /path/to/rootfs -F /path/to/kernel/System.map 4.19.xx3. 实操上手:从编译模块到正确刷新依赖
3.1 完整链路:编译 → 安装 → depmod → modprobe
我见过太多新手在编译模块后直接手动把.ko拷进/lib/modules/$(uname -r)/,然后运行insmod各种报错,或者modprobe直接提示找不到模块。问题就出在漏了depmod这一步。
正确流程应该是下面这样:
- 编译模块:
make -j$(nproc)- 安装模块到系统目录:
sudo make modules_install这个操作会把模块复制到/lib/modules/$(uname -r)/extra/或对应子目录,同时生成modules.order和modules.builtin等文件。
- 刷新模块依赖关系:
sudo depmod -a- 加载模块:
sudo modprobe mymodulemake modules_install这一步很多发行版并不会自动执行depmod,所以第3步必不可少。如果不刷,modprobe mymodule大概率提示:
modprobe: FATAL: Module mymodule not found in directory /lib/modules/5.15.0-xxx模块明明就在目录里,却说找不到,原因就是modules.dep里没有索引记录。
3.2 手动拷贝模块时的处理方式
如果不想执行完整的make modules_install,而是自己把编译好的mymodule.ko放到某个目录下,比如/lib/modules/$(uname -r)/extra/,那也要重新刷新:
sudo mkdir -p /lib/modules/$(uname -r)/extra sudo cp mymodule.ko /lib/modules/$(uname -r)/extra/ sudo depmod -a sudo modprobe mymodule注意modprobe默认搜索路径是根据模块目录递归扫描的,不局限于某个子目录。所以只要你放对位置并刷新,指定模块名(不带.ko后缀)就能加载。
再补充一句:模块名的连字符和下划线问题也常让人踩坑。内核模块文件叫my-module.ko,但modprobe查询时用的是my_module,因为内核模块名规范是统一把-转换成_。比如modprobe my-module和modprobe my_module效果一样。
3.3 使用-A快速刷新和-n预检查
有些场景下全量扫描所有模块很浪费时间,尤其服务器上模块数量上千。如果只是刚刚放入一个新模块,可以用-A只扫描有变动的模块:
sudo depmod -A它的机制是比对.modinfo的mtime,只处理新文件或修改过的文件,速度快很多。
在改动系统前如果想先看看结果对不对,用-n空转一把:
depmod -n | head -50这条命令会读取模块并打印出即将生成的modules.dep内容,但不会实际改动任何文件。对于谨慎型选手和运维变更前检查,非常好用。
4. 场景实战:嵌入式开发、内核升级与自动加载
4.1 交叉编译环境里依赖关系分析的完整操作
嵌入式Linux开发中,我们经常在x86主机上交叉编译出arm架构的.ko,再拷贝到开发板的rootfs里用。这时候不能直接跑宿主机上的depmod -a,而是要用-b指定目标板的根目录。
假设目标系统rootfs放在/home/user/rootfs,内核版本是4.19.100,交叉编译出hello_drv.ko并拷贝到/home/user/rootfs/lib/modules/4.19.100/,那么正确刷新依赖执行的命令是:
depmod -b /home/user/rootfs -F /home/user/kernel/System.map 4.19.100-b告诉depmod把/home/user/rootfs当作/来看待,所以内部路径会映射到/home/user/rootfs/lib/modules/4.19.100/。-F指定System.map,让depmod知道内核自身提供的符号集合,避免把内核符号误判为模块依赖。
这一步漏了-F,最常见的现象就是生成的modules.dep里,明明只是依赖内核导出的函数,却被写成依赖某个根本不存在的模块,甚至直接报警告:
WARNING: Symbol 'some_func' not found in kernel module directory到板子上modprobe自然失败。嵌入式平台踩这个坑的人特别多。
4.2 内核升级后没有刷新模块依赖的情况
升级内核也是depmod的高频触发场景。安装新内核后,初始ramdisk和模块目录针对不同内核版本,操作指令稍有区别。
假设新内核版本是6.6.1,安装完成后你检查/lib/modules/6.6.1/,发现模块文件都在,但目录下没有modules.dep。此时:
sudo depmod -a 6.6.1指定内核版本号,就会只对6.6.1目录生成依赖文件。升级后用uname -r确认当前内核版本,再加载第三方驱动模块之前,一定要确保当前版本的目录依赖是完整的。
另外注意,depmod写依赖文件时,会把模块路径写成绝对路径。如果你在chroot环境或容器里操作,输出路径可能和你预期的不一样,这时-b同样能兜底。
4.3 结合modprobe理解完整加载链
为了彻底理解depmod的用处,我建议你手动走一遍加载链,观察各环节的文件变化。
执行strace modprobe 8139cp 2>&1 | grep modules就能看到modprobe读取的关键文件。正常流程大概是:
- 查找
modules.dep,看目标模块是否存在及依赖; - 读取
modules.symbols,解析符号与模块映射; - 通过
modules.alias做设备别名匹配; - 按顺序加载依赖模块;
- 最后加载目标模块本体。
一旦modules.dep缺失或内容不正确,modprobe会立刻报模块找不到。所以你有没有发现,modprobe本质上只做“查表+依赖顺序加载”,真正的“侦察兵”工作是depmod干的。
5. 常见问题与排查技巧实录
5.1 模块存在但modprobe提示找不到
这类报错在运维和嵌入式群里几乎每周都有人问:
modprobe: FATAL: Module xxx not found in directory /lib/modules/5.15.0-91-generic排查思路按顺序来:
- 确认模块文件确实存在于模块目录里:
find /lib/modules/$(uname -r) -name "*.ko" | grep xxx; - 确认
modules.dep是否包含该模块:grep xxx /lib/modules/$(uname -r)/modules.dep; - 如果
grep结果为空,执行sudo depmod -a后再次验证; - 手动尝试
sudo insmod /绝对路径/xxx.ko看是否还有别的报错。
看到这里你应该明白了:第2步和第3步就是最核心的排查动作。我建议在每个模块目录变更后,都用depmod -a刷新一次,宁可多跑一次也不留隐患。
5.2 Unknown symbol错误分析
insmod一个模块时报:Unknown symbol。这是模块使用了内核或其他模块提供的符号,但当前环境中没有对应提供者。
排查步骤:
# 查看模块依赖了哪些符号、由谁提供 modinfo xxx.ko nm xxx.ko | grep ' U 'nm输出中带U标记的就是未定义符号。然后用:
grep 符号名 /lib/modules/$(uname -r)/modules.symbols看这个符号是否被某个模块导出。如果grep为空,说明符号不存于任何模块,那是内核符号缺失;如果grep到了,再确认对应模块有没有在modules.dep中挂好依赖,或者是否已被成功加载。
在实际设备上,还有一种常见情况是编译模块的主机内核版本和运行环境不一致,导致ABI不匹配、符号地址错位。这种问题depmod没法救,只能重新用目标环境的内核源码或headers编译。
5.3 嵌入式设备上depmod不存在怎么办
很多精简的嵌入式rootfs里根本没有depmod工具,特别是用busybox裁剪出来的系统。替代思路有几种:
- 在宿主机上对rootfs执行带
-b的depmod,生成好依赖文件再打包进rootfs; - 用
busybox depmod,支持基础功能; - 交叉编译depmod工具放进板子;
- 模块数量很少时,手工编辑
modules.dep内容。
我实际做量产系统时,都是在PC端交叉分析完成后,把整个/lib/modules/$(uname -r)目录打包进rootfs,不在板子上跑depmod,效率和可靠性都更好。
5.4 权限和文件属性导致的隐藏异常
还有个别案例比较坑:模块文件权限不对。.ko文件如果所有者和权限异常,depmod虽然能扫描,但modprobe加载时可能由于模块自身vermagic或文件读取问题而失败。
检查方法:
ls -l /lib/modules/$(uname -r)/extra/xxx.ko file /lib/modules/$(uname -r)/extra/xxx.kofile命令输出里能看到模块是ELF 64-bit还是32-bit,以及架构信息。32位模块放到64位内核里,depmod都会直接跳过或者报警告,后面加载更是必然会失败。
6. 经验心得与进阶操作
6.1 我用depmod踩过最深的坑
聊个我自己的案例。当时在给一块ARM开发板编写SPI驱动,交叉编译完成后把.ko丢进板子/lib/modules/4.19.100/,执行modprobe spi_test报Module not found,但ls明明能看到文件。
排查了一圈发现,问题出在我交叉编译时用的是宿主机的/lib/modules下的depmod,没有用-b指定rootfs,导致依赖文件里记录的都是宿主机路径。比如kernel/drivers/spi/spidev.ko这种相对路径少写了一大截,板子上自然找不到。
后来养成的习惯是:任何嵌入式rootfs的模块操作,都写成一条固定命令:
depmod -b <rootfs路径> -F <内核System.map> <内核版本>再也没在这上面翻过车。
6.2 把这套逻辑用到内核开发调试中
如果你在开发自己的内核模块,depmod -n是非常好的调试伙伴。每次改完模块代码重编译后,先跑一次:
depmod -n | grep 模块名看看新模块是否被正确索引、依赖了哪些模块。它能帮你在insmod之前就发现依赖关系的异常。
如果模块开发中用到EXPORT_SYMBOL导出给别的模块使用,别忘了刷新后检查modules.symbols内容:
grep 导出的符号名 /lib/modules/$(uname -r)/modules.symbols查到记录,说明外部模块才能正常引用它。查不到,说明导出有问题,后面写测试模块怎么也调不通。
6.3 简化运维:把depmod固化到自动化和脚本里
对运维同学来说,与其每次都手动执行,不如放进初始化脚本或Ansible这类自动化工具里。我自己常用的一个习惯是在安装任何第三方驱动后,统一执行:
sudo depmod -a sudo modprobe -r 旧模块 2>/dev/null; sudo modprobe 新模块脚本里注意一个细节:depmod -a执行时如果恰好有模块正在被使用,并不会造成破坏,但如果你紧接着modprobe -r卸载正在运行的模块,业务就中断了。变更窗口内操作,并且先确认没有进程占用模块:
lsmod | grep 模块名6.4 不引入新文件直接检查系统的方法
最后一个实用技巧:在不做任何修改的前提下快速判断系统模块依赖是否完整,可以用:
test -f /lib/modules/$(uname -r)/modules.dep && echo "modules.dep exists"如果要对比两个内核版本的依赖文件是否一致:
diff /lib/modules/5.15.0/modules.dep /lib/modules/6.6.1/modules.dep | head这在升级回滚场景里很有用。回滚内核时如果旧版本依赖文件被误删或覆盖,提前发现能避免重启后网络模块加载失败、网卡起不来的尴尬。
最后再说一句,我个人的经验是:任何涉及模块增删、内核版本变化、rootfs构建的场景,都默认把depmod -a当成和cp、make一样不可或缺的一步。这条命令平时毫无存在感,可一旦你跳过它,各种诡异报错会在深夜加班时挨个找上门来。把这篇文章收藏起来,多实操几遍,把这些“坑”提前踩平,后面你会感谢现在的自己。