在Linux容器中进行硬件加速的OpenGL渲染
如何解决在Linux容器中用硬件OpenGL加速渲染时在Windows上的远程显示问题
本文将告诉你如何解决在Linux容器中用硬件OpenGL加速渲染时在Windows上远程显示的问题。这个问题发生在一个真实的项目中,我有机会和我的团队为我们的一个客户实施这个项目。稍后你会看到,我们已经成功解决了这个问题。今天,你可以从我们的经验中学习。
你将在这里找到什么
本文旨在让大家更深入地了解底层技术,以建立一个带有X窗口系统的Linux服务器来显示运行在Docker容器中的应用程序的图形用户界面。假设在一台Windows电脑上有一个远程显示。
特别注意的是基于OpenGL应用的3D应用,其中包括医学成像应用、CAD应用或游戏。
这篇文章特别集中于使用NVIDIA硬件和驱动程序的硬件加速渲染,以及在Linux服务器上渲染图像帧,而不是在Windows端。
讨论最多的是与X11授权的配置有关的问题,以及在Linux直接渲染基础设施(DRI)中实现OpenGL的架构。你还可以在这里找到关于X11的一般描述。此外,还介绍了Docker容器中NVIDIA驱动程序和库的设置。
业务背景和定位
市场。
Solwit项目在嵌入式医疗设备、医疗成像、3D计算机视觉、虚拟化行业的应用
技术。
Linux、OpenGL、X11、VNC、Docker容器、SSH、实时放置CRT设备的导线电极
质量管理体系。
ISO 13485:2016质量管理体系
参与者/扮演者。
大学医院(科学和临床专业知识),负责软件、集成和测试的合作伙伴,以及负责硬件放大器开发的另一个合作伙伴
解决的问题。
应用程序容器化提供了可预测的运行环境配置,并与系统的其他部分相对高度隔离。OpenGL应用的硬件加速使性能得到提升。在服务器上渲染OpenGL允许使用低性能的查看器,例如,低成本的Windows笔记本电脑。
介绍。远程显示和OpenGL渲染
远程显示一个应用程序的GUI可能有很大的优势:多个用户可以同时运行该应用程序,而且该应用程序可以在一个无头的高性能服务器上运行。为了显示应用程序的GUI,每个用户都需要在他们的本地计算机上安装一些查看器软件,在那里也会产生键盘和鼠标控制。
如果服务器和查看器的机器都是Windows,可以使用RDP来实现远程显示。RDP主要通过网络发送图形命令,最大限度地减少带宽利用率,降低延迟,并提供OpenGL的远程渲染。
如果服务器和观看者的机器都是Unix,则可以使用X11进行远程显示。X11也可以用来在网络上发送未压缩的位图,在某些应用领域会导致大量的带宽消耗,但现代版本的X11提供了直接控制观看者计算机的视频卡的扩展(如MESA),从而允许全屏视频,渲染3D应用等。在Unix世界中,其他选择可以是Wayland或Mir。
OpenGL渲染是指从一个二维或三维模型生成一个二维视频帧的过程。渲染不仅要考虑三维模型的空间排列,还要考虑其位置角度和视口的位置(=”camera”
)、闪电、纹理等。 OpenGL渲染是计算密集型的,从专门的GPU硬件中获益。
虽然利用观察者计算机资源的OpenGL渲染增加了这种分布式多用户系统的整体可扩展性,但它也产生了为每个观察者计算机配备适当的、相对先进和昂贵的GPU硬件的要求。因此,在分布式多用户环境中,控制OpenGL渲染发生的位置仍然是有用的。
在Windows和Unix服务器上,都可以使用虚拟网络计算(VNC)。VNC只通过网络发送压缩的位图,而不是图形命令,因此OpenGL渲染总是在服务器上进行的。VNC本身并不采用GPU硬件,如果服务器端存在这样的硬件,这可能是一种损失。但是,在服务器上运行带有VirtualGL包装器的GUI应用程序,可以在服务器上进行OpenGL渲染,并且只将渲染好的2D视频帧传给VNC进行压缩并通过网络传输给观看者。VNC是独立于平台的,即在服务器上可以使用Windows或Unix,在任何一种情况下,Windows或Unix都可以控制观看者。
远程音频或文件共享的问题不在本文的讨论范围之内。
图1:用VNC和VirtualGL传递3D数据和2D帧。
背景。X Window System (X11)
X窗口系统(通常称为X11)是一个计算机图形窗口界面系统,常用于类Unix操作系统。X11只提供在显示设备上绘制和移动窗口以及通过鼠标和键盘进行交互的基本框架。它不决定与各个窗口的互动,也不决定各个窗口的视觉外观。协调各个窗口与操作系统其他部分之间的输入和输出是显示服务器的责任,并通过显示服务器协议(如X11、Wayland、Mir)来进行。X显示服务器在提供显示和输入设备的计算机上运行,即在 "用户面前 "的计算机上。 其他作为X11客户端的软件在本地或远程连接到X服务器以显示内容。
在这些X客户端中,X显示管理器通常首先启动(如XDM、SDDM、GDM、LightDM),以便从X服务器上的同一台或另一台计算机提供图形化的登录会话。如果X显示管理器在本地计算机上运行("在用户面前"),它首先根据X显示管理器的配置文件(Xservers)启动至少一个本地X服务器,然后与本地X服务器联系,呈现一个用户登录屏幕,并等待用户认证成功后再启动用户的图形会话。
如果X显示管理器运行在远程计算机上,即与X服务器运行在不同的计算机上,可以使用X显示管理器控制协议(XDMCP)来代替X显示管理器配置文件(Xservers)。使用XDMCP时,装有X服务器的计算机必须主动向远程计算机请求一个显示管理器连接。这通常是通过在有X服务器的计算机上运行一个XDMCP选择器来实现的。XDMCP选择器提供了一个用户可以选择的X显示管理器的列表。这个列表可以包括一组预定义的主机,以及一组广播地址来检测可用的X显示管理器。如果需要,XDMCP允许X显示管理器授权自己作为普通的X客户端访问X服务器。
窗口在屏幕上的位置和外观由一个称为窗口管理器的X客户端控制(例如,Mutter/Metacity、KWin、IceWM、dwm、Xfwm)。窗口管理器决定一个新窗口的初始位置,并在窗口周围画一个装饰性的框架。它对移动、调整大小、图标化或去图标化窗口的请求做出反应。它通常也处理在其根窗口中的鼠标点击,以呈现窗格和其他视觉控制元素。它处理特定的组合键(例如,关闭一个窗口),并决定在启动时运行哪个应用程序。 窗口管理器与会话管理器(例如 xsm, ksmserver)进行通信,以保存和恢复一组应用程序的状态。
会话是一组与他们的窗口有关的X11客户端应用程序,以及允许这些应用程序在请求时恢复其窗口的信息。会话管理器可以在相同的状态下提供完全相同的窗口,例如,在从交互式会话中注销后登录时。也可以保存一些会话,并在适当的时候随时加载其中的任何一个。
为了恢复窗口的内容,应用程序必须能够在会话管理器的要求下保存其执行状态,并在再次启动时加载回来。X会话管理协议(XSMP)管理着应用程序和会话管理器之间的交互。
背景。多个X11服务器,多个显示设备
X11服务器与 "显示器 "的概念紧密相连,在X11术语中,它表示一个或多个显示器的集合(确切地说,是帧缓冲器),共享一组共同的输入设备(键盘、鼠标、平板电脑等),由一个用户使用。现在,X11意义上的帧缓冲器是一个模糊的术语。帧缓冲器是包含代表一个完整视频帧中所有像素的数据的RAM,它被RAM-DAC电路转换为模拟视频信号,可以显示在计算机显示器上,或显示在由该帧缓冲器驱动的一组显示器上。因此,在这个意义上,帧缓冲器被用来表示一个单一的显示器或一组由X服务器的单一实例驱动的显示器。一个X服务器可以在其xorg.conf配置文件中定义多个屏幕部分,以满足该服务器使用的多个帧缓冲器,每个都有自己的分辨率。
今天,有了GPU形式的图形加速器和模拟显示器以外的一系列视频显示设备,CPU并不直接访问帧缓冲器,而是向GPU发送特定的图形(例如OpenGL)命令,而GPU则迅速操纵多个视频内存缓冲器的内容,以加速创建图像,并输出到一个或多个视频显示设备。因此,现在的X服务器很少知道硬件加速器所驱动的显示器的真实数量。
另一方面,运行X服务器的CPU可以使用一个虚拟的(软件模拟的)帧缓冲器,例如,如果机器是无头的(没有显卡),或者在像VNC服务器这样的虚拟X服务器的情况下,图片帧被发送到一个远程软件查看器,而不是直接显示在本地设备上。
在一个操作系统实例中可以有多个(最多64个)X11服务器在运行,例如,如果一个较大的多用户系统支持这一点(在xorg.conf配置文件中定义的multiseat设置),则每个用户在每个X显示器上工作一个。从用户的角度来看,使用X服务器--以及使用正确的X服务器--需要指明显示器。
图2:Multiseat设置和显示器的命名。
每个显示名称都包括相关的X服务器的编号。显示屏名称的形式是。
Shell
hostname:displaynumber.screennumber
这个信息被应用程序用来确定如何连接到X服务器(网络字节流、共享内存等)。例如,如果没有给出主机名,就会使用与同一(本地)机器上的X服务器通信的最有效方式。显示屏号码在显示名称中是强制性的,并且在该显示的X服务器启动时被分配(从0开始)。因此,每个显示器可以被认为等同于一个正在运行的X服务器。屏幕号与帧缓冲器有关,现在通常是0。
默认的显示名称被存储在DISPLAY
环境变量中。比如说。
Shell
$ DISPLAY=myws:0; export DISPLAY
或者,大多数X程序接受一个命令行选项来覆盖DISPLAY
环境变量的值。
背景。用SSH进行X转发
X流量是不加密的,在一台机器上的第一个服务器使用TCP 6000端口。如果机器上运行多个X11服务器,第二个服务器使用TCP 6001端口监听,而第n个服务器使用TCP 6000+n端口监听。运行X服务器被认为是一种安全风险,因此,通往上述端口的外部流量通常被网络安全机制所阻止。
为了加密X流量,并在X端口被网络阻断时连接到X服务器,可以使用SSH。与X的角色相比,SSH客户端和服务器的角色是相反的:SSH客户端必须在X服务器上运行,而且连接必须指向X客户端要启动的计算机上的SSH服务器。为了通过SSH隧道传输X11,X11转发必须在两边启用--在SSH服务器和X服务器一边(在SSH客户端)。
在X服务器端,当用"-X"(大写X)选项调用时,SSH为当前会话启用X11转发。
Shell
x-server $ ssh -X username@x-clients-computer
图 3: 用 SSH 进行 X 转发
在 X 服务器端,通过在 SSH 客户端配置文件 (${HOME}/.ssh/config
) 中设置 "ForwardX11 yes",X11 转发可以在默认情况下为某些或所有连接永久启用。
在要运行X客户端的计算机上,"X11Forwarding yes "必须在ssh-server配置文件中指定(通常是:/etc/ssh/sshd_config
)。
在运行X客户端的计算机上,必须安装xauth程序,但不需要手动设置环境变量:变量DISPLAY
和XAUTHORITY
(关于XAUTHORITY
,详见下面的X11安全)由SSH服务器自动处理。在认证成功后,SSH 服务器首先为 X 客户端设置DISPLAY
环境变量,使其指向 SSH 服务器在此打开的本地 TCP 套接字,然后将 X11 通信隧道返回到 X 服务器上的 SSH 客户端。然后,SSH客户端生成一个代理MIT-MAGIC-COOKIE-1
,并将其传递给SSH服务器。SSH 服务器调用xauth
添加到要运行 X 客户端的计算机上的.Xauthority
文件中(见下面的X11 安全)。SSH客户端有责任接受这个代理MIT-MAGIC-COOKIE-1
,并通过一种称为认证欺骗的技术将其交换到X服务器的正确MIT-MAGIC-COOKIE-1
,如图4所示。
图4:SSH X转发过程中认证欺骗的技术。[来源:docstore.mik.ua/orelly/netw…]
虚拟网络计算(VNC)
通过本文,Xorg和VNC服务器被作为X服务器使用。VNC把X服务器的作用分成一个代理X服务器和一个VNC浏览器,它们通过网络与远程帧缓冲协议进行通信。VNC浏览器提供对物理输入设备(鼠标、键盘)和输出设备(显示器)的访问。VNC服务器被称为代理X服务器。它在许多方面的行为与任何X服务器一样。具体来说,到目前为止关于X11的所有内容对VNC服务器来说都是正确的。
代理X服务器只在虚拟帧缓冲器上运行,作为其输出设备--它不与本地物理GPU或本地显示器通信。当有一个远程观看者连接时,虚拟帧缓冲器的视频帧将通过网络发送。如果没有连接远程观看器,虚拟帧缓冲器的内容会被下一个帧悄悄覆盖。
操作指南。安装和使用Vnc
本文假设VNC服务器将在Linux计算机上运行,VNC浏览器将在Windows计算机上运行。
VNC有很多实现方式:TigerVNC、TightVNC、TurboVNC、UltraVNC和(非免费)RealVNC。在本文中,TightVNC被作为一个X服务器使用。在要运行X客户端的机器上安装TightVNC。 要安装TightVNC,请添加软件分发包turbovnc_2.2.6_amd64.deb[https://sourceforge.net/projects/turbovnc/files/2.2.6/turbovnc_2.2.6_amd64.deb/download]。包文件被安装到/opt/TurboVNC。
在启动VNC服务器之前,必须找到一个未使用的X服务器号码(见下文列出X服务器、X显示器和X屏幕),其范围为0-63,在下面的例子中用NNN表示。以任何用户身份运行,启动TightVNC服务器。
Shell
$ /opt/TurboVNC/bin/vncserver -localhost -autokill :NNN # replace NNN with a yet unused display number
第一次启动时,VNC服务器将更新(或创建,如果它不存在)带有认证信息的${HOME}/.Xauthority
文件(见下面的X安全)。在第一次启动时,TightVNC服务器也会要求设置一个8位数的密码。在后面的步骤中,需要这个密码来验证与TightVNC服务器的远程查看器的连接。没有其他的认证方式--知道这个密码和NNN,就可以进入启动TightVNC服务器的用户的图形会话。该密码存储在${HOME}/.vnc/passwd
,只有其所有者才能访问。
TightVNC服务器可以通过调用来停止。
Shell
$ /opt/TurboVNC/bin/vncserver -kill :NNN
VNC服务器号码NNN在TCP端口XXX=5900+NNN上监听,例如,NNN=2,服务器在TCP端口XXX=5902上监听。由于这个范围内的TCP端口出于安全原因通常被网络封锁,因此SSH隧道可以从装有VNC浏览器的Windows计算机到装有VNC服务器的Linux计算机上发挥作用。
要创建一个SSH隧道,必须指定两个端口。VNC服务器端口XXX(在Linux上)和带有VNC浏览器的计算机上的自由端口YYY(在Windows上)。Windows上的YYY端口可以在自由的本地端口中自由选择;为了简单起见,TCP端口YYY=5900+NNN可能值得尝试。
命令行SSH调用的一般形式是。
Shell
windows> ssh –L YYY:localhost:XXX username@IP
其中 "localhost "部分是在隧道的末端解决的,即 "localhost "这个名字指定了Linux计算机。IP是Linux计算机的互联网协议地址。
创建一个NNN=2的隧道的例子可以如下。
外壳
windows> ssh –L 5902:localhost:5902 artur@10.40.0.71
使用Putty或Kitty,可以通过如图5的设置来创建隧道。
图5:在Putty/Kitty中设置一个SSH隧道。
在SSH登录会话中成功认证后,隧道将被建立。Linux服务器的XXX端口可以通过连接到Windows系统的本地端口YYY来达到。因此,VNC客户端应该打开一个连接到localhost:YYY(对自己),以便使用SSH隧道实际连接到服务器的XXX端口。指引VNC客户端连接到server:XXX将是试图绕过已建立的隧道,直接连接到服务器,这可能会由于网络限制而失败。
最后一部分是在Windows上运行一个VNC浏览器。所有的VNC实现都是兼容的,所以要连接到TightVNC服务器,可以使用TightVNC以外的浏览器。在服务器和查看器中混合使用VNC,可能会导致网络流量性能不理想。 在本文中,TurboVNC被用作查看器,它与TigerVNC、TightVNC和RealVNC服务器等完全兼容。
要安装TightVNC查看器,请从www.tightvnc.com/download/1.… 下载并解压Windows版TightVNC查看器
启动vncviewer.exe查看器并设置以下字段。
VNC Server: localhost:NNN
,比如说localhost:2
在第一次启动VNC服务器时,你将被要求提供所设置的密码。认证成功后,vncviewer.exe会显示一个远程图形窗口,其中包含启动VNC服务器的用户的Linux桌面会话。与使用X服务器相反,对于VNC浏览器本身,在Windows端不需要设置DISPLAY
和XAUTHORITY
;但在Unix端仍然需要设置,以访问VNC服务器。
在Ubuntu 18.04.1和Gnome gdm3中,用户在启动图形会话后应立即禁用非活动状态下的自动屏幕锁定。否则,由于一个众所周知的错误,用户将很快无法解锁屏保,除非用。
贝壳
$ loginctl list-sessions # get <your-session-number>
$ loginctl unlock-session <your-session-number> # use <your-session-number> from above
Howto:列出X服务器、X显示器和X屏幕
似乎没有可靠的方法来获得一个系统中所有X11显示器的列表。这是可以理解的,考虑到如果DISPLAY
的hostname部分不是空的,X11显示器就不需要是本地的。如果一个用户是远程连接的,例如通过启用了X转发的SSH,实际上只有他的DISPLAY
环境变量可以找出他们的远程X服务器(使用X转发的SSH为本地服务器留下初始的X11DisplayOffset
号码。X11DisplayOffset
可以在sshd配置文件中设置,默认值为10,也就是说,默认情况下,数字0-9是留给本地X服务器的,因此DISPLAY
以10开头)。
相反,正在使用的X11显示器(本地以及远程)可以通过分析w或who命令的输出来识别。这只有在X窗口管理器在utmp中记录桌面登录的情况下才有效,而这并不总是这样。在下面的例子中,只有第一个X服务器与一个登录会话相关。
Shell
$ w
01:07:38 up 5 days, 58 min, 4 users, load average: 0.40, 0.37, 0.41
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
oli tty7 :0 Sat00 5days 4:22m 0.94s gnome-session --session=gnome-fallback
oli pts/4 :0 Sat00 47:09m 0.77s 0.77s /bin/bash
oli pts/6 :0 Wed02 0.00s 0.12s 0.00s w
试图获得一个只有本地X11显示的列表(当前使用的或未使用的)归结为列出X服务器,这也很棘手,因为X服务器的可执行名称可能不明显。要获得一个X服务器的列表,寻找X服务器的可执行文件名,例如,在下面命令的输出中寻找Xorg
或Xvnc
。在这个例子中,有三个X服务器正在运行。
Shell
$ ps aux | grep X
damian 31430 0.0 0.4 3106148 134768 ? Sl lip12 8:06 /opt/TurboVNC/bin/Xvnc :2 -desktop TurboVNC: my-x-server:2 (damian) -httpd /opt/TurboVNC/bin//../java -auth /home/damian/.Xauthority -geometry 1240x900 -depth 24 -rfbwait 120000 -rfbauth /home/damian/.vnc/passwd -x509cert /home/damian/.vnc/x509_cert.pem -x509key /home/damian/.vnc/x509_private.pem -rfbport 5902 -fp /usr/share/fonts/X11/misc,/usr/share/fonts/X11/Type1 -deferupdate 1 -dridir /usr/lib/x86_64-linux-gnu/dri -registrydir /usr/lib/xorg -localhost :2
root 141167 0.0 0.1 172316 48696 tty1 Sl+ 15:28 0:00 /usr/lib/xorg/Xorg vt1 -displayfd 3 -auth /run/user/125/gdm/Xauthority -background none -noreset -keeptty -verbose 3
artur 141521 0.0 0.1 2799872 57164 pts/1 Sl 15:29 0:00 /opt/TurboVNC/bin/Xvnc :1 -desktop TurboVNC: my-x-server:1 (artur) -httpd /opt/TurboVNC/bin//../java -auth /home/artur/.Xauthority -geometry 1240x900 -depth 24 -rfbwait 120000 -rfbauth /home/artur/.vnc/passwd -x509cert /home/artur/.vnc/x509_cert.pem -x509key /home/artur/.vnc/x509_private.pem -rfbport 5901 -fp /usr/share/fonts/X11/misc,/usr/share/fonts/X11/Type1 -deferupdate 1 -dridir /usr/lib/x86_64-linux-gnu/dri -registrydir /usr/lib/xorg -localhost :1
还有一种识别在主机上运行的X服务器的方法是查看/tmp/.X11-unix
下的显示套接字,看是否有提示表明服务器已被启动。这些只是提示,因为X服务器可以自由地在其他地方打开套接字。在下面的例子中,有三个服务器已经被启动,并将它们的显示套接字放在/tmp/.X11-unix
。
Shell
$ ls -l /tmp/.X11-unix
total 0
srwxrwxrwx 1 root root 0 lip 22 15:28 X0
srwxrwxrwx 1 artur team 0 lip 22 15:29 X1
srwxrwxrwx 1 damian team 0 lip 12 14:51 X2
如果显示套接字在其他地方被打开,可以通过下面的命令在打开的Unix域套接字列表中进行筛选,但同样,X服务器可执行文件的名称使搜索更容易。
Shell
$ netstat -lp|grep X
(不是所有的进程都能被识别出来,不属于自己的进程信息不会被显示出来,而且你必须是root才能看到所有的信息)
Shell
tcp 0 0 localhost:5901 0.0.0.0:* LISTEN 141521/Xvnc
tcp 0 0 localhost:5801 0.0.0.0:* LISTEN 141521/Xvnc
活跃的UNIX域套接字(只有服务器)。
纯文本
unix 2 [ ACC ] STREAM LISTENING 728369 - /tmp/.X11-unix/X0
unix 2 [ ACC ] STREAM LISTENING 726347 141521/Xvnc /tmp/.X11-unix/X1
unix 2 [ ACC ] STREAM LISTENING 728368 - @/tmp/.X11-unix/X0
unix 2 [ ACC ] STREAM LISTENING 715845 - @/tmp/dbus-XJ7Eroc7
unix 2 [ ACC ] STREAM LISTENING 717754 141572/dbus-daemon @/tmp/dbus-6jG7Er3hXU
unix 2 [ ACC ] STREAM LISTENING 195839 - /tmp/.X11-unix/X2
unix 2 [ ACC ] STREAM LISTENING 741382 141616/dbus-daemon @/tmp/dbus-dXuX09TlKl
为了获得更多关于已识别的(本地或远程)X服务器的信息,例如,它们的屏幕及其分辨率的列表,有必要连接到它们(这需要适当的授权,见下文),并使用Xlib函数查询:XOpenDisplay(), XScreenCount(), XDisplayWidth(), XDisplayHeight(), XScreenOfDisplay(), XCloseDisplay()
或使用以下命令之一。
壳牌
$ xset -q -display :0
或。
Shell
$ xdpyinfo -display :1
或。
壳
$ xprop -display :2
背景。X11安全
客户端连接到X显示服务器,例如,通过计算机网络或通过域套接字。这两种方式都可以被网络中或同一操作系统内的所有代理访问。对X服务器的访问允许读写正在使用该显示器的用户的所有信息:记录击键,阅读屏幕内容,显示自己的应用程序,等等。
因此,对X服务器的访问需要某种形式的授权。可用的访问控制机制如下。
-
基于主机的访问控制。
-
基于Cookie的访问控制:共享纯文本Cookie(MIT-MAGIC-COOKIE-1)或基于DES的私钥(XDM-AUTHORIZATION-1)。
-
基于Cookie的访问控制:基于Sun的安全rpc系统(SUN-DES-1)或Kerberos用户到用户(MIT-KERBEROS-5)。
使用基于主机的访问控制优先于其他形式的授权。
基于主机的访问控制
基于主机的访问方法包括指定一组被授权连接到X显示服务器的代理。程序xhost可以用来显示和改变这个集合。xhost管理由DISPLAY
环境变量指示的X服务器。改变访问控制的xhost命令行选项只能从 "控制主机 "运行,即运行X显示管理器的主机。即使如此,xhost本身也需要对X服务器的安全访问。
xhost接受一个名字作为其命令行参数。这个名字可以是一个主机名,但它也可以属于有趣的 "服务器解释 "或 "本地 "系列之一。local:(注意结尾的冒号)这个名字家族表示所有非网络(即本地)连接。服务器解释的值si:localuser:username可以指定一个名为username的本地用户。
给定的名字会被加入或从允许连接到X服务器的列表中删除。
$ xhost local: ###
将所有本地连接一次性加入到列表中;注意后面的冒号
$ xhost - si:localuser:artur ###
从列表中删除本地用户artur
打印当前是否启用了访问控制,并打印允许连接的名字列表。这是唯一可以在控制主机之外使用的选项。
$ xhost
这个在互联网上被广泛滥用。尽量避免它,因为它授予所有人访问权,即访问控制完全关闭。
$ xhost + ###
避免这种情况,特别是在生产中
打开访问控制(=恢复到默认值)。现有的连接不会被中断。
$ xhost -
一个使用xhost的例子。
$ xhost +
xhost: unable to open display ""
$ DISPLAY=:1 xhost
启用访问控制,只有被授权的客户可以连接
$ DISPLAY=:1 xhost +
禁用访问控制,客户可以从任何主机连接
$ DISPLAY=:1 xhost
禁用访问控制,客户可以从任何主机连接
$ DISPLAY=:1 xhost local:
非网络本地连接被添加到访问控制列表中
$ DISPLAY=:1 xhost
禁用访问控制,客户可以从任何主机上进行连接
LOCAL:
基于Cookie的访问控制
基于cookie的方法是基于生成一段数据("神奇的cookie"),并在X显示服务器启动时将其传递给它,通常通过一个命令行选项指示cookie文件的位置。每一个后来能证明知道这个cookie的客户端都被授予连接到服务器的授权。所有的X客户端一旦获得授权,就会获得完全相同的访问X服务器的权限--使用(或不使用)超级用户的权限并不影响这一点。即使是那些没有用X服务器登录计算机的用户启动的X客户端,一旦获得授权,也被允许完全访问(根据X安全扩展规范,在使用信任/不信任的情况下,影响不大)。
当用户通过X显示管理器登录时,Cookies也被X显示管理器存储在用户登录的计算机上,即他的X客户端将被启动的地方,用户可以访问的文件中。该文件在用户会话期间由XAUTHORITY
环境变量指示。如果没有设置XAUTHORITY
,则假定是${HOME}/.Xauthority
。.Xauthority
的文件访问权限只对文件的所有者而言是读和写。如果用户绕过cookie传输机制登录计算机,例如,通过没有启用X转发的SSH字符终端会话启动,则必须采取额外步骤,将cookie安全地复制到计算机上。
由于.Xauthority
文件是运行X服务器的要求,该文件是自动创建的,因此应该永远不需要手工生成它,但这样做的命令是。
$ xauth generate $DISPLAY . trusted
.Xauthority
文件可以用xauth
来管理。要根据可选择指定的XAUTHORITY
文件的位置,查看描述.Xauthority
文件的信息。
Shell
$ xauth info [ -f ${XAUTHORITY} ]
Authority file: /home/artur/.Xauthority
File new: no
File locked: no
Number of entries: 6
Changes honored: yes
Changes made: no
Current input: (argv):1
根据可选择指定的XAUTHORITY
文件位置,查看所有显示屏的授权条目,包括cookies。注意显示数字从0开始。下面,所有的显示都可以在本地(如mmy-x-server/unix:1
)和通过网络(如my-x-server:1
)访问。
Shell
$ xauth list [ -f ${XAUTHORITY} ]
my-x-server:1 MIT-MAGIC-COOKIE-1 504bc2122ba41de0e9e8b8c72af9553f
my-x-server/unix:1 MIT-MAGIC-COOKIE-1 504bc2122ba41de0e9e8b8c72af9553f
my-x-server:2 MIT-MAGIC-COOKIE-1 c3db197572e1faf37049473f9f207db4
my-x-server/unix:2 MIT-MAGIC-COOKIE-1 c3db197572e1faf37049473f9f207db4
my-x-server:3 MIT-MAGIC-COOKIE-1 71939bddd1039944b1e627554c015ce7
my-x-server/unix:3 MIT-MAGIC-COOKIE-1 71939bddd1039944b1e627554c015ce7
在属于不同用户的.Xauthority
文件之间复制服务器my-x-server上显示:2的本地Unix域套接字授权条目(需要超级用户权限)。
壳牌公司
# xauth -f /home/damian/.Xauthority list my-x-server/unix:2
my-x-server/unix:2 MIT-MAGIC-COOKIE-1 c3db197572e1faf37049473f9f207db4
# xauth -f /home/artur/.Xauthority add my-x-server/unix:2 MIT-MAGIC-COOKIE-1 c3db197572e1faf37049473f9f207db4
通过合并`${XAUTHORITY}
`的内容来复制cookies到~/.Xauthority: XAUTHORITY=~/.Xauthority
xauth merge "${XAUTHORITY}
"
Howto:显示X客户端GUI
在SSH登录成功后,如果没有X服务器的转发,进入要启动X客户端的计算机,DISPLAY
和XAUTHORITY
变量最初都没有设置。因此,试图启动一个X客户端失败了。
$ xeyes
Error: Can't open display:
在找到正确的X服务器显示号码后(见:列出上面的X服务器、显示和屏幕),启动X客户端仍然失败--这次是由于授权不足。
$ DISPLAY=:1 xeyes ###
另一种情况。 xeyes -display :1
No protocol specified
Cannot open display::1
粗放的、被严重滥用的授权方法是使用基于主机的访问控制。但由于xhost本身首先需要授权,它通常必须从xterm中运行,即从图形会话,而不是从字符终端SSH会话。
Shell
xterm$ DISPLAY=:1 xhost si:localuser:artur
ssh$ DISPLAY=:1 xeyes
另一种方法是获取与相关X服务器有关的.Xauthority
文件的内容(或只是单个cookie)。有以下几种选择。
-
在有关的X服务器上打开任何X客户端,可能通过图形登录会话的方式。使用在启动X客户端的机器上创建的
.Xauthority
文件。 -
从另一个用户那里获得
.Xauthority
的内容。注意:只有当用户以前使用基于cookie的访问控制与X服务器一起工作时,.Xauthority
才会包含有关的X服务器的cookie。 -
从一个运行中的X客户端获取
DISPLAY
和XAUTHORITY
的值。注意:只有当这个X客户端的用户曾经使用基于cookie的访问控制与X服务器合作时,.Xauthority
才会包含有关的X服务器的cookie。 -
从运行中的X服务器进程的命令行选项中获取
.Xauthority
文件的路径。
上面的前两个选项在本文的前几部分应该是很清楚的。要想从运行中的X客户端获得DISPLAY
和XAUTHORITY
的值,那么必须弄清连接到有关X服务器的进程的PID。
$ cat /proc/${PID}/environ | tr '\0' '\n' | grep -E '^(DISPLAY|XAUTHORITY)='
要从运行中的X服务器进程的命令行选项中获得.Xauthority
文件的路径,在ps
命令的输出中寻找Xauthority。在下面的例子中,X服务器:0使用/run/user/125/gdm/Xauthority
,X服务器:1使用/home/artur/.Xauthority
,而X服务器:2使用/home/damian/.Xauthority
。
壳牌公司
$ ps aux | grep X
damian 31430 0.0 0.4 3106148 134768 ? Sl lip12 8:06 /opt/TurboVNC/bin/Xvnc :2 -desktop TurboVNC: my-x-server:2 (damian) -httpd /opt/TurboVNC/bin//../java -auth /home/damian/.Xauthority -geometry 1240x900 -depth 24 -rfbwait 120000 -rfbauth /home/damian/.vnc/passwd -x509cert /home/damian/.vnc/x509_cert.pem -x509key /home/damian/.vnc/x509_private.pem -rfbport 5902 -fp /usr/share/fonts/X11/misc,/usr/share/fonts/X11/Type1 -deferupdate 1 -dridir /usr/lib/x86_64-linux-gnu/dri -registrydir /usr/lib/xorg -localhost :2
root 141167 0.0 0.1 172316 48696 tty1 Sl+ 15:28 0:00 /usr/lib/xorg/Xorg vt1 -displayfd 3 -auth /run/user/125/gdm/Xauthority -background none -noreset -keeptty -verbose 3
artur 141521 0.0 0.1 2799872 57164 pts/1 Sl 15:29 0:00 /opt/TurboVNC/bin/Xvnc :1 -desktop TurboVNC: my-x-server:1 (artur) -httpd /opt/TurboVNC/bin//../java -auth /home/artur/.Xauthority -geometry 1240x900 -depth 24 -rfbwait 120000 -rfbauth /home/artur/.vnc/passwd -x509cert /home/artur/.vnc/x509_cert.pem -x509key /home/artur/.vnc/x509_private.pem -rfbport 5901 -fp /usr/share/fonts/X11/misc,/usr/share/fonts/X11/Type1 -deferupdate 1 -dridir /usr/lib/x86_64-linux-gnu/dri -registrydir /usr/lib/xorg -localhost :1
由于上面的例子中X服务器 :1使用了/home/artur/.Xauthority
,任何对该文件有读取权限的用户都可以在服务器上启动一个X客户端。
$ XAUTHORITY=/home/artur/.Xauthority DISPLAY=:1 xeyes
在右端渲染Opengl
OpenGL库和驱动程序
梅萨
OpenGL作为一种语言规范,需要软件实现。OpenGL的开源实现是在Mesa项目中完成的,它还实现了其他图形API,包括OpenGL ES、OpenCL、OpenMAX、VDPAU、VA-API、XvMC、Vulkan和EGL。各种设备驱动使得Mesa库可以为一些GPU提供软件仿真以及完整的硬件加速。
Mesa库通过LLVMPipe或swrast或softpipe驱动在软件中提供OpenGL渲染。实现也可以与特定于GPU的驱动相结合,提供硬件加速的渲染。Mesa库使用AMD、Broadcom、Intel i965、Intel ANV和Intel Iris等驱动程序。
Mesa和硬件驱动遵循直接渲染基础设施(DRI)架构。X11的最初假设是,X服务器拥有对图形硬件的独家访问权,因此,X服务器是唯一能够在帧缓冲区上实际渲染的进程。由于这样的间接渲染在OpenGL管道中为大量的3D数据引入了延迟,DRI引入了X客户端的直接渲染。DRI架构协调了Linux内核、X窗口系统、GPU硬件和一个基于OpenGL的渲染引擎,如Mesa。Linux内核中的直接渲染管理器(DRM)将3D图形硬件视为一种共享资源,以支持多个同时进行的3D应用。
间接渲染只限于libGLX.so中定义了协议编码的功能集。这个库提供了对X协议的扩展,它将OpenGL与X11窗口系统绑定在一起,使X窗口的3D绘图成为可能。
OpenGL调度表
OpenGL需要在运行时通过特定平台的API调用明确加载其API函数,以获得指向核心以及扩展(例如GLX)的指针。这通常是通过特定于平台的OpenGL加载库(例如GLEW、GL3W、glad)完成的。在基于X11的系统上,函数glXGetProcAddress()
,在内部用于按函数名称检索函数指针。这个函数在没有OpenGL上下文的情况下运行,也就是说,返回的函数没有与任何上下文相关联,而且可能不被某个特定的上下文所支持。
libGL.so是libGLX.so的一个封装器,所以它实现了GLX接口以及主要的OpenGL API入口。当使用间接渲染时,libGL.so会创建GLX协议信息并通过套接字将它们发送到X服务器。
GL Vendor-Neutral Dispatch Library
在英伟达按照XDC 2013的提议提供GL厂商中立调度库之前,libGL.so是针对特定厂商的,因为它包含一个与特定驱动程序相耦合的OpenGL实现。因此,在一台电脑上使用不同的OpenGL渲染器并不容易,而且软件分发包的依赖关系被破坏,可能导致离开或安装错误的libGL.so版本或构建。
旨在为OpenGL渲染提供硬件加速的厂商专用驱动程序和库的激增,导致了GL厂商中立调度库(libglvnd)的出现。libglvnd的目标是允许来自不同供应商的多个OpenGL实现在同一个系统上共存,而不互相干扰或需要手动配置。有了libglvnd,libGL.so变成了一个独立于厂商的调度库,而不是任何驱动程序的一部分。每个厂商都在一个单独的库中提供其OpenGL实现。应用程序仍然链接到libGL.so,但libGL.so会自动加载适当的3D DRI驱动程序。
大多数GLX入口点都明确或隐含地指定了一个X屏幕。在这种情况下,当libGLX.so第一次被调用时,它就会向X服务器查询该X屏幕的GLX供应商,以便从LIBGL_DRIVERS_PATH环境变量定义的搜索补丁中加载相应的libGLX_VENDOR.so。libGLX_VENDOR.so的GLX调度表被读取,这个映射被缓存起来,以便在随后直接向该驱动程序调度OpenGL库调用时使用。通过这种方式,libGL.so也有能力支持异构的多头配置。如果一个系统中有多个不同类型的显卡,一个应用程序可以同时使用所有这些显卡。
在安装了libglvnd的系统中,应用程序可以照常使用libGL.so,也可以使用libGLX.so代替。libGLX.so只输出GLX入口点,所以要获得针对其中一个入口点库的OpenGL函数链接,一般来说,可能需要libOpenGL.so--如果不使用OpenGL加载库。由于所有GL入口点只有一个共同的调度表,每个函数的名称都只存在一次,与查找库无关:libOpenGL.so、libGL.so、libGLES*.so、glXGetProcAddress或eglGetProcAddress。
图6:GL Vendor-Neutral Dispatch库。[来源 :github.com/freedesktop…]
Howto:通过X服务器选择渲染器
一个OpenGL应用程序应该与GL库相连。
Shell
$ ldd /tmp/app/bin/App | fgrep GL
libQt5OpenGL.so.5 => /opt/qt/lib/libQt5OpenGL.so.5 (0x00007f4d8f095000)
libGL.so.1 => /usr/lib/x86_64-linux-gnu/libGL.so.1 (0x00007f4d8a447000)
libGLX.so.0 => /usr/lib/x86_64-linux-gnu/libGLX.so.0 (0x00007f4d846f3000)
libGLdispatch.so.0 => /usr/lib/x86_64-linux-gnu/libGLdispatch.so.0 (0x00007f4d8443d000)
上述库与一些软件发行包有关。在amd64架构的Ubuntu 18.04.1 LTS (bionic)上,这些软件包如下。
Shell
$ dpkg -S /usr/lib/x86_64-linux-gnu/libGL.so\* /usr/lib/x86_64-linux-gnu/libGLX.so\* /usr/lib/x86_64-linux-gnu/libGLdispatch.so\*
libgl1:amd64: /usr/lib/x86_64-linux-gnu/libGL.so.1
libglvnd-dev:amd64: /usr/lib/x86_64-linux-gnu/libGL.so
libgl1:amd64: /usr/lib/x86_64-linux-gnu/libGL.so.1.0.0
libglx0:amd64: /usr/lib/x86_64-linux-gnu/libGLX.so.0
libglvnd-dev:amd64: /usr/lib/x86_64-linux-gnu/libGLX.so
libglx0:amd64: /usr/lib/x86_64-linux-gnu/libGLX.so.0.0.0
libglvnd0:amd64: /usr/lib/x86_64-linux-gnu/libGLdispatch.so.0
libglvnd-dev:amd64: /usr/lib/x86_64-linux-gnu/libGLdispatch.so
libglvnd0:amd64: /usr/lib/x86_64-linux-gnu/libGLdispatch.so.0.0.0
在amd64架构的Ubuntu 18.04.1 LTS (bionic)上,这些文件来自libglvnd。
Shell
$ dpkg -l libgl1 libglvnd-dev libglvnd0 libglx0
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name Version Architecture Description
+++-=========================================-=========================-=========================-=======================================================================================
ii libgl1:amd64 1.0.0-2ubuntu2.3 amd64 Vendor neutral GL dispatch library -- legacy GL support
ii libglvnd-dev:amd64 1.0.0-2ubuntu2.3 amd64 Vendor neutral GL dispatch library -- development files
ii libglvnd0:amd64 1.0.0-2ubuntu2.3 amd64 Vendor neutral GL dispatch library
ii libglx0:amd64 1.0.0-2ubuntu2.3 amd64 Vendor neutral GL dispatch library -- GLX support
但也有其他GL库可用,总共有38个文件和链接。
Shell
$ ls /usr/lib/x86_64-linux-gnu/*GL* | wc -l
38
在这些库中,有Mesa的库。
Shell
$ dpkg -S /usr/lib/x86_64-linux-gnu/*GL*.so* | fgrep mesa
libegl-mesa0:amd64: /usr/lib/x86_64-linux-gnu/libEGL_mesa.so.0
libegl-mesa0:amd64: /usr/lib/x86_64-linux-gnu/libEGL_mesa.so.0.0.0
libglu1-mesa-dev:amd64: /usr/lib/x86_64-linux-gnu/libGLU.so
libglu1-mesa:amd64: /usr/lib/x86_64-linux-gnu/libGLU.so.1
libglu1-mesa:amd64: /usr/lib/x86_64-linux-gnu/libGLU.so.1.3.1
libglx-mesa0:amd64: /usr/lib/x86_64-linux-gnu/libGLX_indirect.so.0
libglx-mesa0:amd64: /usr/lib/x86_64-linux-gnu/libGLX_mesa.so.0
libglx-mesa0:amd64: /usr/lib/x86_64-linux-gnu/libGLX_mesa.so.0.0.0
还有一些供应商提供的库,是3D驱动的一部分。在这种情况下,这是适用于NVIDIA GFX 1080显卡的NVIDIA驱动程序v.450。
贝壳
$ dpkg -S /usr/lib/x86_64-linux-gnu/*GL*.so* | fgrep -i NVIDIA
libNVIDIA-gl-450-server:amd64: /usr/lib/x86_64-linux-gnu/libEGL_NVIDIA.so.0
libNVIDIA-gl-450-server:amd64: /usr/lib/x86_64-linux-gnu/libEGL_NVIDIA.so.450.119.04
libNVIDIA-gl-450-server:amd64: /usr/lib/x86_64-linux-gnu/libGLESv1_CM_NVIDIA.so.1
libNVIDIA-gl-450-server:amd64: /usr/lib/x86_64-linux-gnu/libGLESv1_CM_NVIDIA.so.450.119.04
libNVIDIA-gl-450-server:amd64: /usr/lib/x86_64-linux-gnu/libGLESv2_NVIDIA.so.2
libNVIDIA-gl-450-server:amd64: /usr/lib/x86_64-linux-gnu/libGLESv2_NVIDIA.so.450.119.04
libNVIDIA-gl-450-server:amd64: /usr/lib/x86_64-linux-gnu/libGLX_NVIDIA.so.0
libNVIDIA-gl-450-server:amd64: /usr/lib/x86_64-linux-gnu/libGLX_NVIDIA.so.450.119.04
本例计算机配备了一个GPU,即NVIDIA GeForce GTX 1080。
壳
$ sudo lshw -C display
*-display
description: VGA compatible controller
product: GP104 [GeForce GTX 1080]
vendor: NVIDIA Corporation
physical id: 0
bus info: pci@0000:05:00.0
version: a1
width: 64 bits
clock: 33MHz
capabilities: pm msi pciexpress vga_controller bus_master cap_list rom
configuration: driver=NVIDIA latency=0
resources: irq:53 memory:ee000000-eeffffff memory:d0000000-dfffffff memory:e0000000-e1ffffff ioport:c000(size=128) memory:c0000-dffff
人们还可以通过使用mesa-utils软件发行包中的glxinfo程序查询所有现有的X服务器,来检查哪个X服务器使用了NVIDIA GeForce GTX 1080。为了获得适当的授权,在下面的每个例子中都使用了正确的.Xauthority
;该程序以超级用户的权限被调用,以获得对其他用户(显示器:0和:2)的.Xauthority
文件的读取权限。可以看出,显示器:0(与Xorg服务器有关)由NVIDIA显卡驱动,提供OpenGL 4.6.0,而其余两个显示器(与VNC X服务器有关)使用Mesa与llvmpipe
软件渲染器,提供OpenGL 3.1。
$ sudo XAUTHORITY=/run/user/125/gdm/Xauthority glxinfo -display :0 | grep -E "vendor|OpenGL version string"
server glx vendor string: NVIDIA Corporation
client glx vendor string: NVIDIA Corporation
OpenGL vendor string: NVIDIA Corporation
OpenGL version string: 4.6.0 NVIDIA 450.119.04
$ glxinfo -display :1 | grep -E "Vendor|Device:|OpenGL version string"
Vendor: Mesa/X.org (0xffffffff)
Device: llvmpipe (LLVM 11.0.0, 256 bits) (0xffffffff)
OpenGL version string: 3.1 Mesa 20.2.6
$ sudo XAUTHORITY=/home/damian/.Xauthority glxinfo -display :2 | grep -E "Vendor|Device:|OpenGL version string"
Vendor: Mesa/X.org (0xffffffff)
Device: llvmpipe (LLVM 11.0.0, 256 bits) (0xffffffff)
OpenGL version string: 3.1 Mesa 20.2.6
上述情况表明,这是一个安装了Mesa、NVIDIA驱动程序和libglvnd
的系统,并且配备了NVIDIA GeForce GTX 1080,它正在驱动显示器:0。因此,一个拥有正确定义的DISPLAY的OpenGL应用程序应该首先调用libglvnd
。然后,libglvnd将联系与该DISPLAY相关的X服务器,以获得相关的驱动程序。然后将使用适当的库(无论是用于硬件加速渲染的NVIDIA库还是用于软件渲染的Mesa库)。这种行为可以用任何OpenGL X客户端来检查,但是来自amd64virtualgl
软件发行包[https://sourceforge.net/projects/virtualgl/files/2.6.5/]的程序glxspheres64
,除了在相应的显示器上渲染移动的球体之外,还提供了有价值的字符终端SSH会话的信息(如果只打开了SSH登录会话,将保持不可见)。从下面可以看出,在X服务器:0上的渲染是用GeForce GTX 1080完成的,每秒超过3100帧;在X服务器:1和:2上的渲染是用软件渲染器llvmpipe完成的,每秒达到约30帧。
壳
$ sudo DISPLAY=:0 XAUTHORITY=/run/user/125/gdm/Xauthority /opt/VirtualGL/bin/ glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0xad (8/8/8/0)
Visual ID of window: 0x27
Context is Direct
OpenGL Renderer: GeForce GTX 1080/PCIe/SSE2
3161.386940 frames/sec - 3528.107825 Mpixels/sec
3108.203579 frames/sec - 3468.755194 Mpixels/sec
$ DISPLAY=:1 /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0x164 (8/8/8/0)
Visual ID of window: 0x3f7
Context is Direct
OpenGL Renderer: llvmpipe (LLVM 11.0.0, 256 bits)
30.220802 frames/sec - 29.509042 Mpixels/sec
29.964094 frames/sec - 29.258380 Mpixels/sec
^C
$ sudo DISPLAY=:2 XAUTHORITY=/home/damian/.Xauthority /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0x164 (8/8/8/0)
Visual ID of window: 0x3f7
Context is Direct
OpenGL Renderer: llvmpipe (LLVM 11.0.0, 256 bits)
30.791668 frames/sec - 35.626699 Mpixels/sec
28.124818 frames/sec - 32.541090 Mpixels/sec
^C
Howto:用VirtualGL控制渲染的X服务器
在上面的例子中,X客户端在每次调用时只使用一个X服务器,这对X11来说是很自然的。因此,同一个X服务器被用于渲染和显示移动的球体。VirtualGL允许一个应用程序同时使用两个X服务器:一个用于渲染,另一个用于显示。这为控制VNC会话是否使用硬件加速的OpenGL渲染提供了机会。
VirtualGL可以从amd64virtualgl
软件分发包中安装[sourceforge.net/projects/vi…
VirtualGL软件的两个主要部分是插值库和/opt/VirtualGL/bin/vglrun
shell脚本。shell脚本的作用是设置LD_PRELOAD
环境变量。插件库的内容如下。
Shell
$ dpkg -L virtualgl | fgrep faker
/usr/lib/libvglfaker-nodl.so
/usr/lib/libgefaker.so
/usr/lib/libvglfaker-opencl.so
/usr/lib/libvglfaker.so
/usr/lib/libdlfaker.so
大多数应用程序使用共享库,因此依赖于动态链接,特别是标准C库(libc)或X窗口或OpenGL库等系统库。共享库为软件管理提供了优势,并允许节省磁盘和运行内存中的空间。
通过共享库,运行时链接器首先在动态可执行文件中搜索一个符号,然后默认在每个依赖文件中搜索。第一个符合搜索条件的符号被接受。换句话说,如果同一个符号有多个实例存在,第一个实例会盖过所有其他实例。这被用作互换库的基本概念,互换库提供多余的符号(例如,函数),正是为了在其他库上进行互换。一个互置库通常最终会调用原来的互置函数,但保留了在调用互置函数之前和之后做额外处理的选项。
为了允许插值,必须改变运行时链接器的默认搜索顺序。这是通过在LD_PRELOAD
环境变量中提供库的完整路径来实现的,这就是/opt/VirtualGL/bin/vglrun
shell脚本在指定应用程序的环境中所做的。它将提供VirtualGL功能的库添加到LD_PRELOAD
。如果设置了LD_PRELOAD
变量,运行时链接器将首先搜索其中提到的库,按照它们被列出的顺序,并在任何其他依赖关系之前搜索。
预定的VirtualGL功能是将libGLX
请求(应用程序的X11命令和X11事件)发送给 "渲染X服务器",即发送给管理GPU的X服务器,以便在物理设备上进行渲染,在本地显示器上伪造显示。libGL
,用与渲染X服务器有关的渲染器渲染帧。通常最好是做硬件加速渲染,但同样的机制也可以用来在只提供软件渲染器的渲染X服务器中获得渲染。
渲染后的未压缩的二维帧,然后由VirtualGL中间库以X11传输的方式传递给显示X服务器(通常是:VNC代理)。显示器X服务器接收来自应用程序和VirtualGL的X11命令,并将X11命令渲染成帧。
如果显示X服务器连接到一个显示器,它就会在上面显示结果。如果显示X服务器是一个VNC代理,并与VNC查看器有活动连接,VNC代理通过网络向查看器发送压缩的图像流,也接受来自它的键盘和鼠标事件。
显示的X服务器是由DISPLAY
环境变量表示的。渲染的X显示器是作为/opt/VirtualGL/bin/vglrun
shell脚本的一个参数表示的。
图7:VirtualGL穿插库,用于使用单独的X服务器进行渲染和显示。
要同时使用两个X服务器,需要指定X服务器并确保对它们的开放访问。后者最优雅的做法是将两个X服务器的.Xauthority
文件合并到一个新文件中,并使用这个新文件来调用X客户端。
Shell
$ export XAUTHORITY=/tmp/Xauthority
$ cat /dev/null > $XAUTHORITY
$ xauth -f /home/artur/.Xauthority list
$ xauth -f /home/artur/.Xauthority list
pacer:1 MIT-MAGIC-COOKIE-1 88175224b412cda4f5317e76ac322a77
pacer/unix:1 MIT-MAGIC-COOKIE-1 88175224b412cda4f5317e76ac322a77
$ sudo xauth -f /run/user/125/gdm/Xauthority list
pacer/unix: MIT-MAGIC-COOKIE-1 d3bc373d24669f18e2b41b9d1047bdad
#ffff#7061636572#: MIT-MAGIC-COOKIE-1 d3bc373d24669f18e2b41b9d1047bdad
$ xauth add pacer/unix:0 MIT-MAGIC-COOKIE-1 d3bc373d24669f18e2b41b9d1047bdad
$ xauth add pacer/unix:1 MIT-MAGIC-COOKIE-1 88175224b412cda4f5317e76ac322a77
$ xauth list
pacer/unix:0 MIT-MAGIC-COOKIE-1 d3bc373d24669f18e2b41b9d1047bdad
pacer/unix:1 MIT-MAGIC-COOKIE-1 88175224b412cda4f5317e76ac322a77
在下面的例子中,渲染是在vglrun
的命令行选项'-d'指示的X服务器上完成的。显示是在由DISPLAY环境变量指示的X服务器上完成的。可以看出,用llvmpipe
(在display :1)进行软件渲染可以达到每秒26-28帧,而用GeForce GTX 1080进行硬件加速渲染(在display :0)可以达到每秒154-224帧。硬件加速带来了显著的性能提升,不受用于显示的X服务器的影响,但仍然远远低于没有virtualgl
,在单个X服务器上进行渲染和显示时实现的每秒3100帧。
壳牌公司
$ DISPLAY=:0 /opt/VirtualGL/bin/vglrun -d :0 /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0xad (8/8/8/0)
Visual ID of window: 0x21
Context is Direct
OpenGL Renderer: GeForce GTX 1080/PCIe/SSE2
189.055882 frames/sec - 210.986364 Mpixels/sec
158.437785 frames/sec - 176.816568 Mpixels/sec
154.759115 frames/sec - 172.711172 Mpixels/sec
^C
$ DISPLAY=:1 /opt/VirtualGL/bin/vglrun -d :0 /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0xad (8/8/8/0)
Visual ID of window: 0x21
Context is Direct
OpenGL Renderer: GeForce GTX 1080/PCIe/SSE2
219.400586 frames/sec - 214.233264 Mpixels/sec
222.099017 frames/sec - 216.868141 Mpixels/sec
224.862969 frames/sec - 219.566996 Mpixels/sec
^C
DISPLAY=:0 /opt/VirtualGL/bin/vglrun -d :1 /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0x164 (8/8/8/0)
Visual ID of window: 0x21
Context is Direct
OpenGL Renderer: llvmpipe (LLVM 11.0.0, 256 bits)
26.675161 frames/sec - 29.769480 Mpixels/sec
26.894907 frames/sec - 30.014716 Mpixels/sec
26.374685 frames/sec - 29.434149 Mpixels/sec
26.840482 frames/sec - 29.953978 Mpixels/sec
^C
$ DISPLAY=:1 /opt/VirtualGL/bin/vglrun -d :1 /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0x164 (8/8/8/0)
Visual ID of window: 0x21
Context is Direct
OpenGL Renderer: llvmpipe (LLVM 11.0.0, 256 bits)
27.606186 frames/sec - 26.956005 Mpixels/sec
28.523979 frames/sec - 27.852182 Mpixels/sec
28.083525 frames/sec - 27.422102 Mpixels/sec
^C
在Linux Docker容器中进行硬件加速的OpenGL渲染
从上面所说的,Linux容器需要访问X服务器进行OpenGL渲染应该是很明显的。这里似乎有三种选择:通过网络访问远程X服务器,访问主机上运行的X服务器,或者在容器中运行X服务器。
通过网络访问X服务器可能会产生通信延迟和带宽限制,但这可能是一个可行的选择,尤其是在主机上没有X服务器的情况下。作者还没有对在容器中运行X服务器(如x11vnc)进行彻底研究。最直接的选择似乎是使用已经在主机上运行的X服务器(如果有的话),并使用主机的GPU驱动程序,反正必须在主机上安装,因为Linux内核不是容器的一部分。但这并没有为容器提供一个安全的沙箱:它授予容器对主机上的X服务器的完全访问权,所以它假定这两者之间是相互信任的。
从Docker容器中访问X服务器
要从Docker容器中访问主机的X服务器,必须让Unix域套接字对容器可用。这可以通过绑定挂载主机的/tmp/.X11-unix
目录作为一个容器卷来实现。剩下的两个信息与在任何配置中使用X服务器时相同:必须定义显示(例如,通过DISPLAY
环境变量),并且必须打开对X服务器的访问(例如,.Xauthority
cookies应该被传递给容器)。
假设文件系统的权限允许容器中使用的用户名访问,就有可能绑定挂载主机的.Xauthority
文件,使其在容器中可用。它有一个轻微的缺点,即虽然该文件可以在容器中自由编辑(取决于主机文件系统的写入权限,并以读写模式挂载该文件),但它不能从容器文件系统中删除(即/bin/rm .Xauthority
是不允许的)。
由于主机的.Xauthority
文件指的是主机的主机名,因此将容器命名为与主机相同的名称可能是有用的(见下面的选项"--主机名");否则,必须对容器中的.Xauthority
进行调整,将显示名称改为容器的当前名称,以使用Unix域套接字。
然而,另一个选择是重新编写.Xauthority
文件,以完全禁止使用主机名。为了实现这一点,.Xauthority
中的验证家族必须被设置为 "FamilyWild",即十六进制的0xffff。下面的例子就是这样做的,将重新编写的文件保存在XAUTH
环境变量中指定的名称下。
$ xauth nlist :0 | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
由于例子中的计算机配备了GPU,尽管目前没有必要,但下面使用了参数"--gpus all"。在随后的涉及硬件加速渲染的例子中,这个参数是必需的。
最后,启动一个Docker容器的形式可以是。
Shell
host$ docker run -ti \
--hostname `hostname` \
-v /home/artur/.Xauthority:/tmp/.Xauthority \
-v /tmp/.X11-unix:/tmp/.X11-unix \
--env DISPLAY=:1 \
--gpus all \
...rest of parameters omitted...
假设/tmp/.Xauthority
包含正确的cookie,用于DISPLAY=:1
,来自容器的X客户端可以在主机的X服务器上以完全相同的方式显示,就像在主机上一样。
container$ XAUTHORITY=/tmp/.Xauthority xeyes
### the value of DISPLAY has already been passed when starting the container.
图8:使用Linux主机上的X服务器显示容器中运行的X客户端的GUI。
Docker容器中的NVIDIA驱动程序和库
英伟达提供了英伟达容器工具包,用于构建和运行GPU加速的容器。该工具包包括一个容器运行库和实用程序,用于自动配置容器以利用NVIDIA GPU。要安装所有相关的软件分发包,只需安装主要的nvidia-Docker2软件分发包即可。
英伟达软件栈通过一些来自/usr/lib/x86_64-linux-gnu/
的用户区库与英伟达驱动程序进行通信。这些库由英伟达驱动程序运送并安装在主机上,因为驱动程序的API并不稳定,所以每个英伟达驱动程序都需要自己的这些库的版本。容器上也需要这些库,尽管它们不能安装在那里,以免将容器与特定的内核驱动版本捆绑在一起,因为将这样的容器移动到另一台具有不同内核驱动的机器上会导致兼容性问题。因此,在创建容器时,libnvidia-tainer组件通过调用nvidia-container-cli
,将这些库绑定到容器中。这些文件被挂载为只读,其路径与当前主机文件系统中的路径相同。这些文件中的一些是。
Shell
container$ mount | fgrep /usr/lib/x86_64-linux-gnu
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-cfg.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-eglcore.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-glcore.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-tls.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-glsi.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-fbc.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-ifr.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-rtcore.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvoptix.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libGLX_nvidia.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libEGL_nvidia.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libGLESv2_nvidia.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libGLESv1_CM_nvidia.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-glvkspirv.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
/dev/sdb4 on /usr/lib/x86_64-linux-gnu/libnvidia-cbl.so.450.119.04 type ext4 (ro,nosuid,nodev,relatime,errors=remount-ro)
这些文件为容器提供了库和用户区--在Linux内核中使用DRM的DRI的一部分。因此,该容器可以访问GPU的全部功能。容器中运行的X客户端可以执行这些库中的代码,与X服务器进行通信。
图9:从容器内访问NVIDIA设备。
通过在容器内运行NVIDIA工具nvidia-smi
,可以验证容器的NVIDIA设置。该工具通过返回如下表格来确认正确的设置。否则,将返回一个错误信息。
壳牌
container$ nvidia-smi
Tue Jul 6 16:45:26 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 450.119.04 Driver Version: 450.119.04 CUDA Version: N/A |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 GeForce GTX 1080 Off | 00000000:05:00.0 Off | N/A |
| 33% 34C P8 8W / 180W | 97MiB / 8117MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
+-----------------------------------------------------------------------------+
The device permissions should generally allow read and write access to all users, but tighter permissions can be crafted for increased security of rendered data:
container$ ls -la /dev/nvidia*
crw-rw-rw- 1 root root 195, 254 Jul 9 13:56 /dev/nvidia-modeset
crw-rw-rw- 1 root root 234, 0 Jul 9 13:56 /dev/nvidia-uvm
crw-rw-rw- 1 root root 234, 1 Jul 9 13:56 /dev/nvidia-uvm-tools
crw-rw-rw- 1 root root 195, 0 Jul 9 13:56 /dev/nvidia0
crw-rw-rw- 1 root root 195, 255 Jul 9 13:56 /dev/nvidiactl
/dev/nvidia-caps:
total 0
drwxr-xr-x 2 root root 80 Jul 9 14:40 .
drwxr-xr-x 21 root root 4620 Jul 24 13:12 ..
cr-------- 1 root root 237, 1 Jul 9 14:40 nvidia-cap1
cr--r--r-- 1 root root 237, 2 Jul 9 14:40 nvidia-cap2
Howto:在Docker容器中控制渲染功能
通过挂载Unix域套接字来访问容器中所有主机的X服务器,通过挂载.Xauthority
文件来获得X服务器的安全授权,并且拥有OpenGL库,可以在容器中轻松运行OpenGL X客户端。
VirtualGL可以从amd64virtualgl
软件分发包[https://sourceforge.net/projects/virtualgl/files/2.6.5/]中安装在容器中。
图10:从Docker容器中在两个不同的外部X服务器上进行渲染和显示。
假设VirtualGL已经安装在容器中,就可以进行以下演示了。在X服务器host:0上渲染和显示(假设主机和容器有相同的名字)。
壳牌公司
container$ DISPLAY=:0 XAUTHORITY=/tmp/.Xauthority /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0xad (8/8/8/0)
Visual ID of window: 0x27
Context is Direct
OpenGL Renderer: GeForce GTX 1080/PCIe/SSE2
2571.232719 frames/sec - 2869.495714 Mpixels/sec
2681.292557 frames/sec - 2992.322493 Mpixels/sec
2692.696660 frames/sec - 3005.049473 Mpixels/sec
^C
在X服务器host:1上渲染和显示(假设主机和容器有相同的名字),进行远程显示。
Shell
container$ DISPLAY=:1 XAUTHORITY=/tmp/.Xauthority /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0x164 (8/8/8/0)
Visual ID of window: 0x3f7
Context is Direct
OpenGL Renderer: llvmpipe (LLVM 10.0.0, 256 bits)
28.178620 frames/sec - 27.514957 Mpixels/sec
28.541789 frames/sec - 27.869573 Mpixels/sec
27.822042 frames/sec - 27.166777 Mpixels/sec
^C
在X服务器host:0上用VirtualGL渲染和显示(假设主机和容器有相同的名字)。
Shell
container$ DISPLAY=:0 XAUTHORITY=/tmp/.Xauthority /opt/VirtualGL/bin/vglrun -d :0 /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0xad (8/8/8/0)
Visual ID of window: 0x21
Context is Direct
OpenGL Renderer: GeForce GTX 1080/PCIe/SSE2
203.614670 frames/sec - 227.233972 Mpixels/sec
204.642317 frames/sec - 228.380825 Mpixels/sec
^C
在X服务器host:0上渲染并在X服务器host:1上显示(假设主机和容器有相同的名字),用于远程显示。
Shell
container$ DISPLAY=:1 XAUTHORITY=/tmp/.Xauthority /opt/VirtualGL/bin/vglrun -d :0 /opt/VirtualGL/bin/glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
GLX FB config ID of window: 0xad (8/8/8/0)
Visual ID of window: 0x21
Context is Direct
OpenGL Renderer: GeForce GTX 1080/PCIe/SSE2
225.676333 frames/sec - 220.361204 Mpixels/sec
230.135755 frames/sec - 224.715597 Mpixels/sec
228.637145 frames/sec - 223.252283 Mpixels/sec
^C
总结
虽然X11是专门为远程显示而设计的,但基于非X11的查看器、容器隔离和OpenGL对渲染硬件的访问等问题使远程显示明显变得复杂。
本文解释了X11的工作原理和它的授权。它介绍了Docker容器对主机X服务器的访问以及从Docker容器内对主机NVIDIA硬件的访问。
本文介绍的方法并没有假设Docker容器与Linux主机紧密隔离。事实上,开放对X服务器的访问可能被认为是一种非常广泛的信任,因为X服务器在安全方面的滥用由来已久。肯定可以研究出更安全的基于硬件的OpenGL渲染的远程显示方式。
尽管如此,固有的信任和相对较低的X安全性并不妨碍它在医学成像或CAD应用中的广泛传播和使用,所以所提出的方法可以证明对许多组织是足够的。