异步 VM_BIND¶
命名法:¶
VRAM
:设备上的内存。有时也称为设备本地内存。gpu_vm
:虚拟 GPU 地址空间。通常每个进程一个,但可以由多个进程共享。VM_BIND
:使用 IOCTL 修改 gpu_vm 的操作或操作列表。这些操作包括映射和取消映射系统内存或 VRAM 内存。syncobj
:抽象同步对象的容器。同步对象可以是通用的,例如 dma-fences,也可以是驱动程序特定的。syncobj 通常指示底层同步对象的类型。in-syncobj
:VM_BIND IOCTL 的参数,VM_BIND 操作在启动之前等待这些参数。out-syncobj
:VM_BIND_IOCTL 的参数,VM_BIND 操作在绑定操作完成时发出这些信号。dma-fence
:跨驱动程序同步对象。要理解本文档,需要对 dma-fence 有基本了解。请参阅 dma-buf 文档 的DMA Fences
部分。memory fence
:与 dma-fence 不同的同步对象。内存栅栏使用指定内存位置的值来确定信号状态。GPU 和 CPU 都可以等待和发出内存栅栏的信号。内存栅栏有时被称为用户栅栏、用户空间栅栏或 gpu futexes,并且不一定遵守在“合理的时间内”发出信号的 dma-fence 规则。因此,内核应避免在持有锁的情况下等待内存栅栏。long-running workload
:可能需要比当前规定的 dma-fence 最大信号延迟更长的时间才能完成的工作负载,因此需要将 gpu_vm 或 GPU 执行上下文设置为某种不允许完成 dma-fences 的模式。exec function
:exec 函数是一个重新验证所有受影响的 gpu_vma 的函数,提交 GPU 命令批处理,并使用所有受影响的 dma_resv 注册表示 GPU 命令活动的 dma_fence。为了完整起见,尽管本文档未涵盖,但值得一提的是,exec 函数也可能是某些驱动程序在计算/长时间运行模式下使用的重新验证工作程序。bind context
:用于 VM_BIND 操作的上下文标识符。可以使用相同的绑定上下文的 VM_BIND 操作可以假设(在重要的情况下)按照提交的顺序完成。对于使用单独绑定上下文的 VM_BIND 操作,不能做出此类假设。UMD
:用户模式驱动程序。KMD
:内核模式驱动程序。
同步/异步 VM_BIND 操作¶
同步 VM_BIND¶
使用同步 VM_BIND,所有 VM_BIND 操作都在 IOCTL 返回之前完成。同步 VM_BIND 不接收 in-fence 或 out-fence。同步 VM_BIND 可能会阻塞并等待 GPU 操作;例如,交换或清除,甚至之前的绑定。
异步 VM_BIND¶
异步 VM_BIND 接受 in-syncobj 和 out-syncobj。虽然 IOCTL 可能立即返回,但 VM_BIND 操作会等待 in-syncobj,然后才修改 GPU 页表,并在修改完成时发出 out-syncobj 的信号,即下一个等待 out-syncobj 的 exec 函数将看到更改。错误会同步报告。在低内存情况下,实现可能会阻塞,同步执行 VM_BIND,因为可能没有足够的内存可立即用于准备异步操作。
如果 VM_BIND IOCTL 接受操作列表或数组作为参数,则 in-syncobj 需要在第一个操作开始执行之前发出信号,out-syncobj 在最后一个操作完成之后发出信号。操作列表中的操作可以假设(在重要的情况下)按顺序完成。
由于异步 VM_BIND 操作可能会使用嵌入在 out-syncobj 中以及 KMD 内部的 dma-fence 来发出绑定完成信号,因此作为 VM_BIND in-fence 给出的任何内存栅栏都需要在 VM_BIND ioctl 返回之前同步等待,因为 dma-fence 要求在合理的时间内发出信号,永远不能依赖于没有这种限制的内存栅栏。
异步 VM_BIND 操作的目的是使用户模式驱动程序能够流水线化交错的 gpu_vm 修改和 exec 函数。对于长时间运行的工作负载,不允许对绑定操作进行这种流水线化,并且任何 in-fence 都需要同步等待。原因有两方面。首先,由长时间运行的工作负载门控并用作 VM_BIND 操作的 in-syncobj 的任何内存栅栏都需要同步等待(见上文)。其次,用于长时间运行的工作负载的 VM_BIND 操作的 in-syncobj 的任何 dma-fence 都不会允许流水线化,因为长时间运行的工作负载不允许将 dma-fence 用作 out-syncobj,因此,虽然理论上是可能的,但使用它们值得怀疑,并且应该拒绝,直到出现有价值的用例。请注意,这不是由 dma-fence 规则施加的限制,而是为保持 KMD 实现简单而施加的限制。它不会影响将 dma-fence 用作长时间运行的工作负载本身的依赖项,这是 dma-fence 规则允许的,而是仅适用于 VM_BIND 操作。
异步 VM_BIND 操作可能需要相当长的时间才能完成并发出 out_fence 的信号。特别是当操作深度流水线化到其他 VM_BIND 操作和使用 exec 函数提交的工作负载之后时。在这种情况下,如果没有显式依赖关系,UMD 可能希望避免将后续 VM_BIND 操作排在第一个操作之后。为了规避这种排队,VM_BIND 实现可能允许创建 VM_BIND 上下文。对于每个上下文,将保证 VM_BIND 操作按照提交的顺序完成,但对于在单独的 VM_BIND 上下文中执行的 VM_BIND 操作则不是这种情况。相反,KMD 将尝试并行执行此类 VM_BIND 操作,但不保证它们实际上会并行执行。可能存在只有 KMD 知道的内部隐式依赖关系,例如页表结构更改。尝试避免此类内部依赖关系的一种方法是让不同的 VM_BIND 上下文使用 VM 的单独区域。
同样,对于长时间运行的 gpu_vm 的 VM_BIND,用户模式驱动程序通常应选择内存栅栏作为 out-fence,因为这为内核模式驱动程序在绑定/取消绑定操作中注入其他操作提供了更大的灵活性。例如,将断点插入批处理缓冲区。然后,可以使用内存 out-fence 作为 UMD 嵌入到工作负载中的 GPU 信号量的信号条件,轻松地将工作负载执行流水线化到绑定完成之后。
异步 VM_BIND 和同步 VM_BIND 在支持的操作或多操作支持方面没有区别。
多操作 VM_BIND IOCTL 错误处理和中断¶
由于各种原因,IOCTL 的 VM_BIND 操作可能会出错,例如由于资源不足而无法完成以及由于等待中断。在这些情况下,UMD 最好在采取适当的操作后重新启动 IOCTL。如果 UMD 过度提交了内存资源,则会返回 -ENOSPC 错误,然后 UMD 可以取消绑定当前未使用的资源并重新运行 IOCTL。在 -EINTR 上,UMD 应该简单地重新运行 IOCTL,在 -ENOMEM 上,用户空间可以尝试释放已知的系统内存资源或失败。如果 UMD 决定由于错误返回而使绑定操作失败,则无需采取其他操作来清理失败的操作,并且 VM 将保持与失败的 IOCTL 之前的状态相同的状态。保证取消绑定操作不会由于资源约束而返回任何错误,但可能会由于例如参数无效或 gpu_vm 被禁止而返回错误。如果在异步绑定过程中发生意外错误,gpu_vm 将被禁止,并且在禁止后尝试使用它将返回 -ENOENT。
示例:Xe VM_BIND uAPI¶
从 VM_BIND 操作结构开始,IOCTL 调用可以采用零个、一个或多个此类操作。零表示仅执行 IOCTL 的同步部分:异步 VM_BIND 更新 syncobj,而同步 VM_BIND 等待隐式依赖关系得到满足。
struct drm_xe_vm_bind_op {
/**
* @obj: GEM object to operate on, MBZ for MAP_USERPTR, MBZ for UNMAP
*/
__u32 obj;
/** @pad: MBZ */
__u32 pad;
union {
/**
* @obj_offset: Offset into the object for MAP.
*/
__u64 obj_offset;
/** @userptr: user virtual address for MAP_USERPTR */
__u64 userptr;
};
/**
* @range: Number of bytes from the object to bind to addr, MBZ for UNMAP_ALL
*/
__u64 range;
/** @addr: Address to operate on, MBZ for UNMAP_ALL */
__u64 addr;
/**
* @tile_mask: Mask for which tiles to create binds for, 0 == All tiles,
* only applies to creating new VMAs
*/
__u64 tile_mask;
/* Map (parts of) an object into the GPU virtual address range.
#define XE_VM_BIND_OP_MAP 0x0
/* Unmap a GPU virtual address range */
#define XE_VM_BIND_OP_UNMAP 0x1
/*
* Map a CPU virtual address range into a GPU virtual
* address range.
*/
#define XE_VM_BIND_OP_MAP_USERPTR 0x2
/* Unmap a gem object from the VM. */
#define XE_VM_BIND_OP_UNMAP_ALL 0x3
/*
* Make the backing memory of an address range resident if
* possible. Note that this doesn't pin backing memory.
*/
#define XE_VM_BIND_OP_PREFETCH 0x4
/* Make the GPU map readonly. */
#define XE_VM_BIND_FLAG_READONLY (0x1 << 16)
/*
* Valid on a faulting VM only, do the MAP operation immediately rather
* than deferring the MAP to the page fault handler.
*/
#define XE_VM_BIND_FLAG_IMMEDIATE (0x1 << 17)
/*
* When the NULL flag is set, the page tables are setup with a special
* bit which indicates writes are dropped and all reads return zero. In
* the future, the NULL flags will only be valid for XE_VM_BIND_OP_MAP
* operations, the BO handle MBZ, and the BO offset MBZ. This flag is
* intended to implement VK sparse bindings.
*/
#define XE_VM_BIND_FLAG_NULL (0x1 << 18)
/** @op: Operation to perform (lower 16 bits) and flags (upper 16 bits) */
__u32 op;
/** @mem_region: Memory region to prefetch VMA to, instance not a mask */
__u32 region;
/** @reserved: Reserved */
__u64 reserved[2];
};
VM_BIND IOCTL 参数本身如下所示。请注意,对于同步 VM_BIND,num_syncs 和 syncs 字段必须为零。此处的 exec_queue_id
字段是前面讨论的 VM_BIND 上下文,用于促进无序 VM_BIND。
struct drm_xe_vm_bind {
/** @extensions: Pointer to the first extension struct, if any */
__u64 extensions;
/** @vm_id: The ID of the VM to bind to */
__u32 vm_id;
/**
* @exec_queue_id: exec_queue_id, must be of class DRM_XE_ENGINE_CLASS_VM_BIND
* and exec queue must have same vm_id. If zero, the default VM bind engine
* is used.
*/
__u32 exec_queue_id;
/** @num_binds: number of binds in this IOCTL */
__u32 num_binds;
/* If set, perform an async VM_BIND, if clear a sync VM_BIND */
#define XE_VM_BIND_IOCTL_FLAG_ASYNC (0x1 << 0)
/** @flag: Flags controlling all operations in this ioctl. */
__u32 flags;
union {
/** @bind: used if num_binds == 1 */
struct drm_xe_vm_bind_op bind;
/**
* @vector_of_binds: userptr to array of struct
* drm_xe_vm_bind_op if num_binds > 1
*/
__u64 vector_of_binds;
};
/** @num_syncs: amount of syncs to wait for or to signal on completion. */
__u32 num_syncs;
/** @pad2: MBZ */
__u32 pad2;
/** @syncs: pointer to struct drm_xe_sync array */
__u64 syncs;
/** @reserved: Reserved */
__u64 reserved[2];
};