剖析虚幻渲染体系(16)- 图形驱动的秘密

本文深入探讨了图形驱动程序的原理和应用,涵盖了设备驱动的基础概念,详细介绍了图形驱动的架构,包括硬件概览、操作系统图形驱动(如Windows的WDDM和Linux的X11、DRM等)以及GPU驱动。讨论了驱动程序在操作系统和硬件设备之间的接口,以及在图形硬件如NVIDIA、AMD和Intel等公司的不同实现。此外,文章还涉及了图形驱动在视频处理、游戏引擎(如UE)和I/O驱动等方面的应用,揭示了驱动程序对稳定性和性能的重要性。
摘要由CSDN通过智能技术生成

🚀 优质资源分享 🚀

学习路线指引(点击解锁) 知识定位 人群定位
🧡 Python实战微信订餐小程序 🧡 进阶级 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
💛Python量化交易实战💛 入门级 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

目录* 16.1 本篇概述
+ 16.1.1 本篇内容
+ 16.1.2 设备驱动概述
+ 16.1.3 图形驱动概述

16.1 本篇概述

16.1.1 本篇内容

迄今为止,博主在博客中阐述的内容包含图形API、GPU、游戏引擎、Shader、渲染技术、性能优化等等技术范畴内容,但似乎还未涉及图形驱动的内幕。本篇将站在应用层开发者的视角,去阐述图形驱动的相关技术内幕(如果是驱动开发者,则博主不认为是目标读者),主要包含但不限于以下内容:

  • 图形驱动的架构。
  • 图形驱动的技术内幕。
  • 图形驱动的常见实现。
  • 相关的硬件基础。

16.1.2 设备驱动概述

要给“驱动”一词下一个准确的定义是一个挑战。从最基本的意义上讲,驱动程序是一个软件组件,它允许操作系统和设备相互通信。例如,假设应用程序需要从设备读取一些数据,应用程序调用操作系统实现的函数,操作系统调用驱动程序实现的函数。该驱动程序由设计和制造该设备的同一家公司编写,他们知道如何与设备硬件通信以获取数据。驱动程序从设备获取数据后,将数据返回给操作系统,操作系统将数据返回给应用程序。

在计算机中,设备驱动程序是一种计算机程序,用于操作或控制连接到计算机或自动机的特定类型的设备。驱动程序为硬件设备提供软件接口,使操作系统和其他计算机程序能够访问硬件功能,而无需知道所使用硬件的确切细节。

驱动程序通过硬件连接的计算机总线或通信子系统与设备通信。当调用程序调用驱动程序中的例程时,驱动程序向设备发出命令(驱动设备)。一旦设备将数据发送回驱动程序,驱动程序就可以调用原始调用程序中的例程。驱动程序依赖于硬件且特定于操作系统,通常提供任何必要的异步时间相关硬件接口所需的中断处理。

设备驱动程序,特别是在现代Microsoft Windows平台上,可以在内核模式(x86 CPU上的ring 0)或用户模式(x86 CPU上的ring 3)下运行。在用户模式下运行驱动程序的主要好处是提高了稳定性,因为写得不好的用户模式设备驱动程序不会通过覆盖内核内存而导致系统崩溃。另一方面,用户/内核模式转换通常会带来相当大的性能开销,从而使内核模式驱动程序成为低延迟网络的首选。

用户模块只能通过使用系统调用来访问内核空间,最终用户程序(如UNIX shell或其他基于GUI的应用程序)是用户空间的一部分,这些应用程序通过内核支持的函数与硬件交互。

常见的设备驱动包含但不限于:

  • 打印机。
  • 视频适配器。
  • 网卡。
  • 声卡。
  • 各种类型的本地总线,特别是用于在现代系统上控制总线。
  • 各种低带宽输入/输出总线(用于鼠标、键盘等定点设备)。
  • 计算机存储设备,如硬盘、CD-ROM和软盘总线(ATA、SATA、SCSI、SAS)。
  • 实现对不同文件系统的支持。
  • 图像扫描仪。
  • 数码相机。
  • 数字地面电视调谐器。
  • 用于无线个人局域网的射频通信收发器适配器,用于家庭自动化中的短距离低速无线通信(例如蓝牙低能量(BLE)、线程、ZigBee和Z-Wave)。
  • IrDA适配器。

