Direct Rendering Manager (DRM)是LINUX内核的子系统,用来负责与GPU通信。用户程序可以通过DRM提供的API向GPU发送命令和数据,并且可以配置显示器的参数等操作。DRM 最初是作为 X Server 直接渲染框架的内核空间组件开发的,逐渐的也被其他图形显示框架所使用,如Wayland。
用户程序可以使用DRM API与GPU交互进行3D渲染硬件加速、视频解码和GPGPU计算等。
linux kernel最早是使用fbdev API来管理图形显示的framebuffer,但是不能满足基于GPU的现代3D硬件加速技术。这些设备通常需要在自己的内存中创建和管理command queue,以便将command分发给GPU进行渲染,并且还需要对内存进行管理如buffer和可用空间。最初,用户程序(如X Server)直接管理这些资源,但通常只有一个程序访问,当2个及以上程序同时访问相同硬件时,并以不同的方式设置每个硬件资源时大多数情况下会发生异常(图1)。
图1
DRM允许多程序同是访问硬件资源,通过创建单独的通道和GPU交互,并且初始化和管理command queue、memory和其他硬件资源。需要使用GPU的应用程序可以向DRM发送请求,DRM充当仲裁员并注意避免可能的冲突(图2)。
图2
经过多年的发展,DRM的范围扩展的很丰富了,涵盖了以前由用户空间程序处理的更多功能,如framebuffer管理、mode setting、memory共享、memory同步,其中一些扩展被赋予了特定名称,例如 Graphics Execution Manager (GEM) 、 kernel mode-setting (KMS),目前都已经是DRM子系统的一部分了。
计算机包含2个GPU的趋势:独显和集显,会出现一些新问题(如GPU切换)待DRM层解决,为了匹配Nvidia Optimus技术,DRM被提供GPU卸载功能,称为PRIME。
DRM是在kernel中实现的,应用程序必须通过系统调用来获取服务。DRM没有定义专属的系统调用,而是遵循Unix原则“everything is a file:一切都是文件”,通过文件系统使用/dev层次结构下的设备文件来使用GPU.DRM检测到GPU后会创建对应的设备文件如/dev/dri/cardX(其中X表示序号)。想要使用GPU的应用程序需要打开设备文件(如open("/dev/dri/card0"))并调用ioctl与DRM通信,不同的 ioctls参数对应于DRM API的不同函数, 应用程序可以调用libdrm库与DRM子系统通信,该库封装了很多C语言编写的DRM API函数、数据结构等。使用libdrm不仅避免了内核接口直接暴露给应用程序,而且还具有在程序之间重用和共享代码的优点(图3)。
图3
DRM包含2部分(图 4):
DRM Core:通用部分,提供了基础的框架。不同的DRM驱动程序可以在其中注册,并为应用程序提供了一组最小ioctl通用集合,独立于硬件。
DRM Driver: DRM驱动程序实现API的硬件相关部分,特定于它支持的GPU类型,提供了DRM core未涵盖的其余iotl的实现。当一个特定的DRM Driver提供了加强版的API时,应用程序也可以通过调用libdrm-driver库使用该驱动程序额外的ioctl接口。
图4
应用程序可以通过调用libdrm库来使用DRM core封装的函数接口,也可以用过ioctl和sysfs文件系统来访问特殊的设备接口,扩展接口包括:内存映射(memory mapping)、上下文管理(context management)、DMA操作集(DMA operations)、AGP管理(AGP management)、vblank控制(vblank control)、fence管理(fence management)和output(output management)。
出于安全和并发问题,DRM对部分ioctl操作进行了限制,只能由DRM-Master应用进程所调用。当应用进程第一次通过ioctl打开/dev/dri/cardx的时候设置SET_MASTER标志位,其他未设置该标志位的进程尝试执行限制型ioctl时会返回error。一个进程也可以通过调用DROP_MASTER ioctl 来放弃其主角色,让另一个进程获取它,常见的DRM-Master进程如X Server或者其他display server,当他们打开设备节点时会设置SET_MASTER标志位。
其他非DRM-Master进程可以通过DRM-Auth(DRM-Master批准以获取此类权限)来获取受限制的操作,过程如下:
由于视频内存的大小越来越大,并且 OpenGL 等图形 API 的复杂性日益增加,因此在每次上下文切换时重新初始化图形卡在性能方面消耗较大。此外,现代 Linux 桌面需要一种与合成管理器共享 off-screen buffers的最佳方式。
GEM 提供了具有显式内存管理的APIs,应用程序可以通过GEM进行create、handle、destory GPU视频内存中的objects(也称为GEM objects)。由于这些objects是常驻的,当进程重新获取gpu控制权时不需要重新reload objects。当用户进程需要大块视频内存时(当用户空间程序需要一大块视频内存(用于存储GPU所需的帧缓冲器,纹理或任何其他数据),会使用GEM API请求分配给DRM驱动程序。GEM API 还提供了填充缓冲区并在不再需要缓冲区时释放缓冲区的操作。
GEM 还允许使用同一DRM设备的两个或多个应用程序共享一个GEM object。GEM handles每个是32位整数,对进程局部来说每个是唯一的,但是不同进程之间的handle可能是相同的,因此不适合共享。GEM通过name提供了一个全局的命名空间,GEM driver会创建唯一的32整数来构建一个object对象,该object和name是关联的。GEM name可以通过flink从GEM handle中获取,然后通过IPC方式传递给其他的进程,其他进程接收到name后可以获取到包含object的GEM handle,从而实现了不同进程之间的共享。
通过name实现的共享并不安全,访问同一DRM设备的恶意第三方进程可以尝试猜测其他两个进程共享的缓冲区的GEM name,一旦获取到其内容就可以被访问和修改,黑客采用此方法来攻击系统。
上面讲述了内存管理的相关部分,除此之外另一个重要的部分是GPU和CPU之间的内存同步,当前的内存体系结构非常复杂,通常涉及系统内存的各种级别的缓存,有时也涉及视频内存的缓存级别,因此,内存管理器还应处理缓存一致性,以确保CPU和GPU之间共享的数据是一致的。这意味着内存管理内部通常高度依赖于GPU和内存架构的硬件细节。
GEM最初由Intel 工程师开发,为其i915驱动程序提供视频内存管理器。inter GMA 9xx系列是具有统一内存架构(UMA)的集成GPU,其中GPU和CPU共享物理内存并且没有专用的 VRAM。
inter还提供了用于控制执行流(命令缓冲区)的 ioctls GEM API,inter i915 和更高版本的GPU可以使用。其他DRM驱动程序没有尝试实现除了内存管理ioctls之外GEM API的任何部分。
TTM是GEM框架出现之前GPU通用的内存管理框架,它专门设计用于管理GPU可能访问的不同类型的内存,包括专用视频RAM(通常安装在视频卡中)和可通过IOMMU访问的系统内存。TTM 还可以最佳性能进行处理CPU无法直接寻址的视频RAM部分,因为用户空间图形应用程序通常处理大量视频数据。另一个重要作用是保持不同内存和缓存之间的一致性。
TTM管理的buffer objects主要是GPU寻址的video memory区域,当用户空间图形应用程序想要访问buffer object(如填充绘制的数据)时,TTM需要将其重新定位到CPU可寻址的内存类型。 当 GPU访问的buffer object不在其寻址范围内时,可能会发生重定位或 GART 映射操作。每次重定位操作中都必须处理相关的数据和缓存一致性问题。
TTM还有一个重要的概念fence,fence是管理 CPU 和 GPU 之间并发性的机制。当buffer object被GPU使用完后,会被fence检测到通知用户程序访问该buffer。
TTM尝试管理所有类型的memory,在内存管理方面提供了很多特性,导致了很多冗余复杂的API,相比TTM,GEM是一种更简单的内存管理框架。但是在驱动开放方面TTM更适用于专用VRAM和IOMMU的离散视频卡开发,可以将buffer object作为GEM object,这样就可以支持GEM API了,例如AMD显卡的radeon驱动和NVIDIA显卡的部分驱动程序中使用TTM作为局部内存管理器,并且提供了GEM API接口。
DMA-BUF是linux kernel提供的一种在多设备之间共享DMA buffer的通用机制,例如Video4Linux 和graphics adapter可以通过DMA-BUF共享缓冲区,以实现由前者生成并由后者使用的视频流的数据的零拷贝,任何驱动程序都可以作用消费者或者生产者实现该API。
在DRM中首次利用此功能来实现PRIME来完成独显和集显的DRM驱动程序中共享buffer。DMA-BUF的一个重要特性是共享buffer作为fd呈现给用户空间。DRM API中新增了2个ioctl操作:1、本地GEM handle转化成DMA-BUF文件描述符; 2、DMA-BUF文件描述符转化成GEM handle。
这两个新的 ioctl 可以修复 GEM name实现共享buff的安全问题(GEM章节),文件描述符无法通过暴力尝试的方法获取到,Unix操作系统提供了一种安全的方式,使用SCM_RIGHTS语义通过Unix域套接字传递它们。
video card或者graphics adapter在开始工作之前需要设置模式,比如屏幕分辨率、颜色和刷新率,该过程被称为Mode-Setting。通常需要对图形硬件进行访问,即写入video card寄存器的能力。Mode-Setting操作一般发生在framebuffer使用之前或者是mode发生了改变。
在早期,应用程序在使用图形buffer时也要提供mode-setting操作,因此需要有访问硬件的权限才可以。在 Unix 类型的操作系统中,X Server 是最典型的例子,其mode-setting 实现存在于每种特定类型的video card的DDX驱动程序中,该方式被称为User space Mode-Setting(UMS). UMS方式不仅会带来稳定性和安全问题,而且在多个应用程序并发进行Mode-Setting操作时,图形硬件可能会有不一致的问题。为了避免上述问题,X Server同一时刻只能为一个应用程序执行mode-setting操作,其余的应用程序只能依赖于X Server来设置mode-setting操作。最初的mode-setting操作是在X Server进程启动的时候执行的,后来在运行过程中也可以执行。
但是这并不是linux系统执行mode-setting的唯一代码。在系统启动过程中,linux kernel也必须为virtual console设置一个最小的文本模式(基于VESA BIOS扩展定义的标准模式)。并且linux kernel中framebuffer驱动也是用mode-setting配置device。为了避免冲突,XFree86 Server和最新的 X.Org Server是在用户从图形界面切换到console时先保存mode-setting状态,然后在切换回去的时候在读取出来,该过程可能出现闪烁现象。UMS还存在以下问题:
为了解决上述问题,mode-setting的实现被迁移到了kernel中,目前是在DRM模块中。然后包括X Server在内的所有进程需要通过kernel来执行mode-setting,并且kernel将保证并发状态下的一致性。通过Kernel API来执行mode-setting的过程被称为KMS。
KMS模式有几个好处,最直接的就是从kernel(Linux console、fbdev)和用户空间(X Server DDX 驱动程序)中删除重复的mode-setting代码。KMS 还使编写替代图形系统变得更加容易,这些系统现在不需要实现自己的mode-setting代码,KMS同样解决了console和X切换时的闪屏问题。由于KMS是在kernel中实现的,因此也可以在启动过程的使用,从而避免了早期阶段由于mode-change而导致的闪烁.
KMS在kernel实现也允许使用其他可用资源如interrupts(中断),例如当一个进程suspend/resume 状态恢复的时候,通过kernel就变得容易很多,并且提高了安全性(不再需要root权限的tools)。KMS还解决了一个长期存在的问题:display设备的热插拔。由于framebuffer是memory buffer,所以mode-setting也和内存管理紧密相关,可以和显存管理器集成在一起,所以被合并到了DRM中而不是作为单独的一个模块存在。
为了避免破坏 DRM API 的向后兼容性,KMS作为某些DRM驱动的附加特性对外使用。任何DRM 驱动程序都可以在向DRM core注册时设置DRIVER_MODESET标志来表示支持KMS API。那些实现KMS的驱动程序通常称为 KMS driver,以将它们与没有KMS的传统DRM 驱动程序区分开来。
KMS对显示控制器中的pipeline包含的硬件抽象层进行建模,包括:
CRTCs:显示控制器的扫描输出引擎,指向framebuffer。CRTC的作用是读取当前framebuffer中的像素数据,并在 PLL 电路的帮助下生成视频模式时序信号。CRTC的数量决定了硬件可以同时处理多少个输出设备,每个显示设备必须要有一个CRTC,多个CRTC可以在clone模式下从同一个framebuffer中读取数据发送给不同的设备。
Connectors:表示display controller将扫描输出的video信号发送到显示的地方,通常在KMS中connector对应于物理连接器(VGA, DVI, FPD-Link, HDMI, DisplayPort, S-Video),与当前连接物理输出设备相关的信息都保存在connector中,包括connection status, EDID data, DPMS status和 supported video modes。
Encoders:display controller必须将从CRTC中获取到的video时钟信号编码成适合connector传输的格式,encoders表示能进行编码的硬件模块。如数字信号TMDS和LVDS,对于 VGA 和 TV 输出等模拟信号,通常使用特定的 DAC 模块。connector同一时刻只能接收来自一个encoder的信号,并且每种类型的connector仅支持某些编码,有一些硬件的限制并不是每个CRTC都能连接到所有可用的encoder,从而限制了CRTC-encoder-connector的组合。
Planes: plane不是硬件模块,而是包含了从CRTC获取了buffer的内存对象。持有framebuffer的plane称为primary plane。每个CRTC必须有一个primary plane,因为它是crtc视频模式数据的来源,比如像素大小、像素格式、刷新率等。如果display controller支持硬件cursor overlays,则 CRTC 可能还具有与其关联的cursor plane.
近些年,一直为常规的KMS API能够具有原子性(atomic)努力,特别是mode-setting和page-fliping相关的操作。这种具有原子性的KMS API被称为Atomic Display。
atomic mode-setting的目的是避免设置失败或者状态不一致的中间步骤时,确保在具有多个限制的复杂配置中正确更改模式。当mode-setting设置失败必须回滚(rollback)时,原子性可以避免有风险的视频状态。通过提供mode testing能力,Atomic mode-setting允许预先知道某些特殊的mode配置是否合适,当测试atomic mode并且确认是有效时,可以通过atomic commit操作来设置。test和commit操作都由具有不同标志的同一个新 ioctl 提供。
另一方面,Atomic page-flip允许在一个 VBLANK 间隔内同步更新多个plane(如primary plane、cursor plane或者其他overlays plane),确保不出现撕裂现象(tearing),该特性在移动或嵌入式设备中使用较多,通过使用多个plane或者overlays来省电。
新的atomic API 建立在旧的 KMS API 之上,具有相同的mode object(CRTC、plane、encoder、connector),但是property变多了。atomic过程基于更改相关property来建立test和commit状态,property修改依赖于mode-setting还是page-flip,对于这2种情况,iotcl是一样的,不同之处在于他们所具有的properties不同。
在最初的DRM API中,DRM设备节点/dev/dri/cardX即可以用于特权操作(modesetting、其他display control),也可以用于非特权操作(渲染、GPGPU)。出于安全考虑,打开DRM设备需要root权限。可靠的用户空间的程序(如X SERVER、图形合成器...)可以完全访问DRM API,包括特权API(modeset API等)。其他非特权程序想要渲染或者使用GPGPU计算的,需要通过认证接口向DRM Master申请权限,然后,经过身份验证的程序可以使用受限版本的 DRM API 渲染或进行计算,而无需特权操作。这种设计施加了一个严格的限制:必须始终有一个正在运行的图形服务器(X server、Wayland合成器…)充当 DRM-Master,以便其他用户空间程序可以被授予使用设备,即使在不涉及任何图形显示(如 GPGPU 计算)的情况下,渲染节点(render node)可以将DRM API拆分成2类接口(特权和非特权),每类接口使用不同的设备或node来解决上述问题。除了master节点(/dev/dri/cardX),每个GPU对应的DRM驱动都会单独创建一个渲染节点(/dev/dri/renderDx)。要使用DRM框架的客户端和想利用GPU加速的应用都可以通过打开任意的渲染节点而无需申请特权来完成,前提只要有打开这些渲染接节点的权限。display servers、合成器和其他需要modeset API的或者一些需要特权的程序必须通过打开主节点(/dev/dri/cardX)来使用它。渲染节点禁止GEM Flink操作以防止使用不安全的 GEM 全局共享buffer,渲染节点明确禁止 GEM flink 操作,以防止使用不安全的 GEM 全局名称共享缓冲区;只有 PRIME (DMA-BUF) fd可用于与另一个客户端共享缓冲区,包括图形服务器。
linux DRM子系统包括开源和免费的驱动程序,支持3家主要的GPU制造商(AMD、NVIDIA 和Inter)以及越来越多的移动GPU和SOC集成商的硬件。每个驱动程序的质量差异很大,具体取决于制造商的合作程度和其他事项
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
出于历史目的,下表中还详细介绍了许多旧的过时硬件的驱动程序。其中一些仍然保留在内核代码中,但大多数已经被删除。
Historic DRM drivers
|
DRM在 Linux kernel中开发的,源码路径在/drivers/gpu/drm目录中。该模块的维护者是Dave Airlie,其他维护者负责特定的驱动程序。在Linux kernel开发中,DRM子维护者和贡献者将他们新特性和bug修复的patch发送到主要的DRM维护者,后者将它们集成到自己的Linux仓中。反过来,DRM维护者将所有这些patch提交到Linus Torvalds,每当新的Linux版本发布时,这些patch都可以被合入主线。Torvalds作为整个kernel的顶级维护者,对补丁是否适合包含在内核中拥有最后的决定权。
由于历史原因,libdrm库的源代码在Mesa项目下进行维护。
1999 年,在为 XFree86 开发 DRI 时,Precision Insight 为 3dfx video cards创建了第一个版本的 DRM,作为linux kernel的patch,被包含在mesa源码中。当年晚些时候,DRM代码被合入到了linux kernel 2.3.18主线,路径在/drivers/char/drm/ 目录下作为字符设备。在接下来的几年中,支持的video cards数量增加了。当 Linux 2.4.0 于 2001 年 1 月发布时,除了 3dfx Voodoo3 卡 之外,已经支持 Creative Labs GMX 2000、Intel i810、Matrox G200/G400 和 ATI Rage 128, 并且该列表在2.4.x 系列期间有所扩展。带有适用于 ATI Radeon 卡、一些 SiS 显卡和 Intel 830M 以及后续集成 GPU 的驱动程序。
2004年下半年,DRM 拆分为两个组件:DRM core和 DRM driver,并合并到内核版本 2.6.11。这种拆分允许多个设备的多个 DRM 驱动程序同时工作,从而为多 GPU 支持开辟了道路。
将所有video mode-setting代码放在kernel中想法已被公认多年了,但显卡制造商争辩说,进行mode-setting的唯一方法是使用他们自己提供并包含在每个显卡的video BIOS 中的例程。此类代码必须使用 x86 实模式执行,这会阻止它被运行在受保护模式下的kernel调用。 当 Luc Verhaegen 和其他开发人员找到一种方法来进行本机mode-setting而不是基于 BIOS 时,这个问题才得以解决,可以使用正常的kernel代码来完成,并为未来的发展奠定了基础,成为KMS。 2007 年 5 月,Jesse Barnes(inter)发布了第一个关于 drm-modesetting API 的提案,以及 i915 DRM 驱动程序中inter GPU 的mode-setting本机实现。2007 年 12 月,Jerome Glisse 开始将 ATI 卡的本机mode-setting代码添加到 radeon DRM 驱动程序中。API 和驱动程序的开发工作在 2008 年继续进行,但由于kernel中也需要内存管理器来处理帧缓冲区而被推迟。