欢迎光临
我们一直在努力

第一章 DRM 子系统概述:1.4命令提交的硬件模型

1.3 节说明了 GPU 在系统中的位置、内存的访问路径与地址翻译。本节说明命令如何从 CPU 到达 GPU ,并概述新一代 GPU 的硬件调度机制,不涉及软件以何种方式使用这些机制。内容不针对特定厂商,因此不讨论各厂商的寄存器与命令包格式。

1. 硬件组成

GPU 以异步方式执行命令:CPU 将命令写入内存后通知 GPU,随即返回继续其他工作,GPU 稍后执行并报告完成。CPU 交给 GPU 执行的一个工作单元称为作业(job),例如一次绘制或一次计算分派,其命令存放在一个或多个命令缓冲区中。GPU 内部按功能分为多类执行引擎:图形引擎执行绘制,计算引擎按指定的线程网格启动计算着色器(compute dispatch)执行通用计算,复制引擎在内存之间搬运数据,视频引擎负责编解码。同类引擎可以有多个实例,例如多个复制引擎。

命令执行涉及的硬件部件及其交互如图 1.4.1 所示。图中实线为一次提交经过的路径,以图形引擎为例;虚线为管理路径,不在每次提交中发生。
在这里插入图片描述

图 1.4.1 执行引擎与命令处理器的组成与交互

每个引擎的前端设有命令处理器(Command Processor,CP),它是运行微码的专用处理器,本身不执行着色器程序,只负责从内存中取出命令、解析后分发给引擎内的执行单元,并处理同步、缓存控制与完成通知。这里的命令处理器指逻辑上的取包前端。物理上,多个引擎的前端可以集中在同一个硬件块中,各自使用独立的微处理器与流水线,例如 AMD 的 CP 同时包含服务图形与计算的多个微引擎;复制与视频引擎则常带有独立的、较简单的取包前端。

CPU 与 CP 之间的通知由另外两个部件承担:

  • 门铃 BAR 是 GPU 在 PCIe 上暴露的一段地址窗口,划分为许多门铃地址,CPU 写入其中一个地址即通知 GPU 有新命令待处理;
  • 中断控制器则将命令完成、缺页、错误等事件以中断形式通知 CPU。

前者承载提交方向的通知,后者承载完成方向的通知。

部分 GPU 还内置一个调度微控制器(图中的虚线框),运行调度固件,例如 Intel GuC、AMD MES 与 Arm CSF 的固件,负责决定命令处理器当前服务哪些工作负载。它不参与每一次提交,因此图中以虚线表示其管理路径;不具备该部件的 GPU 由主机软件在初始化时把队列固定映射到槽位,此后不再改变。

2. 作业的生命周期