以上的解释在以下几个方面过于简单:

  • 并非所有驱动程序都必须由设计该设备的公司编写。在许多情况下,设备是根据已发布的硬件标准设计的,意味着驱动程序可以由Microsoft编写,并且设备设计器不必提供驱动程序。
  • 并非所有驱动程序都与设备直接通信。对于给定的I/O请求(如从设备读取数据),通常有多个驱动程序参与请求,这些驱动程序分层在驱动程序堆栈中。可视化堆栈的传统方法是,第一个参与者在顶部,最后一个参与者在底部,如下图所示。堆栈中的一些驱动程序可能通过将请求从一种格式转换为另一种格式来参与。这些驱动程序不直接与设备通信,他们只是操纵请求并将请求传递给堆栈中较低的驱动程序。

其中功能驱动程序是堆栈中直接与设备通信的一个驱动程序,过滤驱动程序是执行辅助处理的驱动程序。

  • 一些过滤驱动程序观察并记录有关输入/输出请求的信息,但不主动参与这些请求。例如,某些过滤驱动程序充当验证器,以确保堆栈中的其他驱动程序正确处理I/O请求。

可以通过说驱动程序是观察或参与操作系统和设备之间通信的任何软件组件来扩展驱动程序的定义。

至此,扩展定义相当准确,但仍然不完整,因为一些驱动程序根本与任何硬件设备都没有关联。例如,假设需要编写一个能够访问核心操作系统数据结构的工具,而只有在内核模式下运行的代码才能访问核心操作系统数据结构。可以通过将该工具拆分为两个组件来实现这一点,第一个组件以用户模式运行并显示用户界面,第二个组件以内核模式运行,可以访问核心操作系统数据。在用户模式下运行的组件称为应用程序,在内核模式下运行的组件称为软件驱动程序,软件驱动程序与硬件设备不关联。下图说明了与内核模式软件驱动程序通信的用户模式应用程序。

软件驱动程序总是在内核模式下运行,编写软件驱动程序的主要原因是为了访问仅在内核模式下可用的受保护数据。然而,设备驱动程序并不总是需要访问内核模式的数据和资源。因此,一些设备驱动程序以用户模式运行。

此外,还有总线驱动等更多功能性驱动(下图)。

16.1.3 图形驱动概述

显示驱动程序是允许操作系统与图形硬件一起工作的软件。图形硬件控制显示器,可以是计算机中的扩充卡,也可以内置在计算机的主电路板中(如笔记本电脑),也可以驻留在计算机外部(如Matrox remote graphics units)。每种型号的图形硬件都是不同的,需要一个显示驱动程序来与系统的其余部分连接。具有不同特性的较新图形硬件型号不断发布,每种新型号的控制方式往往不同。

驱动程序将操作系统函数调用(命令)转换为特定于该设备的调用。对于同一型号的图形硬件,使用不同函数调用的每个操作系统也需要不同的显示驱动程序。例如,Windows XP和Linux需要非常不同的显示驱动程序。但是,同一操作系统的不同版本有时可以使用相同的显示驱动程序。例如,Windows 2000和Windows XP的显示驱动程序通常是相同的。

如果未安装特定于计算机图形硬件的显示驱动程序,图形硬件将无法使用或功能有限。如果特定于型号的显示驱动程序不可用,操作系统通常可以使用具有基本功能的通用显示驱动程序。例如,Windows在“安全模式”下使用通用VGA或SVGA显示驱动程序。在这种情况下,大多数特定于模型的功能都不可用。

由于图形硬件非常复杂,并且显示驱动程序非常特定于该硬件,因此显示驱动程序通常由硬件制造商创建和维护,甚至操作系统中包含的显示驱动程序也通常最初由制造商提供。制造商可以完全访问有关硬件的信息,并在确保以最佳方式使用其硬件方面拥有既得利益。

显示驱动程序对系统资源具有低级(内核级)访问权限,因为显示驱动程序需要直接与图形硬件通信,这种低级别访问使得显示驱动程序的编码更加仔细和可靠。显示驱动程序中的错误比应用程序软件中的错误更有可能使整个操作系统暂时无法使用。

幸运的是,某些公司或组织(如Matrox)凭借其对专业用户的传统承诺以及其产品的长期产品生命周期,在制造可靠的显示器驱动程序方面享有盛誉。长生命周期意味着其显示器驱动程序的开发将持续更长的时间,使得悬而未决的问题更有可能得到解决,并且显示驱动程序能够适应不断变化的软件环境。新的操作系统和新的应用软件正在不断发布,每一种都可能需要新的驱动程序版本来保持兼容性或提供新的功能,可用的最新显示驱动程序经常解决此类问题。长的产品生命周期也使得显示驱动程序更有可能添加新的特性和功能,而不考虑操作系统和应用程序软件。

