
如果你在 Linux 服务器上敲过命令大概率见过lsmod这个命令但很多人压根没在意过它觉得不过就是个列模块的。实际上在排查驱动加载异常、设备不识别、内核模块冲突这类问题时lsmod往往是第一个要敲的命令。它和modprobe、modinfo、rmmod、depmod这些命令配合起来就是一整套 Linux 内核模块管理工具链。这篇文章我把它们从头到尾讲一遍不仅讲命令怎么用更讲输出怎么读、依赖怎么理、坑怎么避。适合刚入门的小白也适合想彻底搞懂模块机制的中级用户内容尽量做到拿到就能用用完能理解原理。1. lsmod 入门先搞懂内核模块是怎么一回事1.1 内核模块机制为什么需要动态加载Linux 内核本身是一个完整的程序但并不提倡把所有的功能都编译进内核。原因很简单硬件种类太多文件系统类型太多网络协议栈的扩展也太多如果全部编进去内核镜像会变得巨大而且每一次功能改动都要重新编译、重启机器代价太高。所以内核设计了一套模块机制把一些功能做成独立的 .ko 文件Kernel Object按需加载。比如你插了一张新网卡系统需要对应驱动时就把网卡驱动的 .ko 文件插入内核你暂时不用某个功能也可以把它从内核里卸载掉。这样内核保持精简驱动和功能的扩展也灵活得多。这里我用生活里的例子做个类比把内核本身想象成酒店的总配电箱模块就是各种即插即用的电器——需要用微波炉插上不需要拔掉。而lsmod干的事就是帮你查看当前有哪些电器已经插上电、各自功率多大、被谁占用了。很多 Linux 管理问题本质上都是在跟这套“插拔”机制打交道。1.2 lsmod 的数据来源/proc/modules 的秘密很多看似神奇的命令其实背后就是读某个文件。lsmod也不例外它实际上读取的就是/proc/modules这个文件。你可以用下面两种方式对比一下lsmod cat /proc/modules输出内容基本一致只是格式略有差别。/proc/modules由内核虚拟文件系统实时生成记录当前内核中所有已加载模块的信息。换句话说lsmod本身并不做太多事情内核早就把模块列表准备好放在那里了lsmod只是把这个文件里的内容格式化后展示给你。这也是为什么在排查问题时我通常先cat /proc/modules看一眼原始格式因为有时候lsmod展示的字段经过整理反而让人忽略了某些细节。比如原始文件里每个字段用空格分隔脚本处理时直接解析这个文件比解析lsmod的输出更稳定不容易受终端宽度和换行影响。提示/proc/modules只反映当前内核实例的模块加载状态。如果你更新了内核需要重启到新内核之后才能看到新内核环境下的模块情况。同时/proc/modules里的模块数量会随着硬件接入、驱动绑定、防火墙规则变化而动态增减所以使用姿势要正确避免误判。2. 读懂 lsmod 输出Module、Size、Used by 三列字段拆解与实战解读2.1 三列字段分别代表什么跑一下lsmod输出会分成三列看起来很简单Module Size Used by bluetooth 720896 0 usb_storage 77824 0 vfat 24576 1 ext4 659456 3第一列Module是模块名也就是去掉 .ko 后缀后的文件名。第二列Size是模块占用的内存大小单位是字节。这里注意并不是模块文件在磁盘上的大小而是模块被加载进内核之后代码段、数据段等各段占用的总内存量。对排错来说这一列主要用来看看到底哪些模块吃内存虽然单个驱动模块一般也就几十到几百 KB但如果加载了大量无用模块累积起来也不小。第三列Used by是最关键的表示这个模块被多少个进程或其他模块引用。如果数字是 0说明当前没有任何人在用如果大于 0后面还会列出到底是哪些模块在用这一点在排查依赖关系时特别有用。很多刚接触 Linux 的同事一上来就盯着Used by后面的数字纠结其实重点应该放在后面的小列表上那才是真正的“因果链”。2.2 实战在服务器上跑一次 lsmod看看输出怎么读拿一台比较干净的 CentOS 7 服务器举例执行lsmod后往往有一段和防火墙、网络相关的模块输出。假设你有这么一段Module Size Used by xt_conntrack 16384 1 iptable_nat 16384 1 nf_nat 36864 1 iptable_nat nf_conntrack 110592 3 nf_nat,iptable_nat,xt_conntrack这一段能看出一个典型的依赖链xt_conntrack被 1 个模块使用iptable_nat被 1 个模块使用nf_nat被iptable_nat使用而nf_conntrack被nf_nat、iptable_nat、xt_conntrack三个模块同时使用。看起来有点绕但逻辑很清楚底层模块被上层模块引用上层模块被更上层的模块引用。这个信息在排查防火墙规则失效、网络连接追踪异常时很有价值。如果我想卸载nf_conntrack直接rmmod nf_conntrack大概率失败因为还有三个模块在引用它。你必须先卸载引用方的模块才能卸载被引用的底层模块这和程序依赖库的道理是一模一样的。实践中很多人在/etc/modprobe.d/下改了配置却发现不生效就是因为没有顺着这条引用链把所有相关模块一起处理。2.3 Used by 不等于进程数几个容易误读的地方初学者容易把Used by里的数字理解为有多少进程在用这个模块这是不对的。这个数字表示引用计数也叫 use count。引用者既可以是进程也可以是内核中的其他模块还可以是因为某个硬件设备仍然在位而产生的引用。比如一个 USB 存储驱动只要 U 盘插在机器上相关模块的引用计数就可能不为 0U 盘拔掉之后过一会儿引用计数才可能归零。举个例子USB 存储驱动usb_storage的引用计数为 0不代表 U 盘不能用而是当前没有 U 盘设备挂载在系统上。一旦你插入 U 盘引用计数可能马上就变成 1。相反如果一个模块的引用计数一直居高不下但又不知道谁在用那排查起来就很头疼这种情况我在后面的常见问题部分会专门讲。理解了这个机制再去看lsmod的第三列就不会再被数字误导了。3. 与 lsmod 搭配使用的模块管理命令全家桶modinfo、insmod、rmmod、modprobe、depmodlsmod只是查看真正和它组成完整工具链的是下面这几个命令modinfo、insmod、rmmod、modprobe和depmod。它们各自的分工不同缺一不可。实际工作中我很少单独只用一个命令基本都是几个命令配合着来。3.1 modinfo查看模块的元信息modinfo用于查看模块的详细信息包括模块的路径、描述、作者、许可证、依赖关系等。基本用法modinfo e1000 modinfo /lib/modules/$(uname -r)/kernel/drivers/net/ethernet/intel/e1000.ko第一种写法会从默认模块路径下自动查找第二种指定了具体文件。输出大致如下filename: /lib/modules/5.14.0-362.8.1.el9_3.x86_64/kernel/drivers/net/ethernet/intel/e1000.ko license: GPL description: Intel(R) PRO/1000 Network Driver author: Intel Corporation, linux.nicsintel.com depends: retpoline: Y name: e1000 vermagic: 5.14.0-362.8.1.el9_3.x86_64 SMP mod_unload这里最需要注意的是depends字段。如果它为空说明这个模块不依赖其他模块如果非空会列出它依赖的模块列表用逗号分隔。这个字段和lsmod输出里的Used by正好是反过来的关系——depends是向下找依赖Used by是向上找引用者。两配合着看依赖关系就能理得明明白白。license字段也要留意很多驱动是 GPL 协议如果你加载非 GPL 的模块并且该模块使用了 GPL 导出的内核符号内核会给出 tainted 警告。简单说模块加载后系统可能被标记为“污染”状态这个标记在某些内核调试场景下要特别注意。如果你只是普通使用一般不影响但如果后续要提交内核 bug 或开启某些安全模块就可能会被拦一道。3.2 insmod 与 rmmod最原始的加载与卸载insmod和rmmod是最底层的两个命令用法最简单但也最容易踩坑。insmod /path/to/module.ko rmmod module_name注意insmod必须指定模块文件的完整路径而且它不会处理模块依赖。举个例子你想加载nf_nat它依赖于nf_conntrack。如果你直接用insmod nf_nat.ko内核会报错提示缺少nf_conntrack。你必须自己先把依赖模块全部按顺序加载好再加载目标模块。这个操作在模块很多时会变得非常繁琐一不小心就漏依赖。rmmod则按模块名卸载同样不管依赖关系。如果目标模块正被其他模块引用直接卸载会报错rmmod: ERROR: Module nf_conntrack is in use by: nf_nat,iptable_nat,xt_conntrack这个报错信息其实很友好直接把谁在用它列出来了。按照列表把引用方一个个卸载或者停用再回来卸载目标模块就能成功。实际工作中我很少直接使用insmod除非是加载自己编译的、还没有被系统模块管理机制收录的驱动。绝大多数场景下modprobe才是正确选择。3.3 modprobe推荐首选的智能模块管理工具modprobe是模块管理里最常用、也最聪明的工具。它和insmod最大的区别在于它可以自动解析模块依赖并在加载目标模块之前先把依赖模块按正确顺序加载好。加载模块modprobe nf_nat这条命令会先检查nf_conntrack有没有加载如果没有就先加载它再加载nf_nat。一切正常的话命令无输出返回码为 0。卸载模块modprobe -r nf_nat-r表示 remove它同样会检查依赖关系如果发现有其他模块还在引用目标模块也会拒绝卸载并给出警告。modprobe还支持从配置文件中读取模块参数。比如加载某个驱动时要传入参数可以直接modprobe video mode1或者把参数写进/etc/modprobe.d/xx.conf文件里格式为options video mode1这样系统启动加载模块时也会自动使用这些参数。我自己的习惯是凡是需要带参数加载的模块都写进配置文件而不是每次手工敲这样重启后行为一致不容易忘记。3.4 depmod生成模块依赖关系depmod负责分析/lib/modules/$(uname -r)/目录下所有模块的符号引用关系并生成modules.dep及其相关文件。modprobe之所以能自动处理依赖靠的就是这些文件。正常情况下内核或者包管理器会在安装内核和相关驱动后自动运行depmod。但当你手工编译并安装了一个新驱动或者手动把某个 .ko 文件放到了模块目录下就需要手动运行一次depmod -a否则modprobe可能找不到这个新模块或者无法正确解析它的依赖关系导致加载失败。这条命令我一般会在编译安装驱动后立刻执行否则后面排查问题时总会出现“明明文件在那里但 modprobe 就是找不到”的奇怪现象白折腾半天。3.5 lsmod 与各命令的配合使用总结我把它们的配合关系做一个总结命令核心作用与 lsmod 的配合点lsmod查看已加载模块和引用关系排查现状定位谁在用谁modinfo查看模块元信息与依赖判断模块是否在预期路径依赖是否齐全insmod底层加载模块加载后立即 lsmod 验证是否加载成功rmmod底层卸载模块卸载前确认引用计数为 0modprobe智能加载/卸载模块流程化加载加载后 lsmod 确认依赖链depmod生成模块依赖文件新模块安装后必须先执行否则 modprobe 找不到我自己在排查问题时的习惯动作是先用modinfo确认模块信息和路径再用modprobe加载接着立刻用lsmod验证加载状态和引用关系。这套流程下来绝大多数加载问题都能定位到而且每一步都有据可查不会出现“模块到底加载没加载”的糊涂账。4. 实操模块的加载、卸载与开机自动加载配置4.1 加载模块的标准流程理论上你只需要一条modprobe命令就能把模块装上但标准流程绝不止这一下。我这里总结一套在实际服务器上反复验证过的流程。第一步确认模块文件存在并查看基本信息和依赖modinfo module_name如果输出里没有filename字段或提示找不到文件说明模块不在系统默认路径下需要先更新依赖或补齐驱动。第二步加载模块modprobe module_name如果返回非 0不要硬扛先看dmesg | tail -30。内核加载模块失败的原因十有八九写在 dmesg 里。第三步验证模块是否成功加载以及对系统的实际影响lsmod | grep module_name dmesg | tail -20有时候模块加载成功但硬件设备仍然没有识别这时还要检查/sys或lspci等命令的输出确认设备是否真的绑定了驱动。比如加载网卡驱动后光lsmod看到驱动还不行还要去看网卡接口有没有出现、ethtool能不能读到链路状态。4.2 卸载模块与常见失败原因卸载模块的命令很简单modprobe -r module_name或rmmod module_name但真正让我头疼的是被各种“看不到”的引用卡住。常见的失败原因有这么几种。第一种被其他模块引用这个在lsmod里能看到Used by那列会显示引用方列表按列表把引用方模块先停掉就行。第二种被进程占用有些模块被某个在跑的进程直接打开或持有引用比如某些字符设备驱动程序只要设备文件被进程打开着引用计数就不会归零。这种情况下你得先杀掉占用进程或者关闭对应的设备句柄才能卸载成功。第三种模块正在被使用但lsmod看不到引用。比如某些安全加固类模块或者基于 tracepoint、hook 机制的模块它们可能以隐式方式被引用lsmod显示 0但卸载时仍然报 busy。遇到这种情况优先查 dmesg 和进程的/proc/*/fd看清楚到底是谁在用。如果真的找不出原因又必须卸载重启机器往往是成本最低的解决方案。4.3 开机自动加载与黑名单配置如果你希望某个模块在系统启动时自动加载可以写配置文件。以 RHEL/CentOS 系为例在/etc/modules-load.d/目录下创建一个 .conf 文件一行一个模块名echo 8021q /etc/modules-load.d/8021q.conf这样重启后8021q 模块就会被自动加载。Debian/Ubuntu 也可以放在/etc/modules-load.d/下Debian 传统的/etc/modules文件同样适用。想让模块在启动时自动带参数加载可以在/etc/modprobe.d/下追加 options 配置例如options 8021q debug1想要禁用某个模块可以使用黑名单机制在/etc/modprobe.d/blacklist.conf中加入blacklist pcspkr黑名单的作用是告知系统默认不要自动加载该模块。但注意如果你显式执行modprobe pcspkr黑名单挡不住模块依然会被加载。黑名单只影响自动加载和依赖解析不拦截手动加载操作这个区别要搞清楚。5. 常见问题与排查技巧实录5.1 模块加载失败找不到文件怎么办我遇到过不少次这样的错误modprobe: FATAL: Module xxx not found in directory /lib/modules/5.14.0-xxx大多数原因就三类。一模块名敲错——注意模块名是去掉 .ko 后缀的文件名比如e1000e.ko对应模块名e1000e大小写也要严格一致。二模块文件确实不在当前内核对应的目录下。内核版本和驱动版本不匹配或者新装驱动没有正确安装进/lib/modules/$(uname -r)/kernel/...目录都会导致找不到。三模块依赖文件没有更新手工安装了新驱动后没有执行depmod -a或者执行时用了工具版本不对导致modules.dep里没有记录新模块。这里我建议按顺序执行depmod -a modprobe module_name如果在编译安装新驱动后遇到找不到的问题先补上这一步大概率能解决。我见过不少人在这一步漏掉然后在网上反复搜“modprobe not found”最后发现就是没有更新依赖文件。5.2 模块被占用device busy 怎么处理rmmod报错Module xxx is in use是非常常见的。我的处理思路是先看lsmod输出里Used by是什么针对引用方处理如果lsmod显示 0 但卸载仍然失败则用以下命令排查fuser -v /dev/xxx ps aux | grep xxx找到对应的进程确认是否可以安全关闭后再尝试卸载。如果确认模块完全无用又急着卸载还有一种强制手段用modprobe -rf强制卸载。不过我要提醒强制卸载有风险尤其在驱动正对硬件做 I/O 时可能导致系统卡死或者设备状态异常。我在正式环境里一般不用这招宁可重启机器也不愿意因为一个驱动把整台业务机搭进去。5.3 模块依赖问题依赖顺序和循环依赖Linux 模块是有依赖树的比如加载 A 模块前要先加载 B 模块B 又依赖 C。modprobe帮我们解决了顺序问题但如果你用insmod就得手工按 C-B-A 的顺序来。一旦顺序错了内核会返回 undefined symbol 之类的错误这是比较常见的依赖类报错。看到这种错误第一反应要想到是不是依赖模块没加载或者加载顺序不对。循环依赖属于更罕见的问题。理论上内核模块不允许循环依赖设计上会尽量避免但在交叉编译或外部模块里偶尔会碰到 A 依赖 B、B 又依赖 A 的情况。这种问题处理起来很麻烦实践中我的建议是拆分模块或者把其中一部分依赖固化到内核里绕开循环。你也可以用modprobe --show-depends先看看模块依赖图提前预判问题。5.4 如何判断模块是否真的生效加载完模块别急着走。判断一个模块是否真正生效最直接的方法是两个维度一起看。第一个维度是lsmod输出模块出现在列表里说明已经装载进内核了。第二个维度是内核日志dmesg | tail -20.ko内部的初始化函数如果成功执行通常会在 dmesg 里输出驱动描述信息、设备地址、中断号之类的日志。如果模块出现在lsmod里但 dmesg 没有任何输出有可能是模块初始化函数执行成功但没有输出也可能是设备没有匹配上驱动加载了但没绑定设备。例如加载网卡驱动除了看lsmod还要看ip link或ethtool的输出确认网卡接口真正出现并可用。内核模块这事儿光看模块在不在列表里远远不够要结合设备状态一起判断。很多时候模块加载成功只是一个开始设备能不能正常驱动、链路能不能起来才是真正要关注的。6. 经验总结生产环境操作内核模块的安全姿势与个人心得6.1 生产环境模块操作的正确姿势在正式环境服务器上操作模块我给自己定了几条规矩分享出来。第一操作前先记录当前模块列表。用lsmod /tmp/lsmod_before.txt出问题时能对比。第二加载模块后用dmesg多看几眼内核日志尤其关注是否有异常、警告、panic 前兆。加载驱动模块后如果发现系统卡顿、内存异常飙升第一时间考虑是不是模块与内核版本不兼容。第三需要卸载模块时优先modprobe -r不要直接用rmmod。前者会做依赖检查能避免把正在被引用的模块错杀。哪怕只差一个依赖模块没卸载强制卸载也可能让整个子系统处于不完整状态后续再加载其他模块时会出现难以解释的符号错误。第四不要在业务高峰期做内核模块级别的变更。模块加载/卸载直接作用在内核空间一个操作不当就可能让整台机器重启这个风险绝对值得被认真对待。6.2 安全层面的考虑与可疑模块的识别最后聊一点安全层面的东西。lsmod这类命令不光是给管理员用的任何能在系统上执行命令的用户理论上都能看到加载了哪些模块。虽然这本身不构成直接漏洞但攻击者可以通过模块列表反推内核版本、硬件平台、内核扩展情况从而判断系统中可能存在哪些攻击面。因此在实际安全策略中建议限制普通用户访问内核信息的权限比如通过 sudo 权限控制和 shell 审计来增强安全。更严谨的环境甚至可以监控模块加载事件因为大部分恶意程序会倾向于加载自定义内核模块从而获得内核态的持久化能力。判断系统是否被装了可疑内核模块一种简单有效的办法是定期比对当前lsmod输出和系统安装模块目录/lib/modules/$(uname -r)/kernel/下的文件清单。默认情况下正常加载的模块都必须存在于该目录下如果lsmod里出现了目录中不存在的模块名就要高度警惕了。可以把对比写成一个脚本每天跑一遍输出异常结果发到告警通道这样就不用每次都人工去翻了。根据我个人的经验模块管理这块最容易出的问题不是命令不熟而是对依赖关系不敏感。很多时候你看到一个模块卸载不了第一反应是“强制卸”但正确做法是先顺着lsmod里的引用链把因果理清。搞清楚了谁依赖谁再动手操作基本不会出大乱子。最后再分享一个小技巧在/etc/modprobe.d/下做任何配置前先备份原文件同时用modprobe --show-depends 模块名预演一遍加载路径这样改动有据可查回滚也方便。希望这篇内容能帮你少踩几个坑把 Linux 内核模块管理这套工具真正用明白。