CPU 与命令处理器之间经内存中的数据结构传递命令,接口有两层:

  • 命令缓冲区(Indirect Buffer,IB):存放一项作业的具体命令,如绘制、启动计算着色器、内存复制,由 CPU 写入内存。
  • ring 缓冲区:一段首尾相接的内存区域,存放命令处理器要依次处理的包。ring 中的包多数并不包含作业本身的命令,而是指向 IB 的地址与长度,以及同步、缓存控制、完成通知等控制命令。
  • CPU 以写指针标记 ring 中写入的末尾,命令处理器以读指针标记已经取走的位置,并经门铃得知写指针的更新。ring 连同读写指针与门铃构成一个队列(Queue),这是取包的基本单位,队列的完整组成在第 3 节说明。一次命令提交与完成的过程如图 1.4.2 所示。
    在这里插入图片描述

    图 1.4.2 命令提交与完成:ring、门铃与 fence

  • CPU 将作业命令写入 IB(①)。
  • CPU 在 ring 的写指针处写入两个包(②):一个指向 IB,另一个是 fence 包。fence 包要求引擎在此前的命令全部完成、缓存写回之后,将一个递增的序号写入指定的 fence 内存,并触发中断。
  • CPU 更新写指针并写入门铃(③)。硬件据此得知队列中有新包,无需轮询内存中的写指针。
  • 命令处理器在读指针与写指针之间依次取包(④),引擎按包中的地址读取 IB 并执行(⑤)。
  • 作业完成后,引擎将序号写入 fence 内存(⑥),并产生中断(⑦)。CPU 的中断处理程序读取 fence 内存中的序号,序号不大于该值的作业均已完成。
  • fence 内存不属于队列。fence 写入的地址由 fence 包自身携带,同一队列中的不同 fence 包可以指向不同的地址。

    由于同一 ring 上的包按顺序处理,队列上的作业按顺序执行,fence 序号单调递增,比较序号即可判断任意一项作业是否完成,无需逐项查询。不同队列之间的执行顺序则由第 3、4 节所述的槽位仲裁与调度决定。

    ring 的容量有限,写指针追上读指针时 ring 已满,新的提交必须等待命令处理器处理完一部分包。作业也可能无法完成,着色器陷入死循环、访问非法地址或硬件异常都可能使作业挂起,而硬件通常不会主动报告挂起,只能由软件在等待超时后判定,再复位相应的引擎或整个 GPU。复位会丢失正在执行的作业及其上下文状态。

    3. 队列与硬件队列槽位

    第 2 节把 ring、读写指针与门铃合称为队列,只考察了单个队列上的作业。本节说明队列的完整组成,以及命令处理器如何选择要服务的队列。队列既不是某个单一的硬件部件,也不是软件可以自由定义的数据结构,而是由硬件规定格式、由软件分配存储的一组对象,其组成如表 1.4.1 所示。

    表 1.4.1 队列的组成

    组成部分所在位置作用
    ring 内存 存放待处理的包
    写指针 内存,同时经门铃通知硬件 标记 CPU 写入的末尾
    读指针 命令处理器内部,并写回内存中的指定位置 标记已取走的位置,CPU 据此计算 ring 的剩余空间
    门铃(doorbell) 门铃 BAR 中分配给该队列的一个地址 CPU 写入新的写指针,通知硬件该队列有新包
    队列描述符 内存 按硬件规定的格式记录上述各项的地址、所属地址空间的标识与优先级,以及队列离开槽位时的执行状态

    读写指针之间的包即为已提交、待执行的包。命令只能交给能够执行它的引擎,因此每个队列都属于某一类引擎,例如计算队列中的命令只由计算引擎执行。

    表中各项的分工可以从两个方面理解。其一,格式由硬件规定。包的编码、读写指针的推进规则、门铃与队列的对应方式以及描述符中每个字段的位置,都是命令处理器微码与软件之间的约定,软件只能按此填写。其二,存储由软件分配。ring、指针与描述符所在的内存由软件申请,门铃地址由软件从门铃 BAR 中分配,内容也由软件写入。队列的这种性质与 CPU 的页表相同:页表由软件在内存中建立,格式由处理器规定,遍历由硬件完成。

    队列描述符是把其余各项联系在一起的结构。ring 与指针分散在内存中,门铃位于 BAR 中,命令处理器本身并不知道它们的存在;描述符中的配置装入之后,这些对象才构成一个可以取包的队列。

    描述符装入的位置称为硬件队列槽位。命令处理器能够同时服务的队列数量是固定的,它为每个可同时服务的队列设置一组寄存器,即一个槽位,记录该队列的 ring 基地址与大小、读写指针所在的内存地址、对应的门铃、所属地址空间的标识以及优先级,并带有一个激活位。它只从占用槽位且已激活的队列取包;一个队列若只存在于内存而未占用槽位,即使写入了包、写入了门铃,也不会被执行。槽位数量因架构与引擎而异。计算引擎通常有数十个槽位,使多个队列的计算任务可以同时占用计算单元;图形与复制引擎的槽位较少,常见为数个。各厂商的名称不同,例如 AMD 的 HQD 寄存器组、Intel 的执行列表端口、Arm 的命令流组槽位,作用均与此相当。

    因此,内存中按格式填写完整的队列并不一定处于可执行状态。队列本身位于内存,数量不受槽位数量的限制,只有占用槽位的那一部分队列才会被执行。本节所说的队列均指这种由硬件定义格式的对象,与软件内部用于暂存作业的同名数据结构不同。

    4. 队列的映射与抢占

    队列数量可以远多于槽位。决定哪个队列占用槽位的一方称为调度方,可以是主机上的驱动,由它直接写入槽位寄存器;也可以是 GPU 内置的调度微控制器。队列、槽位与调度方之间的关系如图 1.4.3 所示。
    在这里插入图片描述

    图 1.4.3 队列、硬件队列槽位与调度方

    CPU 写入 ring 包并推进写指针(①),再写入门铃(②);门铃对应的队列已占用槽位时,硬件将通知路由到该槽位(③),仲裁器按优先级从各激活槽位取包并分发到执行引擎(④)。队列未占用槽位时,门铃通知调度方(⑤),由调度方选取空闲槽位装入该队列的描述符(⑥);槽位不足时,调度方先抢占某个已映射的队列,将其执行状态写回描述符并释放槽位(⑦)。若调度方在初始化时一次性完成映射且此后不再改变,则不发生⑤至⑦。

    4.1 映射与解除映射

    队列与槽位之间的关系由映射与解除映射两种操作。

    映射将队列描述符装入一个空闲槽位并激活,CP 随即开始从该队列取包;解除映射先抢占队列中正在执行的作业,将执行状态写回队列描述符,再释放槽位。

    解除映射后,队列及其 ring 仍然存在,写入 ring 的包暂不执行。队列的执行状态(如读指针与 CP 的内部状态)保存在描述符中。重新映射后,CP 从保存的状态继续执行,所用槽位不必与之前相同。槽位与队列的关系对应于 CPU 上处理器核与进程的关系:槽位数量固定,队列数量可以远多于槽位,队列描述符相当于进程被切换出去时保存的上下文。

    驱动作为调度方时,直接写入槽位寄存器完成映射与解除映射。调度微控制器作为调度方时,还接收未占用槽位的队列的门铃通知,并在队列之间按优先级与时间片轮换槽位,从而把数量众多的队列复用到有限的槽位上。

    4.2 抢占

    多个槽位同时激活时,命令处理器在它们之间仲裁,按槽位的优先级轮流从各队列取包。计算引擎可以让来自不同槽位的作业同时在计算单元上执行;图形引擎通常同一时刻只执行一个队列的作业,切换队列需要抢占当前作业。解除映射同样以抢占为前提:队列中的作业正在执行时,必须先使其停下,槽位才能释放。

    抢占使正在执行的作业中途停止,并保存继续执行所需的状态。命令处理器的状态写回队列描述符,作业在执行单元上的状态则保存在描述符所指向的上下文保存区中;作业恢复执行时,再从这两处载入。

    抢占的粒度决定了抢占的延迟与需要保存的状态量。在命令包边界抢占时,只需等待当前包执行完毕,保存的状态最少,但一个耗时很长的绘制或计算命令会使抢占长时间不能完成;在着色器指令级抢占时,执行单元中各线程的寄存器与局部存储都要写入上下文保存区,保存的数据量最大,但延迟可控。较新的 GPU 普遍支持指令级抢占,使解除映射能在有限时间内完成。

    5. 作业边界的缓存同步

    1.3 节已说明,CPU 与 GPU 之间的缓存一致性须由软件显式维护。硬件中没有持续追踪主机访问的部件,同步只能在已知的时间点触发,通常选在作业的边界,即作业开始前与作业结束时。同步动作本身由硬件执行,触发方式如表 1.4.2 所示。

    表 1.4.2 缓存同步的触发方式

    触发方式执行过程典型用途
    命令流中的缓存控制包 命令处理器执行到该包时,向各级缓存发出写回或失效请求,待全部完成后再处理后续的包 作业开始前使 GPU 缓存失效
    fence 包附带的缓存操作 引擎完成此前的全部命令后,先写回缓存,再写入完成序号 保证完成序号可见时,结果已到达内存
    MMIO 寄存器 CPU 写入特定寄存器,直接触发刷新或失效 刷新主机数据通路的写缓冲,使其读缓存失效

    CPU 写入的数据要被引擎读到,需依次经过三层同步:执行存储屏障指令(如 x86 的 sfence)排空写合并缓冲区,刷新主机数据通路,再使 GPU 缓存失效。存储屏障须先于门铃写入,否则 GPU 可能先收到门铃,而数据仍滞留在 CPU 核内。主机数据通路与 GPU 缓存相互独立,刷新其中之一不影响另一方,因此两侧须分别同步。GPU 缓存在容量不足时虽会被动写回脏数据,但时机不确定,不能代替显式同步。

    GPU 内的缓存实例数量众多,每个计算单元都有各自的 L1。部分架构为此设有集中的缓存请求控制器,将一次写回或失效请求分发到所有实例,并在全部完成后统一应答。

    一项作业从 CPU 准备输入到 CPU 读取结果,缓存同步的完整时序如图 1.4.4 所示。
    在这里插入图片描述

    图 1.4.4 CPU 与 GPU 之间的缓存同步时序

  • CPU 经显存 BAR 写入输入数据(①),随后执行存储屏障排空写合并缓冲区(②),再刷新主机数据通路(③),使暂存的数据写入 VRAM。
  • CPU 写入 ring 包并敲门铃(④)。命令处理器在作业开始时先使 GPU 缓存失效(⑤),避免引擎命中该地址的旧数据。
  • 引擎经 GPU 缓存读写数据(⑥),结果可能长时间停留在缓存中。
  • 作业结束时,命令处理器写回 GPU 缓存中的脏数据(⑦),然后写入 fence 序号(⑧)并触发中断(⑨)。由于写回先于序号写入,CPU 看到序号即可确认结果已到达 VRAM。
  • 若主机数据通路带有读缓存,CPU 读取前先使其失效(⑩),再经显存 BAR 读取结果(⑪)。
  • PCIe 的排序规则是这一时序成立的前提。posted 写按发出顺序到达,因此输入数据的写入不会落后于门铃写入;读请求的完成报文不能越过此前的 posted 写,因此 CPU 读回 GPU 的任一寄存器,即可确认此前的写入均已到达 GPU。数据是否已经写入 VRAM,则仍取决于主机数据通路的刷新。

    上述显式同步只在两种情况下可以省略:一是 GPU 缓存设计为显存侧缓存,主机访问与引擎访问共同经过它;二是 CPU 与 GPU 之间采用硬件一致性互连,双方可以互相窥探缓存。

    赞(0)
    未经允许不得转载:171主机测评 » 第一章 DRM 子系统概述:1.4命令提交的硬件模型
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址