对于Linux等开源操作系统,非制造商有时会维护显示驱动程序,操作系统的开源特性使得为此类操作系统编写代码变得更容易(但并不容易)。虽然Matrox为特殊目的维护自己的Linux显示驱动程序,但Matrox Millennium G系列产品提供了基本的开源Linux显示驱动程序,Matrox合作伙伴Xi Graphics(一家在Linux/Unix开发方面有10多年经验的公司)为Matrox产品提供了全功能的Linux显示驱动程序。

即使对于相同的操作系统和图形硬件型号,有时也会同时提供不同的显示驱动程序,以满足不同的需求。以下是Matrox图形硬件某些型号可用的不同显示驱动程序的摘要:

  • “HF”驱动程序:此类驱动程序具有丰富的界面,需要Microsoft .NET Framework软件,适用于喜欢此界面或者已有此界面的用户,微软NET软件包含在许多最近安装的Windows和许多其他需要它的应用程序中。“HF”软件仅适用于某些型号的Parhelia系列产品和Millennium P系列产品。
  • Microsoft .NET Framework:是由Microsoft创建的一种编程基础架构,用于构建、部署和运行用户使用的应用程序和服务.NET技术,如我们的HF驱动程序。
  • 统一驱动程序:此类驱动程序一次支持多个不同型号的Matrox产品。对于需要一次为许多不同的Matrox产品安装显示驱动程序并希望从一个软件包进行安装的系统管理员非常有用,对于不确定自己的图形硬件型号的用户也很有用。由于统一驱动程序的接口必须支持不同的硬件,Matrox统一驱动程序使用支持更广泛的“SE”接口。
  • XDDM:Windows XP显示驱动程序模型,有时被称为XPDM或XPDDM。
  • WDDM:Windows显示驱动程序模型(WDDM)是Windows Vista、Server 2008和Windows 7支持的显示驱动程序体系结构。
  • “WDM”(Windows驱动程序型号)驱动程序包:此类驱动程序适用于需要视频捕获和实时播放功能的用户,有一个需要Microsoft的丰富界面.NET Framework软件和支持视频的额外功能,微软NET软件包含在许多最近安装的Windows和许多其他需要它的应用程序中。仅适用于某些型号的Parhelia系列产品。
  • WHQL驱动程序:“Windows硬件质量实验室”驱动程序接受Microsoft开发的一系列标准测试,以提高驱动程序的可靠性。Matrox等硬件供应商执行这些测试,并将结果提交给Microsoft进行认证。如果用户安装的驱动程序未通过WHQL认证(即使驱动程序通过其他方式认证),则Windows操作系统的最新版本会向用户发出警告。为了避免此类警告和WHQL流程提供的额外测试,系统管理员通常更喜欢使用WHQL驱动程序。
  • 认证驱动程序:Matrox通过领先的专业2D/3D软件认证显示驱动程序,包括用于AEC、MCAD、GIS和P&P(工厂和工艺设计)的软件,这种软件通常要求很高,并广泛使用图形硬件加速。Matrox单独认证此类应用程序,以确保额外的可靠性。该测试是对所有Matrox显示器驱动程序进行的常规测试的补充。
  • ISV认证驱动程序:某些“独立软件供应商”对其应用软件有自己的认证流程,与Matrox认证的驱动程序相似,因为特定图形硬件的显示驱动程序会在特定应用程序中进行额外测试。但是,在这种情况下,Matrox将硬件和显示驱动程序提交给ISV,ISV执行测试。与Matrox认证相比,ISV认证的频率较低,涵盖的应用较少。
  • 测试版驱动程序:有时,特殊支持或修复程序首先作为“测试版”驱动程序发布。这类驱动已经接受了一些测试,但不一定完成了整个测试周期,测试版驱动程序可供想要测试或预览重要新功能的用户使用。

设备驱动程序是操作系统内核代码的最大贡献者,Linux内核中有超过500万行代码,并且会导致严重的复杂性、bug和开发成本。近年来,出现了一系列旨在提高可靠性和简化驱动开发的研究。然而,除了用于研究的一小部分驱动程序之外,人们对这一庞大的代码体的构成知之甚少。

有学者研究Linux驱动程序的源代码,以了解驱动程序的实际用途、当前的研究如何应用于这些驱动程序以及未来的研究机会,大体上,研究着眼于驱动程序代码的三个方面:

  • 驱动程序代码功能的特征是什么,驱动程序研究如何适用于所有驱动程序。
  • 驱动程序如何与内核、设备和总线交互。
  • 是否存在可抽象为库以减少驱动程序大小和复杂性的相似性?

从驱动程序交互研究中,发现USB总线提供了一个高效的总线接口,具有重要的标准化代码和粗粒度访问,非常适合隔离执行驱动程序。此外,不同总线和级别的驱动程序的设备交互水平差异很大,这表明隔离成本将因级别而异。

