☰
Ubuntu虚拟显示驱动安装指南:Xvfb与DRM/KMS方案详解
2026/10/1 7:01:18 网站建设 项目流程

1. 虚拟显示驱动到底是什么,为什么我需要它

先说说我最近遇到的困境。我在调试一个工业控制软件,它的检测模块要求系统必须存在一个至少1080p分辨率的显示设备,否则直接拒绝启动。可那台机器是租来的云服务器,压根没有显卡和显示器。远程桌面连上去,系统面板里显示分辨率只有800x600,而且无法调节。折腾了两天,最后用虚拟显示驱动解决了这个问题。

所谓虚拟显示驱动,本质上就是让操作系统认为存在一个显示器,但实际上这个显示器并不存在——它只是一个位于内存中的虚拟缓冲区域。系统把画面渲染到这个缓冲区内,至于这个画面最终是没人看、还是被编码传输给远程端、还是被程序读取做计算,那就是另一回事了。

这件事在Linux系统里尤其重要。很多服务器版Ubuntu默认情况下根本没有加载显示相关的内核模块,因为服务器不需要图形界面。但当你需要在无头(headless)环境下跑图形程序、做自动化测试、搭建远程开发工作站、或者运行需要GPU加速的计算任务时,你就必须让系统"伪装"出一个显示环境来。

我的实际经验告诉我,虚拟显示驱动主要解决下面这几类真实需求:

  • 远程开发与远程桌面:如果没有真实显示器,XRDP或VNC的桌面分辨率会被系统限制,使用虚拟显示驱动可以设置任意分辨率。
  • 无头环境跑图形应用:比如用浏览器做页面截图、用OpenGL做离屏渲染、跑UI自动化测试,都需要一个"不存在的屏幕"。
  • CI/CD流水线中的图形测试:构建服务器没有显示器,但测试用例需要窗口环境。
  • 云游戏/云渲染场景:GPU渲染后的画面放到虚拟显示缓冲中,再编码推流给终端用户。
  • 显示模块开发调试:开发者需要模拟特定分辨率、色深、刷新率来测试显示效果,没有真实设备时全靠虚拟驱动。

这次我要分享的重点是Ubuntu 20.04/22.04 LTS环境下安装和配置虚拟显示驱动的完整路径,包括Xvfb(X虚拟帧缓冲)和基于DRM/KMS的虚拟显示驱动两种方案。这两条路我用过很久,踩过不少坑,写出来希望对正在折腾的人有帮助。

2. 主流虚拟显示方案选型,我为什么最终推荐这两条路

先别急着执行命令安装,动手前得先搞清楚有几条路可以走,每条路适合什么场景。我在最初接触这个问题的时候,看到网上各种方案眼花缭乱,一股脑全试了一遍,结果把系统搞得一团糟。现在把这个选型过程分享出来,你就能少走弯路。

2.1 四大类方案的横向对比

目前Linux下实现虚拟显示的思路可以分为四类,我用表格整理一下:

方案原理优点缺点适合场景
Xvfb(X虚拟帧缓冲)在用户态模拟X Server,所有渲染写入内存缓冲安装简单,兼容性极强,老牌稳定不支持3D硬件加速,性能有限CI测试、截图、无头运行GUI程序
Xorg + modesetting虚拟驱动用DRM/KMS虚拟显示驱动注册一个虚拟显示器支持较新的KMS机制,可配分辨率配置相对复杂,依赖内核模块需要多个虚拟屏幕、需要xe/wayland支持的环境
GPU厂商虚拟显示功能如NVIDIA的Virtual Display、AMD的虚拟输出支持完整GPU加速仅限特定硬件,专业卡才完整支持云渲染、GPU虚拟化环境
显示服务器合成器(如weston headless)通过合成器后端配置无头输出适合Wayland环境配置方式随版本变化大嵌入式、Wayland开发调试

2.2 为什么多数情况下首选Xvfb

