很多朋友在 Ubuntu 上给 NVIDIA 显卡装驱动,最常遇到的“劝退点”不是下载速度,也不是版本选择,而是安装进行到一半时,终端突然蹦出一行大写的英文:
ERROR: An error occurred while performing the step: “Building kernel modules“. See /var/log/nvidia-installer.log我第一次见到这个报错,第一反应和很多人一样:驱动文件是不是下载坏了?于是删掉重下、换个版本再跑,结果还是原封不动卡在同一句话上。后来老老实实去翻日志,才发现问题根本不在安装包,而在内核头文件、编译工具链、Secure Boot 状态和旧驱动残留这一堆“周边环境”上。
这篇文章就把这套排查思路完整写出来:这个错误到底发生在哪一步、为什么会触发、怎么根据日志快速定位,以及一条我实测下来最稳的处理流程。如果你也卡在这个报错上,千万别急着重装系统,往下看基本能解决。
1. 报错场景还原:卡在“Building kernel modules”究竟意味着什么
1.1 安装过程中的真实阶段
NVIDIA 的.run安装包不是简单地把文件复制到系统里,它内部有一套完整流程:先检查依赖,再解压驱动源码和二进制文件,然后调用 DKMS 或本地构建脚本来编译内核模块,之后尝试加载模块,最后写 X Server 配置。普通用户看到的进度条能走到 80% 以上,往往恰恰是在最后这一段才失败。
“Building kernel modules”翻译过来就是“正在构建内核模块”。这一步是 NVIDIA 驱动和 Linux 内核真正开始打交道的地方。驱动不是一整块静态二进制,而是由若干内核模块文件组成,最典型的有nvidia.ko、nvidia-modeset.ko、nvidia-drm.ko。安装程序需要把这些.ko文件编译成当前运行内核能识别、能加载的格式,所以这一步本质是一次“现场编译”。
如果编译环境有问题,安装器就会抛出那句经典废话:“An error occurred while performing the step: Building kernel modules.”。它并没有告诉你具体原因,真正的原因全部写在后半句提示的/var/log/nvidia-installer.log里。
1.2 为什么这一步是所有阶段里最容易翻车的
说句实话,NVIDIA 驱动安装器在“复制文件”“更新配置”这些阶段几乎不会出问题,因为这些动作无非是把文件放到固定位置。内核模块构建则完全不同,它依赖很多外部条件:
- 当前内核版本对应的头文件(kernel headers)必须存在;
- 编译器版本要和构建内核时使用的工具链兼容;
- DKMS 状态必须干净,不能有旧版本的残留记录;
- 如果开启了 Secure Boot,模块签名流程可能中断安装;
- 系统里若有正在加载的旧 NVIDIA 模块或者 Nouveau 开源驱动,也可能干扰后续加载阶段。
任何一个环节不对,安装器都可能在这个步骤直接终止。这个报错就像一个路由器上的总警告灯,亮起来只告诉你“网络不通”,但网线、光猫、DNS、防火墙都有可能。所以排查的关键不是反复重装驱动,而是先看懂日志,再按顺序排除环境问题。
2. 根因排查:构建内核模块失败的核心原因
2.1 第一优先级:内核头文件缺失或版本不匹配
内核头文件是构建模块时最重要的依赖。它本质上是当前内核开放给外部模块的接口定义,包含了大量.h头文件以及Makefile等构建辅助文件。NVIDIA 驱动的源代码要编译成.ko,第一步就是找到这些头文件。
这里要特别注意一个细节:uname -r输出的内核版本,比如6.8.0-51-generic,必须等同存在于/usr/lib/modules/下的目录名。如果你的系统只有一个内核,但没装对应的linux-headers包,那么构建时会出现类似“Unable to find the kernel source tree”的日志,接着失败。
我见过很多人误以为装了linux-libc-dev就足够了,这是不对的。linux-libc-dev提供的是用户态 C 库所需的头文件,和内核模块构建需要的linux-headers-$(uname -r)不是一回事。正确做法是:
sudo apt update sudo apt install -y linux-headers-$(uname -r)安装完成后检查一下软链接是否正常:
ls -l /lib/modules/$(uname -r)/build如果链接指向的目录不存在,说明 headers 包没装对,或者装完又被清理了。需要重新安装后再继续。
2.2 编译器(gcc)与内核工具链不一致
内核模块是用 C 语言写的,编译工具链默认是 gcc。但这里有个隐含要求:编译内核模块的 gcc 版本,最好和当初编译当前内核时使用的 gcc 版本接近甚至一致。因为内核源码里有一些代码会检测编译器版本,不同版本对类型检查、内建函数、宏展开的处理逻辑可能有差异。
你可以用两个命令快速对比:
gcc --version cat /proc/version/proc/version里会显示当前内核是哪个 gcc 编译的。如果系统默认 gcc 是 13,而内核是 gcc-12 编译的,安装器日志里通常会出现一行警告:Kernel compiler gcc-12 is not the same as the compiler used to build the kernel gcc-13。
这种警告不一定会直接导致失败,但如果后面跟着报错,比如unrecognized command-line option或error: implicit declaration of function,十有八九就是编译器版本问题。解决办法是安装对应版本的 gcc,例如:
sudo apt install gcc-12然后在运行 NVIDIA 安装器时尝试指定编译器:
sudo sh ./NVIDIA-Linux-x86_64-xxx.run --kernel-cc=gcc-12需要说明的是,不同版本的安装器对参数的兼容性不完全一样,有的用--kernel-cc,有的用--cc,如果参数不被识别,安装器会给出提示。我最常用的做法是先在日志里确认 CC 变量实际用的是哪个编译器,再针对性调整。
2.3 Secure Boot 和 Nouveau 的隐藏干扰
如果你是在 UEFI 模式下安装 Ubuntu,并且系统开启了 Secure Boot,那么构建出的内核模块如果没有经过签名,重启后内核会拒绝加载。有些版本会在模块构建完成后进入 MOK 注册流程,弹出一个需要确认的界面,如果用户没注意到,安装过程也可能中断在看似“构建模块”的阶段。
建议先检查:
mokutil --sb-state如果输出是SecureBoot enabled,最简单的办法是到 BIOS 里临时关闭 Secure Boot,装好驱动后再决定是否需要重新开启并配置签名。个人实测下来,测试环境关闭 Secure Boot 最省心。
另一个常见干扰源是 Nouveau 开源驱动。Ubuntu 默认带了一部分开源的 NVIDIA 驱动组件,如果你没有禁用 Nouveau,安装器在加载新驱动时可能检测到模块冲突。虽然这更多影响“加载模块”而不是“编译模块”,但为了干净起见,最好先屏蔽它:
sudo tee /etc/modprobe.d/blacklist-nvidia.conf <<'EOF' blacklist nouveau options nouveau modeset=0 EOF sudo update-initramfs -u然后重启系统,让屏蔽生效,再进行驱动安装。
2.4 DKMS 与旧驱动残留的干扰
如果你以前装过 NVIDIA 驱动,系统里往往还留着旧的 DKMS 记录和/usr/src/nvidia-xxx源码目录。新安装器在注册模块时可能发现版本冲突,或者因为旧目录权限不对、文件不完整而构建失败。
建议在重新安装前,先看一遍:
dkms status ls /usr/src | grep nvidia如果有明显的旧版本记录,先移除:
sudo dkms remove -m nvidia -v <旧版本> --all如果之前是用.run方式安装的,还可以用同版本的安装器自带的卸载功能:
sudo nvidia-uninstall清理完旧环境,再开始新的安装,成功率会高很多。别嫌这步麻烦,我踩过很多次坑都是因为旧 DKMS 状态没清干净。
2.5 两条安装路径的差异:.run 与 apt
很多人不理解为什么缓存在/var/log/nvidia-installer.log里的失败要用 apt 才顺利。其实.run和apt路径最后都要编译内核模块,区别主要在版本匹配度上。.run是 NVIDIA 官方独立安装包,它对内核版本、编译器版本的要求比较严格;而 Ubuntu 官方仓库里的nvidia-driver-xxx是经过发行版测试的,默认版本和当前内核、工具链往往更匹配,所以失败率更低。
但 apt 路径并不是万能药。如果你的内核头文件没装,或者 Secure Boot 环境没处理,apt 装驱动时同样会在 DKMS 构建阶段报错。所以建议先按照前几节的方法把环境清干净,再看情况选哪条路。
3. 实测有效的整套恢复流程
3.1 阶段一:采集现场信息
很多人一看到报错就急着删掉.run文件重下,其实最先要做的是收集现场信息。我每次处理这类问题,都会先跑一遍以下命令:
uname -r gcc --version cat /proc/version mokutil --sb-state dkms status ls /usr/src | grep nvidia这组命令能告诉我当前内核版本、编译器版本、Secure Boot 状态、旧 DKMS 记录,基本覆盖了 80% 的根因。如果是在图形界面环境下,我会先切换到文本终端,避免 X Server 干扰安装流程:
sudo systemctl isolate multi-user.target跑完之后想回到桌面,用sudo systemctl isolate graphical.target即可。
3.2 阶段二:补齐头文件和构建工具链
确认环境信息后,第一件事是补齐基础依赖:
sudo apt update sudo apt install -y build-essential linux-headers-$(uname -r)build-essential包含 gcc、make 等基础工具;linux-headers-$(uname -r)是精确对应当前内核版本的头文件包。如果系统提示该包找不到,要认真看是不是有多个内核,当前运行的内核是否有对应的仓库软件源。
3.3 阶段三:清理旧模块并禁用 Nouveau
记得先清理旧驱动和 DKMS 记录,再屏蔽 Nouveau。具体命令在前文已经列过,这里再整理成一条完整序列:
sudo nvidia-uninstall # 如果之前用 .run 装过 sudo dkms remove -m nvidia -v <版本> --all sudo tee /etc/modprobe.d/blacklist-nvidia.conf <<'EOF' blacklist nouveau options nouveau modeset=0 EOF sudo update-initramfs -u然后重启系统。重启后建议直接进入文本终端,不要登录桌面。
3.4 阶段四:重新运行安装程序
在干净环境下,用如下方式运行安装包:
sudo sh ./NVIDIA-Linux-x86_64-550.144.03.run --dkms --ui=none 2>&1 | tee /tmp/nvidia-install.log--dkms参数表示注册到 DKMS,以后内核升级时驱动可以自动重建;--ui=none减少图形界面的依赖,避免额外交互;2>&1 | tee可以把完整输出保留下来,方便失败后回溯。
如果安装程序仍然报错,不要慌,直接看日志文件。重点搜索ERROR关键字,通常能找到失败原因。
3.5 阶段五:换一条更省心的 apt 路线
如果你对.run安装器实在没耐心,或者试了几次还是卡在同一个位置,我建议切换到 Ubuntu 官方仓库的 apt 安装方式:
sudo ubuntu-drivers devices这条命令会列出推荐的驱动版本。然后安装推荐项:
sudo apt install nvidia-driver-550装完后重启,再运行nvidia-smi验证。使用 apt 的好处是版本经过发行版测试,而且后续内核更新时 DKMS 会自动重新编译模块,比自己手动下载.run省心很多。唯一前提是前面说的环境问题,比如 headers、Secure Boot,同样要先处理好。
4. 安装日志应该怎么看
4.1 从日志报错行推断根因
/var/log/nvidia-installer.log是 NVIDIA 驱动安装的现场记录,也是排查这个报错最重要的文件。日志内容会随着版本不同略有差异,但常见的关键信息有一定的规律,我整理成了一张速查表:
| 日志中的典型错误片段 | 说明 | 首选处理 |
|---|---|---|
Unable to find the kernel source tree | headers 没装或 build 链接失效 | apt install linux-headers-$(uname -r) |
Kernel compiler ... is not the same as ... | 编译器版本与内核编译工具链不一致 | 安装对应 gcc 版本,并通过参数指定 |
unrecognized command-line option '-mcmodel=kernel' | gcc 版本过旧或损坏 | 升级或重装匹配 gcc |
Failed to load the kernel module | 编译已通过但模块加载失败 | 检查 Secure Boot、Nouveau 和模块签名 |
ERROR: Failed to build ...紧跟着大量编译错误行 | 一般是 API 或头文件不匹配 | 先补 headers,再尝试更换工具链版本 |
日志里那些看起来复杂的长串 gcc 参数其实不用每个都看懂。你要做的是定位第一个ERROR或Error出现的位置,看它前面几行是什么。如果是提示找不到头文件,那就不是编译器的问题,先补包;如果是在编译某个.c文件时报语法错误,那多半是工具链版本差异太大。
4.2 日志里被很多人忽略的信息
除了报错行,日志头部还有一些值得注意的内容。比如:
- 日志第一段通常会写明运行用户、安装器版本、目标内核版本;
- 构建参数里会出现
CC=gcc-xx,这个值可以和你gcc --version的输出对比; - 如果日志里显示某个阶段的目录路径,比如
/usr/src/nvidia-550.144.03,你可以手动进这个目录,查看里面的.config或其他构建文件是否存在。
还有一个很多人容易忽略的细节:nvidia-installer.log每次运行都会被覆盖。如果你希望保留现场的报错日志,可以在重试前先备份一份:
cp /var/log/nvidia-installer.log /tmp/nvidia-installer-fail.log这样即使后面重装把日志覆盖了,你也能对比失败前后的差异。
5. 常见问题速查与避坑清单
5.1 从几个高频案例看问题定位
根据我遇到和网友反馈的常见情况,我整理了一张“现象→根因→处理”的对照表,遇到类似问题时可以快速对照:
| 遇到的现象 | 大概率原因 | 处理方式 |
|---|---|---|
| 第一次安装就卡在“Building kernel modules” | headers 没装或 build 链接失效 | 安装linux-headers-$(uname -r) |
apt upgrade之后重装驱动失败 | 新内核 headers 缺失或旧模块残留 | 按当前内核重新安装 headers,清理 DKMS |
日志里全是 gcc 编译参数,最后unrecognized... | gcc 版本不匹配 | 安装对应版本 gcc,指定--kernel-cc |
安装过程顺利,但重启后nvidia-smi报错 | Secure Boot 未处理或模块签名问题 | 临时关闭 Secure Boot 或完成 MOK 注册 |
| 用 apt 安装时 DKMS 构建失败 | 同样多为 headers 缺失或不匹配 | 先装 headers,再重新安装驱动 |
| 日志里提示模块被占用 | Nouveau 或其他旧模块未清理 | 禁用 Nouveau,清理旧驱动,然后重启 |
这张表不能覆盖所有情况,但覆盖了绝大多数“Building kernel modules”失败的核心场景。
5.2 我踩过多次坑之后总结的四个“不要”
第一,不要反复重下同一个.run文件。驱动包本身出问题的概率极低,重复下载纯属浪费时间,而且如果你没有备份,还会把原本有分析价值的日志覆盖掉。出现报错后先看/var/log/nvidia-installer.log,这是最快的路。
第二,不要跳过 headers 安装直接重试。很多人在缺 headers 的情况下反复重装,结果每次都卡在同一句“Building kernel modules”。这不是玄学,是因为构建阶段缺少必要源码,安装器根本走不下去。补上 headers,问题立刻消失。
第三,不要在图形界面环境下反复尝试。X Server 在运行的时候,NVIDIA 安装器可能因为检测到显示服务而做出额外处理,或者模块加载阶段被占用。切到文本终端,让安装器在一个相对干净的环境里运行,能排除很多干扰因素。
第四,不要随手乱删/usr/src下的目录。DKMS 源码目录如果删得不干净,可能影响新驱动注册。正确的做法是先用dkms status确认旧版本号,再用dkms remove清理。如果已经乱删了,可以重新安装对应 headers 和驱动包,引导 DKMS 重新生成。
我在实际处理这类问题时,最后往往会先用nvidia-smi做一个快速验证,如果能看到显卡型号、驱动版本和显存信息,说明模块已经成功加载。这时候再回到图形界面,跑一次压力测试或者glxinfo确认渲染正常,基本就算收工了。
如果你现在正卡在同一个报错上,我的建议是:先看日志,再补 headers,清理旧模块,最后切到命令行重装。大部分情况下,十分钟内就能解决。别在没分析原因之前对着.run反复重装,那才是真的浪费时间。