设备驱动程序是在操作系统和硬件设备之间提供接口的软件组件,驱动程序配置和管理设备,并将来自内核的请求转换为对硬件的请求。驱动程序依赖于三个接口:

  • 驱动程序和内核之间的接口,用于通信请求和访问操作系统服务。
  • 驱动器和设备之间的接口,用于执行操作。
  • 驱动器和总线之间的接口,用于管理与设备的通信。

下图显示了Linux中驱动程序根据其接口的层次结构,从基本驱动程序类型开始,即char、block和net,可确定72个独特的驱动程序类别。大多数(52%)驱动程序代码是字符驱动程序,分布在41个类中。网络驱动程序占驱动程序代码的25%,但只有6个类。例如,视频和GPU驱动程序对驱动程序代码的贡献很大(近9%),这是因为复杂的设备具有每代都会改变的指令集,但这些设备由于其复杂性,在很大程度上被驱动程序研究所忽视。

Linux驱动程序在基本驱动程序类方面的分类。其中提到了5个最大类的大小(以代码行的百分比表示)。

通常认为设备驱动程序主要执行输入/输出。标准本科操作系统教科书规定:设备驱动程序可以看作是转换器,其输入由高级命令组成,如“检索块123”其输出由硬件控制器使用的低级别、特定于硬件的指令组成,硬件控制器将输入/输出设备连接到系统的其余部分。

随着设备功能越来越强大,并拥有自己的处理器,人们通常认为驱动程序执行的处理很少,只是在操作系统和设备之间传输数据。然而,如果驱动程序需要大量的CPU处理,例如计算RAID奇偶校验、网络校验和或视频驱动程序显示数据,则必须保留处理能力。15%的驱动程序至少有一个执行处理的函数,而处理发生在所有驱动程序函数中的1%。

下表比较了PCI、USB和XenBus所有设备类别的复杂性指标。通过比较一个驱动程序支持的芯片组数量来考察支持多个设备的效率,表明了支持新设备的复杂性,以及驱动程序的抽象级别。支持来自不同供应商的许多芯片组的驱动程序表示具有高水平通用功能的标准化接口。相比之下,支持单个芯片组的驱动程序效率较低,因为每个设备都需要一个单独的驱动程序。

三种总线(bus)的驱动效率差别很大。PCI驱动程序支持每个驱动程序7.5个芯片组,几乎总是来自同一供应商。相比之下,USB驱动程序的平均值为13.2,通常来自许多供应商。其中很大一部分差异在于USB协议的标准化,而许多PCI设备都不存在这种标准化。例如,USB存储设备实现标准接口。因此,主USB存储驱动程序代码在很大程度上很常见,但包括对设备特定代码的调用。此代码包括特定于设备的初始化、挂起/恢复(未提供通过USB存储并作为附加功能要求保留)和其他需要设备特定代码的例程。虽然USB驱动程序有了更大的标准化工作,但仍不完整。

所有现代操作系统中驱动程序的另一个关键要求是需要多路访问设备。例如,一个磁盘控制器驱动程序必须允许多个应用程序同时读写数据,即使这些应用程序没有其他关联。这一要求会使驱动程序设计复杂化,因为它增加了对多个独立线程之间同步的需要。研究驱动程序如何跨长延迟操作多路访问:它们倾向于线程化代码、在堆栈上保存状态并阻止事件,还是倾向于事件驱动代码、将回调注册为USB驱动程序的完成例程或PCI设备的中断处理程序和计时器。如果驱动程序被移动到内核之外,驱动程序和内核将使用通信通道相互通信,支持事件驱动并发可能更自然。

下图中标记为事件友好型和线程化的条形图中显示的结果表明,线程化和事件友好型代码的划分在驱动程序类中差异很大。总的来说,驱动程序出于不同的目的广泛使用这两种同步方法。驱动程序使用线程原语来同步驱动程序和设备操作,同时初始化驱动程序并更新驱动程序全局数据结构,而事件友好型代码用于核心I/O请求。

线程化和事件友好型同步原语覆盖范围内的驱动程序入口点的百分比。

随着GPU性能的提高,对图形驱动程序的要求也越来越高。良好的用户体验取决于稳定、可靠的软件栈。

16.2 图形驱动基础

16.2.1 硬件概览

稍早期(如2012年)的计算机硬件架构大多可抽象成如下摸样:

其中一款显卡的结构见下图,包含了GPU(执行所有计算)、视频输出(连接到屏幕)、显存(存储纹理或通用数据)、电源管理(降低电压,调节电流)、主机交互总线(与CPU的通信)等部件:

如今,所有计算机的结构都是类似的:一个中央处理器和许多外围设备。为了交换数据,这些外围设备通过总线互连,所有通信都通过总线进行。下图概述了标准计算机中外围设备的布局。

典型计算机中的外围互连。

总线的第一个用户是CPU。CPU使用总线访问系统内存和其他外围设备。然而,CPU并不是唯一能够向外围设备写入和读取数据的设备,外围设备本身也具有直接交换信息的能力。具体地说,能够在没有CPU干预的情况下读取和写入存储器的外围设备被称为具有**DMA(直接存储器访问)**能力,并且存储器事务通常被称为DMA。这种类型的事务很有趣,因为它允许驱动程序使用GPU而不是CPU来进行内存传输。由于CPU不再需要主动工作来实现这些传输,并且由于它允许CPU和GPU之间更好的异步性,因此可以获得更好的性能。**DMA的常见用途包括提高纹理上传或流视频的性能。**如今,所有图形处理器都具有这种能力(称为DMA总线主控),这种能力包括视频卡请求并随后控制总线几微秒。

如果外设能够在不连续的内存页列表中实现DMA(当数据在内存中不连续时非常方便),则称其具有DMA分散-收集(scatter-gather)功能(因为它可以将数据分散到不同的内存页,或从不同的内存页收集数据)。

**请注意,DMA功能在某些情况下可能是一个缺点。**例如,在实时系统上,意味着当DMA事务正在进行时,CPU无法访问总线,并且由于DMA事务是异步发生的,可能导致错过实时调度截止时间。另一个例子是小型DMA内存传输,其中设置DMA的CPU开销大于异步增益,导致传输速度变慢。因此,虽然DMA从性能角度来看有很多优势,但在某些情况下应该避免。

另外,GPU需要主机:

  • 设置屏幕模式/分辨率(模式设置)。
  • 配置引擎和通信总线。
  • 处理电源管理。热量管理(风扇,对过热/功率作出反应),更改GPU的频率/电压以节省电源。
  • 处理数据。分配处理上下文(GPU VM+上下文ID),上传纹理或场景数据,发送要在上下文中执行的命令。

16.2.2 总线类型

总线将机器外围设备连接在一起,不同外设之间的每一次通信都通过(至少)一条总线进行。特别是,总线是大多数图形卡连接到计算机其余部分的方式(一个显著的例外是某些嵌入式系统,其中GPU直接连接到CPU)。如下表所示,有许多适用于图形的总线类型:PCI、AGP、PCI-X、PCI express等等。本小节将详细介绍的所有总线类型都是PCI总线类型的变体,但其中一些总线在原始PCI设计上有独特的改进。

  • PCI (Peripheral Component Interconnect,外设部件互连标准):PCI是目前允许连接图形外围设备的最基本的总线。它的一个关键特性叫做总线控制,此功能允许给定的外围设备在给定的周期数内占用总线并执行完整的事务(称为DMA,直接内存访问)。PCI总线是一致的,意味着无需显式刷新即可使内存在设备间保持一致。

  • AGP (Accelerated Graphics Port,图形加速端口):AGP本质上是一种经过改进的PCI总线,与它的祖先相比,具有许多额外的功能。最重要的是,它的速度更快,主要得益于更高的时钟速度以及在每个时钟tick中每个通道发送2、4或8位的能力(分别适用于AGP 2x、4x和8x)。AGP还有三个显著特点:

    • 第1个特性是AGP GART(图形光圈重映射表),是IOMMU的一种简单形式。它允许从系统内存中取出一组(非连续的)物理内存页,并将其暴露给GPU作为连续区域使用,以很低的成本增加了GPU可用的内存量,并为CPU和GPU之间共享数据创建了一个方便的区域(AGP图形卡可以在该区域进行快速DMA,并且由于GART区域是一块系统RAM,因此CPU访问比VRAM快得多)。一个显著的缺点是,GART区域不一致,因此在另一方开始传输之前,需要刷新对GART的写入(无论是从GPU还是CPU)。另一个缺点是,硬件只处理一个GART区域,它必须由驱动程序分配。
    • 第2个特性是AGP边带寻址(SBA)。边带寻址由用作地址总线的8个额外总线位组成,与多路复用地址和数据之间的总线带宽不同,标准AGP带宽只能用于数据。此功能对驱动程序开发人员是透明的。
    • 第3个特性是AGP快速写入(FW)。快速写入允许直接向图形卡发送数据,而无需图形卡启动DMA。此功能对驱动程序开发人员也是透明的。注意,后两个功能在各种硬件上都不稳定,通常需要特定于芯片组的hack才能正常工作,因此建议不启用它们。事实上,它们是AGP卡上出现奇怪硬件错误的极为常见的原因。
  • PCI-X:PCI-X是为服务器板开发的一种更快的PCI,这种格式的图形外围设备很少(一些Matrox G550卡)。不要将其与PCI-Express混淆,后者的使用非常广泛。

  • PCI-Express (PCI-E):PCI Express是新一代PCI设备,比简单的改进PCI有更多的优点。最后,需要注意的是,根据体系结构,CPU-GPU通信并不总是依赖于总线,在GPU和CPU位于单个芯片上的嵌入式系统上尤其常见。在这种情况下,CPU可以直接访问GPU寄存器。