Xvfb是我最常用的方案,原因就一个字——稳。它从九十年代就存在了,经历了无数生产环境的考验。它的工作方式很简单:在用户态启动一个虚拟的X Server,应用程序照常把界面渲染上来,但输出目标是内存缓冲区而不是物理显示器。对上层应用来说,它感知不到任何差异,照样认为自己在跟一个真实的X Server通信。

Xvfb还有一个好处是它对权限要求极低。普通用户身份就能启动,不需要改内核参数,不需要root权限,不需要重启系统。这一点在CI/CD流水线里极其宝贵——你在流水线的某个步骤里动态启动一个Xvfb,测试完直接杀掉,完全不污染宿主环境。

2.3 什么时候必须用DRM/KMS虚拟显示驱动

Xvfb虽好,但在某些场景下不够用。比如你要测试Wayland合成器、要跑需要KMS接口的应用、或者需要多个独立的虚拟显示输出,那就得用基于DRM/KMS的虚拟驱动方案。

DRM(Direct Rendering Manager)是Linux内核中负责管理显示输出的子系统,KMS(Kernel Mode Setting)则是内核态的显示模式设置。虚拟显示驱动的思路就是往DRM子系统里注册一个虚拟连接器(Virtual Connector),这个连接器向系统报告"我是一块支持多种分辨率的显示器",实际上它只是把帧缓冲映射到一块内存区域里。

这条路的优势是它更"底层",更接近真实显示的路径。系统渲染管线中的每一步——从KMS提交帧缓冲到显示面板的连接——都会被完整的模拟出来,适合做显示栈相关开发。

但这条路配置略麻烦,需要加载特定的内核模块或配置Xorg的modesetting驱动,对初学者不太友好。我的建议是:单纯跑个GUI程序做测试用Xvfb;要做显示相关的开发调试或者需要多屏场景,再考虑DRM/KMS方案。

3. Ubuntu环境安装Xvfb虚拟显示驱动的完整流程

这部分我按Ubuntu 20.04/22.04 LTS来写,两条命令就完成安装。不过光装上去不够,真正让程序跑起来有几个关键参数必须理解到位,否则就会遇到"程序说没有显示环境"这种让人抓狂的问题。

3.1 安装与验证基础环境

安装Xvfb用的是apt命令,一行搞定:

sudo apt update sudo apt install xvfb x11-utils

这里我顺手把x11-utils也装上了,后面用xrandr验证显示配置会用到。安装完成后,先验证一下Xvfb本体能否正常启动:

Xvfb :99 -screen 0 1920x1080x24 &

这条命令的含义是:在显示编号:99上启动一个虚拟X服务器,创建一个1920x1080分辨率、24位色深(即每个像素用24位表示颜色,约1677万色)的屏幕。后台运行后,通过检查进程是否存活来判断是否启动成功:

ps aux | grep Xvfb

看到进程存在说明Xvfb已经跑起来了。这时我们可以用xdpyinfo来查询这个虚拟显示环境的详细信息:

DISPLAY=:99 xdpyinfo | grep dimensions

输出会显示dimensions: 1920x1080 pixels,这就说明虚拟显示环境已经成功创建。

3.2 DISPLAY环境变量的作用与设置

这是新手第一个坑点。Xvfb虽然启动了,但你运行GUI程序时,程序并不会自动使用这个虚拟显示,你必须通过DISPLAY环境变量来告诉程序"画面应该画到哪个显示设备上"。

DISPLAY变量的格式是主机名:显示编号.屏幕编号。我们刚才启动的Xvfb监听的是显示编号99,所以可以这样临时指定:

DISPLAY=:99 firefox &

也可以通过export的方式设置到当前shell环境里:

export DISPLAY=:99

这样之后你在当前终端窗口中启动的所有图形程序,都会自动往这个虚拟显示上渲染。但要注意,这个export只对当前终端会话有效,新开的终端窗口需要重新设置。

3.3 生产级启动脚本设计

如果你只是临时测试一次,手动运行命令行就够了。但如果你需要长期运行,或者想在CI流水线里稳定使用,那就得写一个稍微健壮点的启动脚本。我在项目中用的脚本是这样的:

