ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

清理DKMS幽灵内核:解决旧内核残留模块编译与磁盘占用

2026/10/7 10:13:25 拓冰建站 浏览量
清理DKMS幽灵内核:解决旧内核残留模块编译与磁盘占用 “内核都卸载了DKMS 却还在为它们编译 110M 模块”——这句话原来是我在群里吐槽时随口说的。当时的情况是服务器把旧内核的 linux-image 包卸了uname -r已经切到新内核结果dkms status里还挂着一条旧内核记录。更气人的是只要dkms autoinstall跑一遍它就会真的为那个已经不存在的内核重新编译 i915-sriov-dkms最后在/var/lib/dkms下塞出一个占磁盘 110M 的i915.ko。这问题在玩 Intel 显卡 SR-IOV、PVE 直通、或者自己编译内核的场景里很常见。i915-sriov-dkms 本身是个靠 DKMS 维护的补丁驱动内核一变它就得跟着重编模块体积本来就大一旦碰上“幽灵内核”就是旧内核明明已经不存在DKMS 的记录和/lib/modules残留目录却还在骗人导致白跑编译、白占磁盘、还偶尔让开机 depmod 卡半天。这篇文章就专门说清楚一件事怎么把这种“幽灵内核”从 DKMS 里揪出来彻底清掉并且以后别再让类似问题反弹。适合遇到 dkms status 里有莫名内核版本、模块编译体积异常大、或者手动装过内核不知道怎么收尾的 Linux 用户。1. 先弄明白 DKMS 的工作机制1.1 DKMS 到底在干什么DKMS全称 Dynamic Kernel Module Support设计目标很明确让第三方内核模块能跟着系统内核的安装、升级、卸载自动适配。它的工作方式是这样你把模块源码放到/usr/src/模块名-版本/里面包含一个dkms.conf然后执行dkms add注册。从此以后DKMS 会监听内核包触发的事件。比如你装了一个新内核内核包会跑/etc/kernel/postinst.d/dkms这个钩子DKMS 就会用当前指定版本的模块源码为新内核重新编译并安装一份模块。反过来卸载内核时也有 postrm 钩子理论上会自动把对应的 DKMS 编译产物删掉。编译产物放在/var/lib/dkms/模块名/模块版本/内核版本/这个路径下。也就是说同一份源码每适配一个内核版本就会单独生成一套编译目录。你用dkms status能看到这种记录i915-sriov-dkms/6.1, 5.15.0-71-generic, x86_64: installed i915-sriov-dkms/6.1, 6.2.0-36-generic, x86_64: installed这里每一行代表“DKMS 为某个内核版本编译并安装了该模块”。状态是installed意思是模块已经复制到/lib/modules/内核版本/updates/dkms/下并且 modprobe 能找到它。这个机制本身很好理解。你可以把它类比成装修公司的服务一个客户内核住进新房子装修公司DKMS就按照同一张图纸模块源码给每个房间装插座。问题是当某个客户已经搬走了公司的客户名单里却还留着他的名字下次装修工上门一看发现旧房间的钥匙孔还在于是又吭哧吭哧给空房子装了一套插座。这就是“幽灵内核”的雏形。1.2 为什么内核都卸载了它还在编译很多人一开始和我一样第一反应是“会不会是我没卸载干净”。后来排查多了发现原因其实集中在下面几类手动编译安装的内核不会触发包管理器的钩子。如果你不是通过 apt、dnf、pacman 安装内核而是自己下载内核源码make install那么/lib/modules/下会出现新目录但没有对应的软件包记录。之后你用包管理器卸载旧内核也不会有人通知 DKMS 清理这棵手动编译出来的“裸内核”。包管理器状态不一致。最典型的就是 dpkg。有时候内核镜像包虽然在dpkg -l里显示rc状态也就是“软件包已删除但配置文件残留”但/lib/modules/旧版本目录还在。DKMS 只认目录不认包状态它看到目录就以为内核还活着。文件被占用或者权限不对导致 postrm 钩子没有清理干净。某些老内核模块被进程持有或者/lib/modules下残留了奇怪的符号链接都会让linux-image包卸载时跳过清理步骤。dkms autoinstall的判定逻辑很“傻”。它只检查/lib/modules/下有哪些内核版本目录然后逐个为这些版本安装 DKMS 模块并不会反过去验证/boot/vmlinuz-*是否真实存在。只要残留目录在它就会忠实地把所有模块重新装一遍。所以“幽灵内核”的本质是DKMS 数据库或者/lib/modules文件系统里记录了某个内核版本仍然存在但实际内核已经不可引导、不可使用。2. 定位幽灵内核先诊断再动手2.1 检查当前内核和有效内核不要上来就删目录。先搞清三件事我现在用的内核是哪个、系统里真正有镜像文件的内核是哪些、/lib/modules下有哪些名字。uname -r ls -l /boot/vmlinuz-* ls -d /lib/modules/*/ dpkg -l linux-image-* | grep ^ii我自己习惯这样判断/boot里有vmlinuz-版本同时/lib/modules/版本存在才算一个真实可用的内核。如果/lib/modules/版本存在但/boot/vmlinuz-版本不存在多半是清理时只删了内核镜像没管模块目录。如果两者都不存在但dkms status里还有记录那就是 DKMS 数据库里的孤魂野鬼。还需要留意 dpkg 状态。dpkg -l第二列是状态ii表示正常安装rc表示已删除但残留配置文件。rc状态下即使/boot是干净的/var/lib/dpkg/info/里也可能留着一些 postrm 脚本残留间接影响 DKMS。2.2 dkms status 怎么读才不算白读直接看dkms status的输出dkms status它能列出模块源码的当前状态。当你看到某个模块对应了一个你完全不认识的内核版本先别急着编对照一下刚才的列表# 只看非当前内核的 DKMS 记录 dkms status | grep -v $(uname -r)如果对应内核既不在dpkg -l | grep ^ii里又在/boot里找不到 vmlinuz那这条记录就是准幽灵。比如i915-sriov-dkms/6.1, 5.15.0-71-generic, x86_64: installed但ls /boot/vmlinuz-5.15.0-71-generic # ls: cannot access /boot/vmlinuz-5.15.0-71-generic: No such file or directory那么这个 5.15 就是幽灵。这里有个常见误区installed状态看着很正规容易让人以为“系统还需要它”。其实这个状态只代表 DKMS 曾经为那个内核版本装过不代表内核还在。反过来如果状态是built而不是installed说明编译产物在 DKMS 的工作目录里但没有复制到/lib/modules这种情况同样意味着旧内核实际上没在正常使用。2.3 找出占空间的 110M 模块遇到“模块体积巨大”的问题不能靠感觉要用命令直接量出来。DKMS 编译产物可能散落在两个位置/var/lib/dkms/模块/版本/内核//lib/modules/内核/updates/dkms/你可以这样看du -sh /var/lib/dkms/i915-sriov-dkms/*/ find /lib/modules -path *dkms* -name *.ko -size 50M -exec ls -lh {} \;我遇到过的 110M基本都是i915.ko。这个数字乍一看离谱其实不意外i915 驱动本身就大再加上内核编译时开启了CONFIG_DEBUG_INFOdebug 符号会全部打进.ko文件里。DKMS 默认继承内核配置所以你在/var/lib/dkms看到上百兆的调试版模块说明编译配置里带着调试符号而不是一定编译出错了。搞清楚体积来源后你才会明白单纯删掉 DKMS 记录不够还要处理/lib/modules里已经复制出来的大文件。否则内核目录清理不干净下次depmod -a还是要扫一遍这个巨型.ko。3. 手动清理“幽灵内核”的正确姿势3.1 用 dkms remove 移除对应记录优先级最高的做法是让 DKMS 自己把记录删掉dkms remove i915-sriov-dkms/6.1 -k 5.15.0-71-generic指定/模块/版本和-k内核版本DKMS 会删除/var/lib/dkms下对应的编译目录并刷新模块依赖。如果顺利输出会告诉你 removed。有时候系统里记录的是 5.15.0-71-generic但实际内核目录可能是 5.15.0-71-lowlatency 或者其他 flavor所以先dkms status | grep 5.15看清楚再决定-k参数写什么。如果内核包已经被彻底卸载dkms remove偶尔会报错提示找不到对应内核或者提示 Module is not installed。这种情况不用慌说明 DKMS 数据库本身已经有点乱了直接跳到 3.2 手动清理。3.2 手动删除 DKMS 编译残留目录DKMS 的每个编译残留是一个独立目录路径规律是/var/lib/dkms/模块名/模块版本/内核版本/在这个目录内部还有架构目录比如x86_64/module/。最省事的方法就是直接删掉整层内核版本目录rm -rf /var/lib/dkms/i915-sriov-dkms/6.1/5.15.0-71-generic删完以后执行dkms status depmod -adkms status确认幽灵记录消失depmod -a重新扫描/lib/modules当前实际存在的内核并生成依赖。这一步很关键因为手动删目录不会触发 DKMS 自身的钩子依赖文件可能还留着指向旧模块的引用。手动清理之后/var/lib/dkms里可能还留着一些.build临时目录或者日志。不要到处乱删只删按内核版本命名的目录即可。否则把模块源码目录也删了下次想为当前内核编译时还得重新下载源码。3.3 清理 /lib/modules 残留目录确认 DKMS 记录消失后再处理/lib/modules/旧内核目录。ls -d /lib/modules/5.15.0-71-generic rm -rf /lib/modules/5.15.0-71-generic这个删除逻辑要谨慎。一定先确认当前uname -r不是这个版本/boot/vmlinuz-旧内核不存在dkms status里已经没有任何模块对应这个版本。三者都满足才说明这是一个彻底没有内核镜像的残留目录可以放心删。有些环境里/lib/modules/旧内核删不掉提示 “Device or resource busy”。多半是有进程还在用旧内核模块或者有 NFS 挂载点停留在这个路径下。可以用lsof D /lib/modules/旧内核看看哪个进程占着处理掉占用后再删。如果目标机器是物理机且你能直接控制也可以先重启到新内核确认一切正常后回来再删旧目录。重启后多数占用会自动释放。3.4 处理包管理器的“假安装”状态Debian / Ubuntu 上常见一种情况dpkg -l里内核包是rc状态也就是已经卸载但配置残留。这种残留不影响启动但会影响后面排查视线。dpkg -l linux-image-* | grep ^rc把这些残留包彻底清理掉dpkg --purge --force-all linux-image-5.15.0-71-generic如果系统提示包的状态异常--force-all是一个强制手段平时不建议随便用只有当你明确知道这个内核包已经没用了才用它。使用前最好备份一下/var/lib/dpkg/status。RHEL / Fedora 系则是rpm -qa | grep kernel dnf remove kernel-旧版本3.5 给巨型模块“瘦身”清理完幽灵内核之后如果你决定继续为当前内核保留 i915-sriov-dkms就得想想怎么避免每次编出 110M 的文件。最直接的办法是编译安装后手动 stripfind /lib/modules/$(uname -r)/updates/dkms -name *.ko -exec strip --strip-debug {} \;--strip-debug只剥离调试符号保留代码段和数据段驱动功能不会受到影响。我不建议用--strip-unneeded无差别处理因为有些模块还带.gnu_debuglink之类的额外段过度剥离可能引起 modpost 校验异常。需要注意如果你的内核开启了模块强制签名比如 Secure Boot 环境模块安装时已经附带了签名strip 之后的字节变化会导致签名失效。这时要么关闭签名校验要么用原来用的签名工具重新签一遍。另一个思路是直接从编译参数下手。检查模块的dkms.confcat /usr/src/i915-sriov-dkms-6.1/dkms.conf里面通常有MAKE[0]make ...和CLEAN定义。你可以在MAKE里追加INSTALL_MOD_STRIP1但要注意 DKMS 的构建流程和普通make modules_install不太一样单纯加在MAKE上不一定生效。我实际用下来更稳的是改 POST_INSTALLPOST_INSTALLstrip --strip-debug /lib/modules/${kernelver}/updates/dkms/i915.ko修改后重新执行dkms uninstall i915-sriov-dkms/6.1 -k $(uname -r) dkms install i915-sriov-dkms/6.1 -k $(uname -r)不过dkms.conf会被包升级覆盖。如果你长期维护建议写一个 shell 脚本在每次dkms autoinstall之后统一跑一遍 strip而不是反复改源码包里的配置。4. 实战排查案例一步步把“幽灵”揪出来下面用我自己最近处理的一个 Ubuntu 22.04 机器为例完整记录一遍排查路径。这台机器装了 PVE 内核后来切到 Ubuntu 官方 HWE 内核旧内核 5.15.0-71-generic 已经被 apt 标记删除但问题很快出现。现象是每次内核更新后dkms autoinstall都要花十几分钟反复编译 i915-sriov-dkms。一开始我以为是新内核编译慢后来发现不对劲因为 5.15 内核目录已经被 apt 删了但编译任务里明显还有 5.15 的路径。第一步看全局状态$ uname -r 6.2.0-36-generic $ ls /boot/vmlinuz-* /boot/vmlinuz-6.2.0-36-generic $ ls -d /lib/modules/*/ /lib/modules/5.15.0-71-generic/ /lib/modules/6.2.0-36-generic/这里已经很可疑了。/boot下只有 6.2.0-36 一个 vmlinuz但/lib/modules下还躺着 5.15.0-71-generic。再看 dpkg$ dpkg -l linux-image-* | grep 5.15 rc linux-image-5.15.0-71-generic 5.15.0-71.79 amd64 ...果然是rc状态包早已卸载但配置残留模块目录也没清干净。然后看 DKMS$ dkms status i915-sriov-dkms/6.1, 5.15.0-71-generic, x86_64: installed i915-sriov-dkms/6.1, 6.2.0-36-generic, x86_64: installed5.15 那行的每个要素都符合幽灵特征。为了确认它到底占了多少空间$ du -sh /var/lib/dkms/i915-sriov-dkms/6.1/5.15.0-71-generic 110M $ ls -lh /lib/modules/5.15.0-71-generic/updates/dkms/i915.ko 110M /lib/modules/5.15.0-71-generic/updates/dkms/i915.ko两个地方各占 110M加起来 220M。这还只是一个内核版本如果系统里积攒了三四个幽灵磁盘占用瞬间多出 600M 甚至更多。开始清理。先尝试让 DKMS 自己删$ dkms remove i915-sriov-dkms/6.1 -k 5.15.0-71-generic结果它报了句“Module i915-sriov-dkms/6.1 is not installed for kernel 5.15.0-71-generic”。原因很可能是/var/lib/dkms内部记录和实际目录状态已经不一致。那就手动删rm -rf /var/lib/dkms/i915-sriov-dkms/6.1/5.15.0-71-generic再dkms status5.15 记录已经消失。接着处理包残留dpkg --purge --force-all linux-image-5.15.0-71-generic这里要注意--force-all只在确认系统里没有任何东西依赖这个内核包时用。我这个场景里5.15 已经不在 grub 菜单也没有进程使用安全。最后删/lib/modules目录rm -rf /lib/modules/5.15.0-71-generic depmod -a整个过程中还有个容易踩的坑如果dkms remove和dpkg --purge的顺序反了dpkg 会把dkms.conf相关的 postrm 钩子也触发一遍有时反而会在/var/lib/dkms里留下一个半删除状态。所以我的顺序一直是先dkms remove/ 手动删/var/lib/dkms再dpkg --purge最后/lib/modules。这样每一步都能清晰确认状态。清理完后dkms status只剩当前内核一条记录/lib/modules下也只有当前内核目录。下次dkms autoinstall只编译当前内核时间恢复正常。5. 防止“幽灵内核”反弹日常维护与自动化5.1 内核卸载后的自动清理钩子正常从包管理器安装内核时DKMS 会自动注册 postinst / postrm 钩子。你可以看一下ls /etc/kernel/postinst.d/ ls /etc/kernel/postrm.d/里面通常有dkms脚本。作用就是安装新内核后自动dkms autoinstall -k 新版本卸载内核后自动dkms remove 模块 -k 旧版本。那为什么还会漏两种常见情况内核包卸载脚本先执行把/lib/modules/版本删了但 DKMS 的 postrm 钩子因为顺序问题没有运行成功内核不是包管理器装的而是手动make install压根没有包管理器的生命周期。对于手动编译内核我的建议是编译完make modules_install后马上执行一次所有在册 DKMS 模块的安装dkms autoinstall -k 你刚编译的内核版本同时在笔记本上记下这个版本号。别等到几个月后突然发现/lib/modules下有十几二十个目录那时候清理成本就高了。5.2 防止 DKMS 自动编译“幽灵模块”DKMS 默认的自动安装机制会为/lib/modules下所有存在的内核版本装模块。如果你不想让某个旧内核目录触发编译可以考虑把对应版本的 kernel headers 包也一起清掉。没有 headersDKMS 编译时第一关就过不去会自行跳过那个版本。另外不要轻易改全局 autoinstall。有人为了省事把/etc/kernel/postinst.d/dkms改成不可执行这样确实不会自动编译但以后所有内核升级都不会自动构建模块了。某次内核升级后忘记手动dkms autoinstall重启起来可能就直接没有第三方驱动网卡都不认。这种省事是坑自己。如果你只是不想要 i915-sriov-dkms但还想保留其他 DKMS 模块正确做法是dkms remove i915-sriov-dkms/6.1 --all rm -rf /usr/src/i915-sriov-dkms-6.1--all会移除该模块在所有内核版本下的记录/usr/src里的源码也要删否则 DKMS 的 add 状态还在下次某个钩子触发时可能重新把它捡回来。5.3 针对 i915-sriov-dkms 的特殊提醒i915-sriov-dkms 是 Intel 显卡 SR-IOV 方案里常用的补丁模块。它需要和内核 headers、gcc 版本严格匹配。编译时如果看到/var/lib/dkms下出现 110M 文件不要第一时间怀疑“代码是不是坏了”而是先确认CONFIG_DEBUG_INFO。这个模块比较大还有另一个原因它打了不少补丁包含 SR-IOV 虚拟化相关的虚拟函数配置逻辑代码量本身就不小。所以即使关掉 debug体积也会比内核自带 i915 大不少。你可以在当前内核下看一眼实际值ls -lh /lib/modules/$(uname -r)/updates/dkms/i915.ko # -rw-r--r-- 1 root root 110M ...再对比系统原生模块find /lib/modules/$(uname -r) -path *i915.ko -not -path *updates* -exec ls -lh {} \;如果原生 i915 只有 40M 左右那 110M 里的水分基本都是 debug symbol。只要你不做内核调试就按 3.5 的方式处理。另外i915-sriov 属于补丁性质驱动升级内核后等作者发布兼容版本再用别硬编。我看到过有些人在新内核上强行编译旧 patch结果模块加载后图形界面直接黑屏dmesg刷一堆 “BUG in i915”。老老实实等官方 tag比什么都重要。6. 常见问题速查表现象可能原因处理方式dkms status里出现无法访问的内核版本/lib/modules残留或 dpkg 状态rc先dkms remove对应记录再删/lib/modules最后dpkg --purge --force-alldkms remove报 Module not installedDKMS 数据库与文件系统不一致手动删除/var/lib/dkms/模块/版本/内核目录执行depmod -a模块体积达到 110M内核启用CONFIG_DEBUG_INFOdebug 符号打入.ko安装后strip --strip-debug或改dkms.conf的POST_INSTALL/lib/modules/旧内核删不掉进程占用 / NFS mountslsof D /lib/modules/旧内核确认占用处理完再删dpkg -l显示rc状态内核配置残留dpkg --purge --force-all linux-image-版本卸载内核后重启第三方模块没了DKMS 钩子没跑或源码已删重新dkms add /usr/src/模块-版本后dkms install -k $(uname -r)Secure Boot 环境下 strip 后模块加载失败签名被 strip 破坏重新签名模块或关闭 Secure Boot 校验7. 一个帮我省过不少事的小脚本最后分享我平时用的一个小脚本。它不会直接帮你删只负责快速列出可疑的“幽灵内核”#!/usr/bin/env bash current$(uname -r) for kdir in /lib/modules/*/; do kver${kdir%/} kver${kver##*/} [ $kver $current ] continue if [ ! -f /boot/vmlinuz-$kver ]; then echo ghost kernel: $kver echo - dkms records: dkms status | grep $kver || echo (none) fi done这个脚本只看两点非当前内核 /boot下没有 vmlinuz。两个条件同时成立基本就是幽灵。注意这只是定位工具真正删除前仍然要人工确认一遍。我个人在实际操作中的体会是DKMS 本身是个很“老实”的工具你给它什么状态它就照做什么事。大部分幽灵内核不是 DKMS 的 bug而是手动操作或者包管理器残留给它喂了假信息。所以解决问题的关键不在“禁用 DKMS”而在“让 DKMS 看到的内核列表和真实环境保持一致”。还有一个小技巧清理完记得顺手看一眼/etc/default/grub里的GRUB_DISABLE_OS_PROBER和update-grub输出。有些环境下/lib/modules残留会影响os-prober扫描结果导致启动菜单里出现一个根本无法启动的老内核项。清完幽灵后执行一次update-grub能让启动菜单同步干净。如果你也是 i915-sriov-dkms 的重度用户建议把模块编译放到专门维护的内核版本上不要同时给多个内核版本常开 DKMS 构建。批量编译一时爽等积攒了五六个 110M 的残留目录光磁盘占用就够你头疼的了。