16.2.3 显存架构

虽然DRAM通常被视为一个扁平的字节数组,但其内部结构要复杂得多。对于像GPU这样的高性能应用程序,非常有必要深入地理解它。从下往上大致看,VRAM由以下部分组成:

  • R行乘以C列的内存平面(memory plane),每个单元为一位。

  • 由32、64或128个并行使用的内存平面组成的内存组(memory bank)——这些平面通常分布在多个芯片上,其中一个芯片包含16或32个内存平面。bank中的所有页面都连接到行寻址系统(列也是如此),并且这些页面由命令信号和每行/列的地址控制。bank中的行和列越多,地址中需要使用的位就越多。

  • 由若干个[2、4或8]个memory bank连接在一起并由地址位选择的内存排(memory rank)——给定内存平面的所有memory bank位于同一芯片中。
  • 由一个或两个连接在一起并由芯片选择线选择的memory rank组成的内存子分区(memory subpartition)——rank的行为类似于bank,但不必具有统一的几何结构,而是在单独的芯片中。
  • 由一个或两个稍微独立的memory subpartition组成了内存分区(memory partition)
  • 整个VRAM由几个[1-8]个memory partition组成。

以上数量会因不同的GPU架构和家族而不同。

DRAM最基本的单元是内存平面,它是按所谓的列和行组织的二维位数组:

     column
row  0  1  2  3  4  5  6  7
0    X  X  X  X  X  X  X  X
1    X  X  X  X  X  X  X  X
2    X  X  X  X  X  X  X  X
3    X  X  X  X  X  X  X  X
4    X  X  X  X  X  X  X  X
5    X  X  X  X  X  X  X  X
6    X  X  X  X  X  X  X  X
7    X  X  X  X  X  X  X  X

buf  X  X  X  X  X  X  X  X

内存平面包含一个缓冲区,该缓冲区可容纳整个行。在内部,DRAM通过缓冲区以行为单位进行读/写。因此有几个后果:

  • 在对某个位进行操作之前,必须将其行加载到缓冲区中,会很慢。
  • 处理完一行后,需要将其写回内存数组,也很慢。
  • 因此,访问新行的速度很慢,如果已经有一个活动行,访问速度甚至更慢。
  • 在一段不活动时间后,抢先关闭一行通常很有用——这种操作称为precharging(预充电?)一个bank。
  • 但是,可以快速访问同一行中的不同列。

由于加载列地址本身比实际访问活动缓冲区中的位花费更多的时间,所以DRAM是以突发方式访问的,即对活动行中1-8个相邻位的一系列访问。通常,突发中的所有位都必须位于单个对齐的8位组中。内存平面中的行和列的数量始终是2的幂,并通过行选择和列选择位的计数来衡量[即行/列计数的log2],通常有8-10列位和10-14行位。内存平面被组织在bank中,bank由两个内存平面的幂组成。内存平面是并行连接的,共享地址和控制线,只有数据/数据启用线是分开的。这有效地使内存bank类似于由32位/64位/128位内存单元组成的内存平面,而不是单个位——适用于平面的所有规则仍然适用于bank,但操作的单元比位大。单个存储芯片通常包含16或32个存储平面,用于单个bank,因此多个芯片通常连接在一起以形成更宽的bank。

一个内存芯片包含多个[2、4或8]个bank,使用相同的数据线,并通过bank选择线进行多路复用。虽然在bank之间切换比在一行中的列之间切换要慢一些,但要比在同一bank中的行之间切换快得多。因此,一个内存bank由(MEMORY_CELL_SIZE / MEMORY_CELL_SIZE_PER_CHIP)存储器芯片组成。一个或两个通过公共线(包括数据)连接的内存列,芯片选择线除外,构成内存子分区。在rank之间切换与在bank中的列组之间切换具有基本相同的性能后果,唯一的区别是物理实现和为每个rank使用不同数量行选择位的可能性(尽管列计数和列计数必须匹配)。存在多个bank/rank的后果:

  • 确保一起访问的数据要么属于同一行,要么属于不同的bank,这一点很重要(以避免行切换)。
  • 分块内存布局的设计使分块大致对应于一行,相邻的分块从不共享一个bank。