#!/bin/bash # start-virtual-display.sh # 如果已经存在,先清理掉旧的虚拟显示进程 pkill -f "Xvfb :99" 2>/dev/null # 启动Xvfb,指定分辨率、色深,禁用访问控制便于程序连接 Xvfb :99 -screen 0 1920x1080x24 -ac +extension GLX +render -noreset & # 等待Xvfb完成初始化 for i in $(seq 1 20); do if DISPLAY=:99 xdpyinfo >/dev/null 2>&1; then echo "Virtual display :99 is ready" break fi sleep 0.5 done

这个脚本里有几个参数值得解释:

  • -ac:禁用访问控制,允许任意程序连接,这在CI环境里很有用。但在不安全的环境里要谨慎,因为它相当于把X服务端开放给所有人。
  • +extension GLX:启用GLX扩展以支持OpenGL渲染,很多图形测试框架依赖这个扩展。
  • +render:启用Render扩展,加速2D图形绘制。
  • -noreset:当最后一个连接客户端断开时不要重置X Server,避免意外断开后状态丢失。

为什么需要等待循环?因为Xvfb进程启动后,X Server可能需要几百毫秒才能完成初始化并开始监听,如果脚本立即去连接,有可能出现"无法打开显示设备"的错误。我见过很多人在这上面踩坑——Xvfb进程明明在,但程序就是连不上,就是因为启动和连接之间缺少了这个等待步骤。

3.4 测试一个真实程序是否渲染成功

我自己验证的时候,习惯用xwd(X窗口转储)命令来抓取虚拟显示的内容。安装工具:

sudo apt install x11-apps

先在虚拟显示上启动一个简单程序,比如xclock:

DISPLAY=:99 xclock -update 1 & sleep 1 DISPLAY=:99 xwd -root -out /tmp/virtual-display.xwd

然后用ImageMagick把xwd格式转换成PNG图片查看:

sudo apt install imagemagick convert /tmp/virtual-display.xwd /tmp/virtual-display.png

如果一切正常,打开这张PNG图片,你就能看到虚拟显示上渲染出的一个时钟界面。这个验证方式的妙处在于:它证明了从"程序渲染"到"X Server合成"再到"帧缓冲输出"整个链路是通的。

4. 基于DRM/KMS的虚拟显示驱动方案实战

Xvfb解决了我80%的问题,但有一次需求很特殊——我需要同时模拟三个不同分辨率的独立显示输出,而且程序要求显示设备必须走DRM路径。这时候Xvfb就无能为力了,因为它本质上只是一个X Server,并没有向内核的DRM子系统注册任何显示输出设备。于是我开始研究DRM/KMS级别的虚拟显示驱动。

4.1 需要哪些内核和软件包支持

在Ubuntu下走DRM/KMS虚拟显示这条路,核心依赖是内核的虚拟显示驱动模块。Linux内核中有一个专门用于虚拟显示的驱动,叫virtio-gpu,主要用于虚拟化环境(KVM/QEMU虚拟机)。但我这里要讲的是在物理机或云服务器上直接模拟,更常用的是加载Xorg的modesetting驱动配合虚拟输出。

需要安装的软件包:

sudo apt install xserver-xorg-video-dummy

等等,这个包实际上在Ubuntu 20.04之后就被移除了。所以正确的方式是用modesetting驱动,它已经包含在xserver-xorg-core里面了。也就是说,不需要额外安装驱动文件,只需要写好Xorg配置文件即可。

4.2 创建虚拟显示配置的过程

先为Xorg创建一个独立的配置文件,专门用来启用虚拟显示。我放在/etc/Xorg.virtual.conf,内容如下:

Section "Device" Identifier "VirtualDevice" Driver "modesetting" Option "kmsdev" "/dev/dri/card0" EndSection Section "Monitor" Identifier "VirtualMonitor" Option "PreferredMode" "1920x1080" EndSection Section "Screen" Identifier "VirtualScreen" Device "VirtualDevice" Monitor "VirtualMonitor" DefaultDepth 24 SubSection "Display" Depth 24 Modes "1920x1080" "1280x720" EndSection EndSection

