这个报错几乎每个在 Linux 上用 VMware Workstation 的人都会碰到一次,而且大概率是发生在你刚更新完系统内核、重启之后。我当年第一次遇到时也懵了一下,明明昨天还用得好好的,怎么睡一觉起来虚拟机就起不来了。后来搞明白了才知道,这其实是 VMware 在 Linux 下的一种"常态",不是什么硬件坏了,也不是软件装错了,而是内核模块需要跟着新内核重新编译一遍。
这篇文章不绕弯子,直接围绕"更换内核后 VMware 模块编译报错"这件事,把原因、判断方法、完整解决步骤、还有我踩过的坑一次说清楚。不管你是刚入门的小白,还是被这个问题折磨过的老手,照着做基本都能解决。
1. 先搞明白:VMware 为什么非要"编译模块"不可
1.1 报错里说的"modules"到底是什么
VMware Workstation 在 Linux 上运行虚拟机,不是纯用户态程序就能搞定的。它需要向 Linux 内核注入几个内核模块,最核心的是vmmon(负责虚拟机监视器的底层内存和 CPU 虚拟化支持)和vmnet(负责虚拟网络、NAT、桥接这些网络功能)。这两个模块以.ko文件的形式存在,加载进内核后,VMware 才能和 KVM、内核虚拟化层协同工作。
关键问题在于:Linux 内核的版本一变,内核模块的 ABI(二进制接口)就变了。vmmon和vmnet是针对特定内核版本编译出来的,内核升级后,旧的.ko文件和新内核根本不兼容,强行加载轻则报错,重则直接内核拒绝加载模块。所以 VMware 会在启动时检测当前内核版本和模块是否匹配,不匹配就弹出你看到的那个提示:Before you can run VMware, several modules must be compiled and loaded into the running kernel。
1.2 为什么更换内核后必然触发这个报错
我打个比方:内核模块就像一把钥匙,内核版本就是锁芯的型号。旧钥匙在旧锁芯上转得挺好,但你换了新锁芯,旧钥匙的齿形就不对上了。VMware 这种闭源软件不可能提前为新内核编译好模块,只能等你本地的编译工具链把模块现场"配"出来。
触发这个报错的常见场景有三个:
- 系统发行版自动更新内核(比如
apt upgrade或者dnf update带上了内核更新)。 - 你手动装了新内核(比如从发行版仓库装了一个 LTS 内核,或者用了第三方维护的内核)。
- 内核启动顺序变了,比如刚装完新内核,用旧内核启动时模块还能用,但切到新内核启动就报这个错。
无论哪一种,核心矛盾都是:内核版本号变了,而 VMware 的内核模块还停留在上一个版本。知道了这个根源,后面的解决思路就清晰了——让 VMware 针对当前运行的内核,重新把模块编译出来。
1.3 问题真正的难点在哪
按理说让 VMware 自己重新编译一次模块就行了,但现实里很难一次成功。难点主要在这几处:
- 编译 VMware 模块必须要对应的内核头文件,也就是
linux-headers-$(uname -r)这个包。很多人系统里根本没装,VMware 就卡在"找不到头文件"这一步。 - 编译工具链不完整。GCC、make、build-essential 这些基础编译工具缺一不可,否则 VMware 的编译脚本跑起来一堆报错。
- GCC 版本和内核版本不匹配。尤其新内核配老 GCC,或者老内核配新 GCC,编译出来的模块大概率加载失败。
- Secure Boot(安全启动)会拦截未签名的内核模块。这是最容易被忽略的一个坑,报错信息还特别具有迷惑性。
所以,与其说是"解决一个报错",不如说是"解决一串连环问题"。我会在下面把完整的排查顺序捋一遍,按顺序操作,能省很多时间。
2. 动手解决前:先做三个关键检查
2.1 确认当前运行的内核版本
终端里执行这条命令,确认你现在到底跑在哪个内核上:
uname -r比如输出5.15.0-91-generic,这就是当前内核版本。记住这个版本号,后面所有操作都以它为准。注意千万别只盯着"最新安装的内核",因为可能你重启后还在跑旧内核,那 VMware 的报错原因就不一样了(有可能是模块没问题,而是启动项顺序的问题)。
如果你发现系统里确实装了好几个内核版本,可以用下面的命令查看:
dpkg --list | grep linux-image # Debian/Ubuntu 系 rpm -qa | grep kernel # RHEL/Fedora 系建议直接重启进入你要用的那个内核,然后在这个内核下完成 VMware 模块编译。这样最省事,不要试图给所有内核都编一遍模块,除非你用 VMware 的场景跨越多个内核版本很频繁。
2.2 检查内核头文件是否匹配
这一步是大多数人第一次卡住的地方。VMware 编译模块时需要内核头文件,也就是内核源码提供的接口声明。头文件没装好,编译脚本直接报unable to find the kernel source之类的错误。
Ubuntu/Debian 系执行:
apt install linux-headers-$(uname -r)Fedora/RHEL 系执行:
dnf install kernel-devel kernel-headers装完之后,确认头文件目录存在且名字和内核版本一致:
ls /usr/src/linux-headers-$(uname -r)这里有个坑:如果系统刚更新完内核,uname -r对应的头文件包可能还没装,甚至仓库里还没有。这种情况不用慌,换一个相近版本的内核启动,或者等头文件包同步后再操作。
2.3 检查编译器工具链和 GCC 匹配度
编译内核模块本质上就是在编译 C 代码,GCC 必须有,而且版本不能太离谱。装好编译工具链:
Debian/Ubuntu 系:
apt install build-essential make gccFedora/RHEL 系:
dnf groupinstall "Development Tools"然后检查 GCC 版本:
gcc --version一个常见情况是:新内核要求的 GCC 版本较新,而系统里的 GCC 是旧的,或者刚好相反。如果编译报出gcc: error: unrecognized command-line option这类信息,多半就是 GCC 版本不对。可以考虑用update-alternatives切换 GCC 版本,或者安装对应版本的 GCC 包。不过大多数情况下,发行版默认的 GCC 能匹配当前内核,这一步重在确认有工具,而不是版本极致匹配。
提示:执行任何安装操作前,先把系统更新到最新状态(
apt update && apt full-upgrade或dnf upgrade),然后再装头文件和工具链。否则可能装到一半,内核头文件版本又和你当前运行的内核对不上了。
3. 完整解决流程:让 VMware 重新编译模块
3.1 一键式解决方案:vmware-modconfig
其实 VMware 自带了一个命令行工具用来编译和安装模块,叫vmware-modconfig。大部分情况下,你只需要带--console --install-all参数运行一次,它就能完成模块编译、加载的全过程。
sudo vmware-modconfig --console --install-all这个命令会输出编译进度,期间可能请求你输入 root 密码。如果顺利,最终会显示各个模块均安装成功。然后启动 VMware Workstation,那个报错弹窗就不会再出现了。
但这个方案有个缺点:报错信息往往比较简略。如果编译失败,它只给一句话,不会告诉你具体卡在哪个环节。所以我把这一步作为"第一次尝试"的方案,失败了再走下面的手工排查路线。
3.2 手工触发模块编译
如果vmware-modconfig卡住或秒退,可以试试手工触发编译。VMware 官方文档提到过,直接运行该命令会重新生成模块,但有时候因为权限或缓存问题,需要先清理旧的模块文件。
清理并重新编译的步骤:
sudo rm -rf /usr/lib/vmware/modules/binary sudo vmware-modconfig --console --install-all第一行把旧的二进制模块目录整个删掉,相当于给 VMware 一个"从零开始"的状态。很多情况下,报错反复出现就是因为旧模块文件损坏或不完整,VMware 宁可报错也不自己覆盖。
如果还是不成功,就要看具体的编译日志了。日志位置一般在:
/tmp/vmware-root/vmware-modconfig*.log/tmp/vmware-root/vmware-mod*-*.log
打开后重点找error:或者fatal error关键字,一般是 GCC 报错。这一行信息基本就是问题根因。
3.3 模块编译成功但加载失败:Secure Boot 问题
模块编译好了,但 VMware 启动依然报错,或者vmware-modconfig显示成功,但虚拟机启动时提示模块权限之类的问题,十有八九是 Secure Boot 在起幺蛾子。
Secure Boot 开启时,内核只允许加载有合法数字签名的模块。我们自己现场编译出来的.ko文件没有签名,内核直接拒绝加载。这种情况有两条路:
第一条路:在 BIOS/UEFI 里关掉 Secure Boot。不同主板的入口不一样,Common 的做法是开机按 Del 或 F2 进 BIOS,找到Secure Boot选项,设为 Disabled。重启后进入 Linux 执行:
mokutil --sb-state看到SecureBoot disabled就说明关了。这种方法最简单粗暴,但如果你需要始终保持 Secure Boot 开启(比如公司策略要求),那就别关,走签名路线。
第二条路:给模块签名。这需要用到mokutil工具和一个自己生成的密钥。大体流程是:
- 生成一对密钥。
- 用密钥给
vmmon.ko和vmnet.ko签名。 - 把公钥导入 MOK 数据库。
- 重启后根据提示完成 MOK 注册。
命令大致是:
sudo mokutil --import /path/to/public_key.der这条路配置稍繁琐,但保持 Secure Boot 开启的前提下能解决问题。我实际测试下来,只要按流程走,一次性成功的概率很高,不过需要重启一次,而且重启后会有蓝色界面让你确认注册。
3.4 等待内核模块自动重编的机制:别忘了一件事
VMware Workstation 其实内置了一个自动处理机制:当你启动 VMware 时检测到模块不匹配,它会弹出一个图形化界面,要求输入 root 密码,然后自动执行编译。很多人遇到报错后转头就去搜命令行工具,反而忽略了虚拟机软件自己已经在等待输入密码。
所以最简单的方法或许是:看到弹窗后,直接输入你的密码,让它自己编译。很多情况下问题就没了。只有它自己编译失败、或者弹窗反复出现,才需要用到前面说的vmware-modconfig手工操作。
值得一提的是,这个弹窗在无人值守的远程 Linux 服务器上经常形同虚设(没有图形界面),所以放到服务器场景里,还是老老实实用命令行方案更靠谱。
4. 常见报错场景与排查技巧实录
4.1 编译时报"kernel headers not found"
这个报错几乎所有人都会遇到一次。现象是vmware-modconfig输出类似C header files matching your running kernel were not found,卡住不动。
原因就是 2.2 节里说的,没装内核头文件。解决方法是装对应版本的linux-headers。但注意,有些发行版(比如某些精简版)默认没启用源码仓库,直接apt install会提示找不到包。此时需要先启用仓库,apt update之后再装。
另外有一个细节:如果你是从 Ubuntu 的 hardware enablement stack(HWE)装的内核,头文件包名可能是linux-headers-generic-hwe-$(lsb_release -cs),而不是直接的linux-headers-$(uname -r)。这种情况下先装linux-headers-generic,再对照版本。
4.2 报"gcc: error: unrecognized command-line option"
这个我在某次内核大版本升级后遇到过。系统从 GCC 11 切换到 GCC 12,VMware 的模块代码用了旧的 C 语言语法特性,新 GCC 默认开启-Werror,直接把旧代码当错误处理。
如果 GCC 版本切换是问题根源,可以用update-alternatives切回旧版本:
sudo update-alternatives --config gcc更省事的办法是安装多个 GCC 版本后,用环境变量强制指定:
CC=gcc-11 vmware-modconfig --console --install-all不过这种 hack 方式需要确认内核本身是哪个 GCC 编译的。生产服务器建议直接使用发行版默认的 GCC,手动切换编译器版本容易引入其他问题,只适合本地宽松环境应急。
4.3 编译成功但 VMware 虚拟机启动直接报错
这种情况最气人,因为vmware-modconfig明明显示成功,VMMON 和 VMNET 看起来也加载了。但启动虚拟机时弹出"insufficient permissions"或者“VMware Player kernel module is not loaded”。
排查顺序:
sudo modprobe vmmon sudo modprobe vmnet lsmod | grep vm如果modprobe报错,看具体信息。Operation not permitted大概率是 Secure Boot 的问题(回到 3.3 节);Unknown symbol则说明模块和你内核版本不匹配,需要重新走编译流程。
如果modprobe成功但 VMware 还是报错,检查一下/dev/vmmon和/dev/vmnet是否存在,权限是否正确。有点时候 udev 规则没生效,需要手动加载:
sudo chmod 666 /dev/vmmon /dev/vmnet但这个设置重启后失效,不是根因。真正的根因通常还是 Secure Boot 或模块编译时内核源码路径不干净。
4.4 更新内核后 VMware 总是旧模块加载失败
有朋友问过我:为什么每次系统内核一更新,我又得重新编译一次模块?能不能一劳永逸?
答案很遗憾,不能彻底一劳永逸,但可以大幅减少操作频率。思路有两个:
一是把 VMware 的模块编译固定在一个长期服务的内核版本上(比如 Ubuntu 的 LTS 内核 HWE 系列),然后尽量避免跟随非安全更新升级内核。只有安全补丁的内核小版本升级,通常模块重新编译也就一两分钟的事。
二是写一个小的 systemd service 或者脚本,检测到内核版本变化时自动运行vmware-modconfig --console --install-all。我有一台自用服务器就是这么干的,升级完内核重启后,什么都不用管,模块直接编好。不过脚本要处理好依赖(先装好 linux-headers),否则自动编译只会失败。
4.5 编译日志中最容易忽略的一个 warning
在/tmp/vmware-root/日志里,有时会看到一堆warning: implicit declaration of function之类的警告。很多人看到 warning 就跳过,其实这种 warning 恰恰说明内核头文件路径没找对,或者头文件版本有细微差异。
我建议你把日志里所有的error和warning都看一遍,不要只盯 error。如果一个 warning 出现在vmmon编译的include阶段,那基本就是头文件缺失或路径不对。vmware-modconfig提供--kernel-headers参数,可以手动指定头文件路径:
sudo vmware-modconfig --console --install-all --kernel-headers /usr/src/linux-headers-$(uname -r)这一招在发行版头文件路径比较特殊的场合很管用。
5. 少数特殊情况:换内核后 VMware 模块彻底编不过去
5.1 内核太新,VMware 版本太老
这种情况最容易出现在"钉在旧版本 VMware"的机器上。比如某公司出于授权或兼容性考虑,还在用很多年前的 Workstation 版本,结果宿主机的内核升到了很新的版本。新内核里有些 API 变了,旧版 VMware 模块代码没有适配,编译器直接报一堆类型错误。
我的建议是:能升级 VMware 就升级 VMware。新版 VMware 对新内核的支持明显更好,社区反馈也快。实在不能升级,那就控制宿主机内核版本,把内核锁定在 VMware 版本官方支持的范围内(一般官网 release notes 里都有注明)。锁内核的方法因发行版而异,Ubuntu 可以用apt-mark hold linux-image-generic,Fedora 可以用dnf versionlock。
5.2 DKMS 方案:让模块随内核自动编译
Linux 社区对"内核模块跟着内核版本走"这事早就有了标准解法,叫 DKMS(Dynamic Kernel Module Support)。它的原理是:安装模块时注册到 DKMS 数据库,当系统检测到新内核被安装时,自动为新内核编译模块。
VMware 官方模块不走 DKMS,但社区有人维护了 DKMS 包装脚本。如果你厌倦了每次手动编译,可以考虑把 vmmon 和 vmnet 的源码放到 DKMS 管理下。大致步骤:
- 找到模块源码目录(
/usr/lib/vmware/modules/source/下的 tar 包)。 - 解压并按 DKMS 规范放入
/usr/src/。 - 创建
dkms.conf并注册。 dkms install编译安装。
这套方案我实际配置过,效果不错,后续升级内核后模块会自动编译。但注意,每次 VMware 升级可能覆盖模块源码目录,需要重新同步一次源码。适合有精力折腾的玩家。
5.3 重建 VMware 的二进制模块数据库
如果你发现不管是自动编译还是手工vmware-modconfig,都卡在某个奇怪的进度上,可以试试重建二进制模块数据库:
sudo rm -rf /etc/vmware*.bak sudo rm -rf /usr/lib/vmware/modules/binary sudo vmware-modconfig --console --install-all顺便说一句,/etc/vmware下的配置文件如果残留了旧内核的模块路径,也可能干扰编译流程。建议把vmware相关配置重置一下(注意备份已有配置),再执行编译。
6. 针对"更换内核"场景的具体操作速查
6.1 主流程速查表
| 步骤 | 操作 | 验证命令 |
|---|---|---|
| 1. 确认内核版本 | uname -r | 版本号清晰可读 |
| 2. 安装内核头文件 | apt install linux-headers-$(uname -r) | ls /usr/src/linux-headers-$(uname -r) |
| 3. 安装编译工具链 | apt install build-essential make gcc | gcc --version |
| 4. 执行模块编译 | sudo vmware-modconfig --console --install-all | 无 error 输出 |
| 5. 检查模块加载 | lsmod | grep vm | vmmon 和 vmnet 存在 |
| 6. 启动 VMware 测试 | 打开任意虚拟机 | 正常进入系统 |
6.2 内核升级前后的标准操作顺序
我强烈建议养成一个习惯:升级内核之前,先确认linux-headers-当前版本和linux-headers-新版本都已经可用。也就是说,升级命令执行前先看一眼仓库里有没有新版头文件。
标准流程:
sudo apt update sudo apt install linux-headers-$(uname -r) # 先保证旧内核头文件在 sudo apt full-upgrade # 升级内核和所有包 sudo reboot # 重启进入新内核 sudo vmware-modconfig --console --install-all # 编译新模块这个顺序能最大程度避免"升级后启动进入新内核,头文件却没得装"的尴尬。尤其是公司内网源更新不勤快的情况,仓库里可能还没有新版头文件,那就先别升内核,等源同步了再说。
6.3 五条实操心得
- 心得一:遇到报错别急着反复运行
vmware-modconfig,先看日志。日志在/tmp/vmware-root/,你盲目重试一百遍不如看一遍 error 行。 - 心得二:装头文件时,用
apt install linux-headers-$(uname -r)而不是linux-headers-generic。两个包有时候不完全等价,前者精确匹配当前内核。 - 心得三:如果在虚拟机里跑 VMware(嵌套虚拟化),模块编译流程不变,但要求宿主机 CPU 支持 VT-x 并在虚拟机设置里开启嵌套虚拟化,否则 VMware 内部虚拟机依然起不来。
- 心得四:手动编译模块前,先看看
/usr/lib/vmware/modules/source/下有没有vmmon.tar和vmnet.tar。如果这两个压缩包都不存在,说明 VMware 安装本身有问题,不是编译的问题。 - 心得五:Secure Boot 开启的机器上,如果不想关 SB,务必在第一次编译后用
mokutil注册公钥。这个过程需要重启一次,别嫌麻烦。重启后再跑一次vmware-modconfig,后续就会稳定很多。
7. 最后的经验总结:从踩坑到一劳永逸
我个人在实际操作中的体会是:这个 VMware 模块编译报错,本质上是 Linux 生态里"内核版本与第三方内核模块脱节"的典型缩影。它不复杂,但很烦人,因为每一次内核升级都可能把它重新引出来。理解它的机制比背命令更重要——只要你知道模块是跟着内核走的,你就知道解决思路永远是"匹配当前内核,重新编译,确保加载"。
从效率角度,我最推荐的做法是日常使用时不锁定内核升级(除非工作环境要求),但一定要保证头文件和编译工具链随时可用。这样即使遇到报错,跑一次vmware-modconfig最多两分钟解决。如果连这两分钟都不想花,那就配置 DKMS,让编译过程自动化。
还有一个值得提的小技巧:每次 VMware 大版本升级后,顺手删一次/usr/lib/vmware/modules/binary,再重新编译。这么做能避免旧版本的残留模块干扰新版本。我见过不少人升级 VMware 后遇到一堆诡异问题,删掉二进制目录就好了。
这个内容后续还可以往两个方向扩展:一个是写一个脚本,把内核升级、头文件检测、模块编译串起来,做成一条命令;另一个是研究一下 VMware Workstation 的模块源码结构,尝试直接给 vmmon 打补丁,解决某些内核版本下的深度兼容问题。不过对于绝大多数人来说,能把本文的流程走通,这个报错就已经不再构成困扰了。