内存子分区在GPU上有自己的DRAM控制器。1或2个子分区构成一个内存分区,它是一个相当独立的实体,具有自己的内存访问队列、自己的ZROP和CROP单元,以及更高版本卡上的二级缓存。所有内存分区与crossbar逻辑一起构成了GPU的整个VRAM逻辑,分区中的所有子分区必须进行相同的配置,GPU中的分区通常配置相同,但在较新的卡上则不是必需的。子分区/分区存在的后果:

  • 与bank一样,可以使用不同的分区来避免相关数据的行冲突。
  • 与bank不同,如果(子)分区没有得到同等利用,带宽就会受到影响。因此,负载平衡非常重要。

虽然内存寻址高度依赖于GPU系列,但这里概述了基本方法。内存地址的位按顺序分配给:

  • 识别内存单元中的字节,因为无论如何都必须访问整个单元。
  • 多个列选择位,以允许突发(burst)。
  • 分区/子分区选择-以低位进行,以确保良好的负载平衡,但不能太低,以便在单个分区中保留相对较大的tile,以利于ROP。
  • 剩余列选择位。
  • 所有/大部分bank选择位,有时是排名选择位,以便相邻地址不会导致行冲突。
  • 行位。
  • 剩余的bank位或rank位,有效地允许将VRAM拆分为两个区域,在其中一个区域放置颜色缓冲区,在另一个区域放置zeta缓冲区,这样它们之间就不会有行冲突。

此外,可以不同倍数的数据速率同步动态随机存取内存,正是我们所熟知的DDR-SDRAM或DDR:

单ank和双rank对比。

单速率、双速率、四速率对比图。

下图是CPU和GPU内存请求路线:

GTT/GART作为CPU-GPU共享缓冲区用于通信:

16.2.4 虚拟和物理内存

内存有两种主要的不同含义:

  • 物理内存。物理内存是真实的硬件内存,存储在内存芯片中。
  • 虚拟内存。虚拟内存是物理内存地址的转换,允许用户空间应用程序查看其分配的块,就好像它们是连续的,而它们在芯片上是碎片化和分散的。

虚拟地址空间的一些关键功能,以及虚拟内存和物理内存的关系。

一些操作系统(如Windows)还存在**分页缓冲池(paged pool)非分页缓冲池(nonpaged pool)**的机制。在用户空间中,所有物理内存页面都可以根据需要调出到磁盘文件。 在系统空间中,某些物理页面可以调出,而其他物理页面则不能。 系统空间具有用于动态分配内存的两个区域:分页缓冲池和非分页缓冲池。分页缓冲池中分配的内存可以根据需要调出到磁盘文件,非分页缓冲池中分配的内存永远无法调出到磁盘文件。

为了简化编程,更容易处理连续的内存区域。分配一个小的连续区域很容易,但分配一个更大的内存块将需要同样多的连续物理内存,在启动后由于内存碎片化导致难以实现。因此,需要一种机制来保持应用程序的连续内存块外观,同时使用分散的内存块。

为了实现这一点,内存被拆分为多个页。就本文的范围而言,可以说内存页是物理内存中连续字节的集合,以便使分散的物理页列表在虚拟空间中看起来是连续的,一个称为**MMU(内存映射单元)**的硬件使用页表将虚拟地址(用于应用程序)转换为物理地址(用于实际访问内存),如下图所示。如果页面不存在于虚拟空间中(因此不在MMU表中),MMU可以向其发送信号,为报告对不存在内存区域的访问提供了基本机制。反过来又被系统用来实现高级内存编程,如交换或动态页面实例化。由于MMU仅对CPU访问内存有效,虚拟地址与硬件无关,因为无法将它们与物理地址匹配。

MMU和IOMMU。

虽然MMU只适用于CPU访问,但它对外围设备有一个等价物:IOMMU。如上图所示,IOMMU与MMU相同,只是它虚拟化了外围设备的地址空间。IOMMU可以在主板芯片组(在这种情况下,它在所有外围设备之间共享)或图形卡本身(在图形卡本身上,它将被称为AGP GART、PCI GART)上看到各种化身。IOMMU的工作是将外围设备的内存地址转换为物理地址。特别是,它允许“欺骗”设备将其DMA限制在给定的内存范围内,是更好的安全性和硬件虚拟化所必需的。