这里有个关键点:modesetting驱动需要一个DRM设备节点/dev/dri/card0。但云服务器上往往没有这个设备。怎么办?桌面级GPU的驱动会创建这个节点,但无头服务器上常见的情况是/dev/dri目录根本不存在。

我的解决办法是加载虚拟的DRM内核模块,内核中有一个叫udl的驱动最初是给USB DisplayLink设备用的,实际上它也能创建DRM设备。不过更通用的做法是用drm_vram_helperc或virtio_gpu模块。实测中最稳妥的是:

sudo modprobe virtio-gpu

加载成功后,检查一下设备节点:

ls /dev/dri/

正常会看到card0和renderD128两个节点。之后用这个配置启动Xorg:

sudo Xorg :1 -config /etc/Xorg.virtual.conf

4.3 验证DRM虚拟显示是否真正生效

启动Xorg后,用xrandr命令来检查虚拟显示器的详细信息:

DISPLAY=:1 xrandr

输出会列出VirtualMonitor以及支持的分辨率模式。注意观察输出中是否出现"connected"字样,这意味着DRM子系统确实注册了一个已连接的虚拟显示器。如果显示"disconnected",说明KMS没有正确感知到这个虚拟显示连接,需要检查内核模块是否真的加载成功。

4.4 开机自动加载与自启配置的完整示例

为了让虚拟显示在系统重启后自动运行,我配置了一个systemd服务。先创建服务文件:

sudo tee /etc/systemd/system/virtual-display.service <<'EOF' [Unit] Description=Virtual Display Service After=multi-user.target [Service] Type=simple ExecStartPre=/usr/bin/modprobe virtio-gpu ExecStart=/usr/bin/Xorg :1 -config /etc/Xorg.virtual.conf -noreset -sharevts Restart=on-failure RestartSec=3 [Install] WantedBy=multi-user.target EOF

然后启用服务:

sudo systemctl daemon-reload sudo systemctl enable virtual-display.service sudo systemctl start virtual-display.service

检查服务状态:

systemctl status virtual-display.service

如果一切正常,服务会显示active (running),而且重启系统后也会自动拉起。这套配置我在Ubuntu 20.04和22.04上都验证过,稳定性相当不错。

5. 安装配置过程中的常见坑——我踩过的和你们可能遇到的

这条路我走了一年多,上面提到的方法整体是顺的,但中途踩过不少细坑。挑几个特别常见的,展开讲讲。

5.1 云服务器环境缺少 /dev/dri 设备节点

这是最常见的问题。如果你用的是阿里云、腾讯云、AWS这类云服务器,/dev/dri目录很可能不存在。因为云服务商的虚拟化平台没有给实例挂载物理GPU,而virtio-gpu模块在部分虚拟化平台上又可能无法正常创建DRM设备。

排查手段:

ls /dev/dri/ dmesg | grep -i drm

如果输出为空或者提示无法创建设备,可以考虑用Xvfb绕过这条路径。毕竟Xvfb不需要DRM节点。

如果项目必须用DRM路径,那通常需要物理GPU支持。在没有GPU的云服务器上,我的实操经验是放弃DRM方案,回归Xvfb,或者将桌面环境跑在Docker容器中、内部使用Xvfb、通过X11转发或VNC访问。

5.2 分辨率设置了无效的系统异常表现

有位朋友按网上的教程配置了一个3840x2160分辨率的虚拟显示,结果远程桌面整个画面变成黑屏。排查后发现是总像素量超过了他远程桌面客户端能承载的带宽和编码极限,而且X服务器渲染这么大的帧缓冲占用内存很高。

Xvfb的帧缓冲内存计算方式是宽 x 高 x 字节深度。以1920x1080x24位色深为例:

1920 x 1080 x 3 = 6,220,800字节 ≈ 5.93MB

看起来不大,但如果你开满四个虚拟屏幕都是4K分辨率:

3840 x 2160 x 4 x 4 = 132,710,400字节 ≈ 126.6MB

这个内存开销在云服务器上可能会触发OOM或性能问题。我的建议是根据实际需要选择分辨率,不要盲目追求高分辨率。在远程开发场景中,1080p或2K(2560x1440)在显示效果和资源消耗上最为均衡。实测下来2K分辨率下XRDP的流畅度已经明显下降,而1080p流畅度很好。

