先说个场景你以为什么都清理干净了磁盘却莫名其妙少了100多兆。跑了一圈du最后定位到/var/lib/dkms和/usr/src下面躺着几个幽灵目录里面全是旧内核的模块编译产物。更气人的是那个内核明明已经apt purge卸载了DKMS 却在后台一遍遍为它重编 i915-sriov-dkms 这个模块。这事我最近就踩了一回花了差不多半天时间才把根子挖出来。一句话总结问题内核镜像卸载了但 DKMS 的注册元数据里还留着一份“幽灵内核”记录。它指向一个已经不存在的/lib/modules/版本号目录可 DKMS 每次触发重建时都会尝试往里面塞模块。如果你恰好装了 i915-sriov-dkms 这种体量夸张的显卡虚拟化模块一次编译就能生成上百 MB 中间文件攒久了相当可观。这篇文章把我完整的排查思路和清理步骤写下来给同样掉进坑里的朋友一个可以直接照抄的方案。1. 先搞清楚 DKMS 为什么盯着老内核不放1.1 DKMS 的工作方式一句话就能讲透DKMS 的全称是 Dynamic Kernel Module Support思路其实不复杂。它给第三方内核模块建立了一个“登记-编译-安装-升级”的完整生命周期。每次系统安装新内核、或者内核版本发生变化时/etc/kernel/postinst.d/dkms这套钩子脚本会被调用DKMS 会扫描自己手里有哪些模块需要针对新内核重新编译然后自动跑一遍configure、build、install。关键点在这里DKMS 判断“是否需要为新内核编译”时依赖的是自己的注册表而不是内核是否真实存在于磁盘上。这个注册表放在/var/lib/dkms目录下每个模块、每个版本都有自己的子目录里面记录了它曾经为哪些内核版本构建过模块。如果卸载内核的过程出现异常比如包管理器的触发链断掉了、DKMS 的 postrm 脚本没跑完注册表里那条记录就会残留下来。这时候系统里就出现了一个“幽灵内核”——它没有对应内核镜像没有 System.map更没有任何启动入口但 DKMS 依然认为它是合法的安装目标。每次你手动执行dkms build、dkms autoinstall甚至只是安装了另一个无关的内核模块包都可能触发 DKMS 去为这个幽灵内核重新编译。你根本没察觉它却在后台默默生产一堆二进制文件。1.2 i915-sriov-dkms 这类模块为什么更容易“阴魂不散”如果说普通模块比如virtualbox-dkms、nvidia-dkms的残留只是几个零碎文件那 i915-sriov-dkms 就是典型的“重量级钉子户”。这个模块本质上是给 Intel 核显打 SR-IOV 补丁后重新编译出的 i915 驱动变体用在虚拟化环境里做 GPU 直通和 vGPU 拆分。它的构建链特别长涉及从头生成i915.ko、一堆固件头文件、调试符号以及各种中间.o文件。我这边实际编译一次落盘的中间产物加最终模块动辄就是 100-120M。如果你有多个内核版本残留每个版本都留一份数值翻倍很轻松。所以标题里那个“110M”并不夸张这只是 i915-sriov-dkms 的正常单核编译体积。另一个让它更难清理的原因在于这个模块经常是从 Git 仓库手动 clone 下来再注册进 DKMS 的安装方式通常是make dkms-install或手动复制源码目录到/usr/src而不是通过官方 apt 源安装。这导致包管理器对它根本没有记录apt purge内核时压根不知道要顺带处理它。你以为是系统级的卸载流程在管理它实际上它完全游离在包管理之外只能全靠手动收拾。2. 现象确认怎么判断“幽灵内核”真的存在2.1 磁盘占用异常110M 是怎么来的排查的第一步是确认现象。如果你也怀疑自己的机器里有这种问题先跑一句最直接的命令看看/usr/src和/var/lib/dkms各占了多大空间sudo du -sh /usr/src/* /var/lib/dkms/* 2/dev/null | sort -rh | head -20正常系统里/usr/src下只有少量头文件和模块源码包总量通常不会超过几百 MB。但如果你看到类似下面的输出就要警惕了110M /usr/src/i915-sriov-dkms-6.8.12-1-pve 108M /usr/src/i915-sriov-dkms-6.8.8-1-pve 105M /usr/src/i915-sriov-dkms-6.8.4-2-pve 98M /var/lib/dkms/i915-sriov-dkms/5.2.3/6.8.8-1-pve/x86_64/module这个模式非常典型源码目录一个版本一个构建产物目录互相独立彼此不共享。DKMS 在设计上选择了“每内核、每架构、每版本”隔离的目录布局好处是互不干扰坏处就是一旦残留空间浪费成倍增长。2.2 dkms status 输出里藏着关键线索确认完空间占用接下来看 DKMS 自己怎么描述状态dkms status正常输出每一行是“模块名/版本, 内核版本, 架构: 状态”。比如i915-sriov-dkms/5.2.3, 6.8.8-1-pve, x86_64: installed i915-sriov-dkms/5.2.3, 6.8.4-2-pve, x86_64: installed如果内核已经卸载但这行状态仍然显示installed甚至built那八成就是幽灵记录了。还有一个更直接的信号你试着对所有内核重新编译一次模块会看到它真的去访问一个早已消失的路径。运行sudo dkms autoinstall或者干脆sudo dkms build i915-sriov-dkms/5.2.3 -k 6.8.8-1-pve观察输出。如果它报错说某个头文件目录不存在或者收尾时无法安装到/lib/modules/6.8.8-1-pve/updates/dkms/就说明 DKMS 正在为一个已经不存在的内核忙碌——它自己却没有这个概念。2.3 顺着模块路径反查内核版本还有一条排查路径直接看模块安装到哪个目录去了。DKMS 安装完成的.ko文件会放在/lib/modules/$(uname -r)/updates/dkms/之类的路径下。如果/lib/modules里存在某个版本的目录但对应的/boot/vmlinuz-版本或/boot/config-版本已经不见了那这个模块目录就是纯孤儿ls /lib/modules/ ls /boot/vmlinuz-* /boot/config-* 2/dev/null两列信息一对哪些内核版本只有模块目录、没有实际内核镜像一眼就能看出来。这些“只有modules目录没有内核本体”的版本正是幽灵内核候选对象。3. 排查定位一步步找出残留源3.1 从 /lib/modules 反向比对这一步是核心排查动作。打开终端一口气列出当前所有内核模块目录和 boot 目录下的内核镜像做交叉比对。我用一句话判断凡是/lib/modules/version有但/boot/vmlinuz-version没有的就标记为可疑。被标记出来的版本需要用下面的命令确认内核包是否已经被卸载干净dpkg -l | grep linux-image-6.8.8-1-pve如果输出里没有任何已安装记录只有rc状态表示配置残留或者干脆空输出那就实锤了对应内核已经不在系统中只是还有一堆模块文件赖在/lib/modules下面。这一步是整个排查的“破案”环节它能帮你搞清楚到底有几个幽灵内核。3.2 翻开 /usr/src 查源码副本接下来检查源码。DKMS 模块的源码一般会放在/usr/src/module-version命名规则是“模块名-版本号”中间用连字符。i915-sriov-dkms 这种从 Git 仓库手动接入的模块情况更特殊源码目录名可能直接就是i915-sriov-dkms-版本而且里面除了源码还带着.git目录和完整的构建脚本。排查这里时重点看两点。第一每个源码目录对应哪个内核版本如果源码目录本身不区分内核版本DKMS 设计上是共享源码、分目录编译那就要看/var/lib/dkms下的构建副本。第二这个源码包是否还被任何 DKMS 记录引用。判断方法是看/var/lib/dkms/module/version/source这个符号链接指向哪里。如果符号链接已经断裂或者指向一个存在但你不想保留的路径它就是在给幽灵记录当“供血源”。3.3 查看 /var/lib/dkms 的元数据DKMS 的注册元数据才是幽灵记录的“老巢”。/var/lib/dkms/module/version/下面的内容结构大致如下build/每次编译生成的中间文件体积大头基本都在这里source/指向/usr/src下源码的符号链接kernel-版本-架构/记录该内核下模块的构建配置和安装状态dkms.conf模块编译配置文件mk_driver_image、kernel等辅助文件真正要清理的是把这些元数据和上面两节查到的孤儿版本对应起来。如果dkms status显示某个模块为某个已经卸载的内核构建过而这个模块又没有其它内核还在使用就可以针对性地移除它。3.4 确认内核是否真的卸载干净有时候你以为卸载了其实没卸干净。特别是 PVE 这类基于 Debian 的系统内核包分为linux-image、linux-headers、linux-modules等多个子包调用链稍微一断某个子包就可能残留在系统里。我刚才提到过dpkg -l输出中的rc状态这里再补充一下它的含义rc表示包已经被移除remove但配置文件还没清理purge。如果残留的是linux-headers-*包它的头文件目录/usr/src/linux-headers-version可能还在。而DKMS 编译时找的正是这个头文件目录不是/boot下的内核镜像。所以可能出现一种更隐蔽的情况内核本体卸了头文件包却留着DKMS 依然能顺利编译。这种状态下光看/lib/modules对不上号还不够必须把/usr/src/linux-headers-*也列入检查清单。4. 清理实操把“幽灵内核”连根拔起4.1 先给系统做快照或备份在动 DKMS 和内核目录之前强烈建议先备份。我的习惯是至少把/var/lib/dkms和/usr/src里与 i915-sriov-dkms 相关的目录做一次复制用不上也能图个安心。尤其是你还要用这个模块做 GPU 虚拟化的话删错一个源码目录意味着要重新从 Git 仓库拉取补丁集重新配置编译链那比清理残留痛苦多了。如果机器是虚拟机拍个快照是最省事的物理机的话用tar把相关目录打包到外部磁盘也够了。这一步从来不是浪费时间我见过太多人在清理时误删了还在使用中的内核模块源码事后只能干瞪眼。4.2 用 dkms remove 清掉模块记录最正规的动作是用 DKMS 自己的命令删除模块记录而不是直接rm -rf目录。命令有两种粒度先按模块版本全清sudo dkms remove i915-sriov-dkms/5.2.3 --all如果希望只删某个内核版本对应的记录可以指定-ksudo dkms remove i915-sriov-dkms/5.2.3 -k 6.8.8-1-pve--all会移除该模块该版本在所有内核下的构建记录并把build/、kernel-*等子目录一并清掉但不会自动删除/usr/src下的源码目录。这是 DKMS 的一个设计取舍它默认你未来可能还要重新注册这个模块所以保留了原始源码。实际上dkms remove执行完之后/usr/src/module-version还是会在。如果执行时抛错比如提示Module ... is not registered说明注册表里其实已经没有这条记录了那问题单纯出在残留目录上我用 4.3 节的方法直接手动清理。4.3 手动删除源码与构建目录dkms remove清完注册记录后剩下的是磁盘目录级别的残留。这一步要胆大心细先列清楚再删sudo rm -rf /var/lib/dkms/i915-sriov-dkms sudo rm -rf /usr/src/i915-sriov-dkms-*注意/var/lib/dkms/i915-sriov-dkms是模块在 DKMS 下的总目录里面包含所有版本和所有内核的记录/usr/src/i915-sriov-dkms-*是源码目录。这两处删完基本就把模块在系统里的痕迹清除干净了。但在删之前务必确认这个模块当前没有在内核中被加载。检查方法lsmod | grep i915如果i915还在被加载说明还有虚拟机正在使用 SR-IOV 拆分出来的 vGPU要么先停掉相关 VM要么跳过清理当前运行内核对应的模块目录只清理已卸载内核的对应目录。这里很容易翻车你正在用的内核可能也装了同一个模块直接全删会导致下次重启后模块丢失。4.4 清理 /lib/modules 下的孤儿内核目录解决了模块记录接着处理/lib/modules下的孤儿目录。用第一节做过比对得到的结果删除那些没有对应内核镜像和头文件的目录sudo rm -rf /lib/modules/6.8.8-1-pve这里要特别强调千万别用rm -rf /lib/modules/$(uname -r)那是你当前正在跑的内核。删除前先uname -r看清楚当前版本排除掉它再执行。另外有些目录里可能含有别的模块文件比如第三方闭源驱动删之前最好du -sh看一看内容和大小确认确实是残留再动手。4.5 检查引导与 initramfs 残留内核卸载后/boot下通常还留着对应版本的initrd.img-version和config-version这些一般是linux-image包的 postrm 脚本没跑干净导致的。虽然它们和 DKMS 没有直接关系但既然要彻底清理“幽灵内核”最好一并处理sudo rm -f /boot/vmlinuz-6.8.8-1-pve /boot/initrd.img-6.8.8-1-pve /boot/config-6.8.8-1-pve /boot/System.map-6.8.8-1-pve之后顺手把 GRUB 菜单刷新一下避免启动项里残留指向不存在内核的条目sudo update-grub这一步做完/boot下的陈旧启动文件也消失干净了不光是磁盘空间连启动时的隐患都一起排掉。4.6 验证空间回收与模块状态清理完成验证一下成果。先看 DKMS 状态dkms status如果没有 i915-sriov-dkms 的残留条目说明注册层面的问题解决了。接着看磁盘空间sudo du -sh /usr/src /var/lib/dkms /lib/modules df -h /对比清理前后的数值能直观看到 110M 甚至更多空间被释放。如果还剩下一些零碎文件通常不影响使用。最后建议重启一下或者至少重载一下模块框架确认系统能正常启动、i915 驱动能正常加载。5. 常见问题与避坑实录5.1 dkms remove 操作失败怎么办我在清理过程中实际碰到过dkms remove失败的情况。报错信息类似Error! The module ... is not registered.这时候不用慌说明 DKMS 注册表里已经没有该模块记录了纯粹是目录残留。直接跳过我上面说的 4.3 节手动删/var/lib/dkms/i915-sriov-dkms和/usr/src/i915-sriov-dkms-*即可。还有一种报错是/var/lib/dkms/module/version/source符号链接指向的源码不存在通常发生在你手动挪动过源码目录之后。解决方式也很简单把 source 链接重新指到正确的源码位置或者删掉整个版本目录重新注册。5.2 卸载内核的正确姿势这次的问题根子还是出在卸载过程的异常上。正确做法是用包管理器卸载内核比如 Debian/Ubuntu 系sudo apt purge linux-image-6.8.8-1-pve sudo apt autoremovepurge会连带移除配置文件和 postrm 触发脚本autoremove会清理变成孤儿的依赖包包括linux-headers、linux-modules等。DKMS 的正式卸载流程依赖postrm.d脚本里的钩子只有走包管理器卸载这些钩子才会被正确触发。如果你图省事直接rm -rf /lib/modules/version或者手动删/boot/vmlinuz-*包管理器根本不知道这事后续各种残留就是这么来的。另外卸载前先查一下当前用的是哪个内核避免把正在运行的内核卸载掉uname -r5.3 DKMS 自动为新内核编译模块的设置在哪如果你希望彻底关掉 DKMS 对某些模块的自动编译可以改配置文件。DKMS 的自动构建行为由/etc/dkms/framework.conf和模块自己的dkms.conf共同控制关键参数是AUTOINSTALL。把它设置成false就不会每次新内核安装时自动触发编译了。但说实话多数情况下不需要彻底关闭自动安装。i915-sriov-dkms 这种模块恰恰是自动编译才能保证每次内核升级后 GPU 虚拟化功能可用。更合理的做法是定期检查dkms status发现异常条目尽早处理而不是因噎废食地关闭整个机制。5.4 防止下次再中招的几个习惯这次踩完坑我给自己立了几条规矩分享给大家参考第一内核升级后别急着卸载旧内核至少保留一个可用内核作为回退方案。通常在确认新内核下所有功能正常包括 SR-IOV 虚拟化之后再统一清理旧内核。第二手动安装的 DKMS 模块要做好记录。这类模块在包管理器里没有痕迹特别容易变成“漏网之鱼”。我习惯在/opt/notes/下保存一份安装命令和时间卸载时照着清单处理。第三把 dkms status 纳入定期巡检哪怕只是一条简单的 cron 任务记录输出变化也能在残留累积成磁盘灾难之前发现问题。最后再分享一个小技巧清理完 DKMS 残留之后可以顺手看看/var/lib/dkms下有没有其它模块的陈旧记录比如不用的网卡驱动、虚拟化增强工具等。这种“全面体检”一次做完能省去日后不少麻烦。我用同样流程处理掉了一个旧版 VirtualBox 模块的残留又释放了将近 200M 空间。