IOMMU的一个特例是Linux swiotlb,它在引导时分配一段连续的物理内存(使得有一个大的连续物理分配是可行的,因为还没有碎片),并将其用于DMA。由于内存在物理上是连续的,不需要页转换,因此可以在该内存范围内进行DMA。但是,意味着该内存(默认为64MB)是预先分配的,不会用于其他任何用途。

AGP GART是IOMMU的另一个特例,它与AGP图形卡一起使用,向图形卡显示一个线性区域。在这种情况下,IOMMU被嵌入主板上的AGP芯片组中。AGP GART区域作为虚拟内存的线性区域向系统公开。

IOMMU的另一个特例是某些GPU上的PCI GART,它允许向卡公开一块系统内存。在这种情况下,IOMMU表嵌入到图形卡中,通常使用的物理内存不需要是连续的。

显然,有这么多不同的内存类型,性能是不均匀的,并非所有的访问组合都是快速的,主要取决于它们是否涉及CPU、GPU或总线传输。另一个问题是内存一致性:如何确保跨设备的内存一致,尤其是CPU写入的数据可供GPU使用(或相反)。这两个问题是相关的,因为较高的性能通常意味着较低水平的内存连贯性,反之亦然。

就设置内存缓存参数而言,有两种方法可以在内存范围上设置缓存属性:

  • MTRR。MTRR(内存类型范围寄存器)是描述给定物理内存范围属性的寄存器。每个MTRR包含一个起始物理地址、一个大小和一个缓存类型。MTRR的数量取决于系统,但非常有限。虽然适用于物理内存范围,但效果会作用于相应的虚拟内存页。例如,可以使用特定的缓存类型映射页面。
  • PAT(页面属性表)允许设置每页内存属性。与MTRR一样依赖有限数量的内存范围不同,可以在每页的基础上指定缓存属性。但是,它是仅在最新x86处理器上可用的扩展。

除此之外,可以在某些体系结构上使用显式缓存指令,例如在x86上,movntq是一条未缓存的mov指令,clflush可以选择性地刷新缓存行。

有三种缓存模式,可通过MTRR和PAT系统内存使用:

  • UC(UnCached)内存未缓存。CPU对此区域的读/写是未缓存的,每个内存写入指令都会触发实际的即时内存写入。有助于确保信息已实际写入,以避免CPU/GPU争用的情况。
  • WC(Write Combine)内存未缓存,但CPU写入被组合在一起以提高性能。在需要未缓存内存但将写操作组合在一起不会产生不利影响的情况下,非常有利于提高性能。
  • WB(Write Back)内存已被缓存。是默认模式,可以获得CPU访问的最佳性能。然而,并不能确保内存写入在有限时间后传播到中央内存。

请注意,以上缓存模式仅适用于CPU,GPU访问不受当前缓存模式的直接影响。然而,当GPU必须访问之前由CPU填充的内存区域时,未缓存模式可确保实际完成内存写入,并且不会挂起在CPU缓存中。实现相同效果的另一种方法是使用某些x86处理器(如cflush)上的缓存刷新指令,但比使用缓存模式的可移植性差。另一种(可移植的)方法是使用内存屏障,它可以确保在继续之前将挂起的内存写入提交到主内存。

显然,有这么多不同的缓存模式,并非所有访问都具有相同的性能:

  • 在CPU访问系统内存时,未缓存模式提供最差的性能,回写提供最好的性能,写入组合介于两者之间。
  • 当CPU从离散卡访问视频内存时,所有访问都非常慢,无论是读还是写,因为每次访问都需要总线上的一个周期。因此,不建议使用CPU访问大面积的VRAM。此外,在某些GPU上需要同步,否则可能会导致GPU挂起。
  • 显然,GPU访问VRAM的速度非常快。
  • GPU对系统RAM的访问不受缓存模式的影响,但仍须通过总线,DMA事务就是这种情况。由于都是异步发生的,从CPU的角度来看,它们可以被视为“免费”,但是每个DMA事务都涉及到不可忽略的设置成本。这就是为什么在传输少量内存时,DMA事务并不总是优于直接CPU访问。

最后,关于内存的最后一个重要观点是内存屏障和写入账本(write posting)的概念。对于缓存(写合并或写回)内存区域,内存屏障可确保挂起的写操作实际上已提交到内存。例如,在要求GPU读取给定的内存区域之前,会使用此选项。对于I/O区域,存在一种类似的称为写入账本的技术:包括在I/O区域内进行虚拟读取,作为一种副作用,它会等到挂起的写入生效后再完成。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lNEQw0ro-1656218692594)(https://img2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值