5.3 程序启动报错"cannot open display"的排查链路

这个问题频率最高。程序报错说无法打开显示设备,原因通常有以下几点,按发生频率排序:

  1. DISPLAY变量没设置。不同终端会话之间DISPLAY不继承,需重新export。
  2. Xvfb还没就绪程序就去连接了。X Server初始化需要时间,加等待循环即可。
  3. 权限问题。Xvfb默认会启用访问控制,如果你用-ac启动,就不会有这个问题。
  4. 显示编号对不上。程序用的DISPLAY编号和Xvfb启动的编号不一致。
  5. SELinux/AppArmor拦截。Ubuntu默认的AppArmor可能会限制某些程序连接到X Server,检查相关日志。

排查这类问题有个很顺手的小技巧:先用xdpyinfo -display :99这个命令手动测试连接的可用性,如果这个命令都失败,说明问题出在显示环境本身,还没轮到程序头上。

5.4 无GPU环境下OpenGL支持受限,别把Xvfb当显卡用

还有一点必须提醒:Xvfb的GLX扩展只能支持软件渲染,也就是基于Mesa的llvmpipe——它会用CPU来模拟GPU的渲染管线。这意味着你可以在虚拟显示里跑OpenGL程序,但性能跟真实GPU相比是两个数量级。

在无GPU的服务器上做3D渲染测试,我测出一个经验值:一个中等复杂度的OpenGL场景,llvmpipe渲染一帧可能需要几百毫秒,而真实GPU只需要几毫秒。如果你的项目对渲染性能有硬性要求,应该考虑用GPU实例或离屏渲染方案(如EGL的surfaceless平台),而不是寄希望于Xvfb能给你带来流畅的3D体验。

6. 进阶用法——多虚拟显示、自定义分辨率与自动化场景扩展

基础环境搭好之后,进阶用法才是真正提升生产力的地方。我分享一下我在实际项目中用到的几个扩展方式。

6.1 多虚拟显示配置的具体操作

在Xvfb中创建多个独立的虚拟显示很简单——启动多个展示编号不同的进程实例即可:

Xvfb :99 -screen 0 1920x1080x24 & Xvfb :100 -screen 0 1280x720x24 & Xvfb :101 -screen 0 2560x1440x24 &

但要注意,这样创建的三个虚拟显示是完全独立的X Server,它们之间不能像真实多显示器那样组合成一个扩展桌面。如果你需要的是"一个逻辑桌面横跨多个虚拟显示器"的效果,那就要使用Xvfb的多屏幕配置:

Xvfb :99 -screen 0 1920x1080x24 -screen 1 1280x720x24 -screen 2 2560x1440x24 &

这样每个screen属于同一个X Server但相互独立。

如果走DRM/KMS路线,多虚拟显示可以通过配置Xorg中多个Monitor节和Screen节来实现。我曾经在调试多屏拼接算法时,用DRM方案模拟了三个分辨率不同的虚拟显示器,效果非常逼真,程序完全感知不到这是虚拟设备。

6.2 动态修改分辨率的方法与限制

Xvfb启动后能否修改分辨率?可以,但要精确理解它支持的路径。通过xrandr工具可以实现:

DISPLAY=:99 xrandr --output default --mode 1600x900

但这里有个前置条件:你启动Xvfb时指定的分辨率模式必须已经包含在屏幕配置中。比如你启动时只指定了一个1920x1080,那xrandr切换分辨率时无可用的其他模式供选择。

解决办法是启动时多指定几种模式。Xvfb的-screen参数支持多个分辨率:

Xvfb :99 -screen 0 1920x1080x24 -screen 0 1280x720x24 -screen 0 1024x768x24

不过实测中,这种写法在不同版本Xvfb上的行为略有差异。更稳妥的做法是在程序启动前确定好分辨率,避免运行中切换。我的经验是,虚拟显示的定位是"让程序有的放"而不是"让用户调分辨率",把它当成一台固定分辨率的显示器看待,整个使用体验会顺畅得多。

6.3 配合Docker在无头环境下使用虚拟显示

容器化部署越来越普遍,很多团队希望在Docker容器里运行带图形界面的程序,然后通过某种方式访问。虚拟显示驱动在这里发挥着关键作用。

我的典型做法是创建一个OpenGL开发镜像:

FROM ubuntu:22.04 RUN apt update && apt install -y xvfb x11-utils mesa-utils firefox ENV DISPLAY=:99 CMD ["bash", "-c", "Xvfb :99 -screen 0 1280x720x24 & sleep 2 && firefox"]

容器启动后在里面运行firefox等GUI应用。如果需要在宿主机上查看容器内的画面,可以配合x11docker或VNC方案。这里有个经验是:容器内的Xvfb跑起来之后,可以通过挂载宿主机X11 socket的方式把窗口直接显示到宿主机桌面上:

docker run -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix your-image

但前提是宿主机允许X11连接(用xhost +放开访问控制)。如果你的宿主机也是无显示环境的服务器,那就直接在容器里用Xvfb,然后在容器里跑VNC服务器,宿主机通过VNC客户端连接访问。

6.4 CI流水线中动态起停虚拟显示的经验

最后重点提一下CI/CD流水线中的用法。很多人写流水线时,第一步先把Xvfb启动起来挂在那里,全程不管理。这样做有个隐患:长时间运行的Xvfb可能会积累内存碎片占用,画面缓冲区一直处于活动状态,CI节点资源被白白消耗。

我习惯的做法是,在每个需要图形界面的测试步骤内,动态启动Xvfb,测试结束后立即杀掉进程:

# 在测试任务的开头启动 export DISPLAY=:99 Xvfb :99 -screen 0 1280x720x24 -ac +extension GLX +render -noreset & XVFB_PID=$! # 等待就绪 for i in $(seq 1 30); do DISPLAY=:99 xdpyinfo >/dev/null 2>&1 && break; sleep 1; done # 执行测试 run_your_tests # 清理 kill $XVFB_PID

这种动态启停方式的好处是,测试环境始终从干净状态开始,互不干扰,也不会有残留的X Server占着资源不放。

还有一个经常被忽略的细节:并行CI任务之间的隔离。如果两条流水线同时启动Xvfb :99,后启动的那个会抢不到显示编号而导致启动失败。我建议在脚本里动态生成可用的显示编号:

DISPLAY_NUM=$((99 + RANDOM % 1000)) Xvfb :$DISPLAY_NUM -screen 0 1280x720x24 & export DISPLAY=:$DISPLAY_NUM

这样每条CI任务用的都是独立的显示编号,互不冲突。这个细节让我少加了很多班。

7. 几个实用的踩坑复盘与终极建议

写到最后,我把这些实操经验浓缩成几条建议,虽然不是"总结",但都是我用真金白银的时间换回来的心得。

先说分辨率选择。虚拟显示的分辨率不是越高越好,要结合你实际的远程访问方式和程序需求决定。XRDP访问建议1080p,延迟好可上2K。单纯的自动化测试程序不需要画面,720p甚至更低也没问题,帧缓冲更小,渲染更快。

再说方案选择。如果你在纠结用Xvfb还是DRM/KMS虚拟显示驱动,记住这个原则:越快能跑通越好。Xvfb一条命令就搞定,百分之九十的场景都够用。只有当你明确需要KMS接口或Wayland支持时,才值得花时间弄DRM方案。

最后一点是环境变量的管理。维护一个集中的环境配置脚本,把DISPLAY、虚拟显示编号等变量统一管理起来,避免每次新开终端都要手动设置一遍。我一般放在~/.bashrc里一个函数定义,需要用的时候直接调用。

虚拟显示驱动这个技术,看起来不起眼,但它是无数开发流程中不可或缺的底层工具。希望这篇不只是告诉你怎么敲命令,而是让你在用的时候知道底层发生了什么,遇到问题能自己定位根源。我到现在家里还留着一台装了双虚拟显示的测试服务器,随时需要模拟多屏场景就能用上,非常顺手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询