非同步 VM_BIND¶
命名法:¶
VRAM:裝置上的記憶體。有時稱為裝置本地記憶體。gpu_vm:虛擬 GPU 地址空間。通常每個程序一個,但可以由多個程序共享。VM_BIND:使用 IOCTL 修改 gpu_vm 的一個操作或一系列操作。這些操作包括對映和取消對映系統記憶體或 VRAM 記憶體。syncobj:一個抽象同步物件的容器。同步物件可以是通用的,例如 dma-fence,也可以是特定於驅動程式的。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 不同。memory fence 使用指定記憶體位置的值來確定訊號狀態。GPU 和 CPU 都可以等待和發出 memory fence 的訊號。Memory fence 有時被稱為 user-fence、userspace-fence 或 gpu futex,並且不一定遵守 dma-fence 在“合理時間內”發出訊號的規則。因此,核心應避免在持有鎖時等待 memory fence。long-running workload:一個可能花費超過當前規定的 dma-fence 最大訊號延遲才能完成的工作負載,因此需要將 gpu_vm 或 GPU 執行上下文設定為某種不允許完成 dma-fence 的模式。exec function:exec 函式是一個重新驗證所有受影響的 gpu_vma、提交 GPU 命令批處理,並將表示 GPU 命令活動的 dma_fence 註冊到所有受影響的 dma_resv 的函式。為了完整起見,雖然本文件未涵蓋,但值得一提的是,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 操作會在修改 GPU 頁表之前等待 in-syncobj,並在修改完成後向 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 提供的 memory fence 都需要在 VM_BIND ioctl 返回之前同步等待,因為 dma-fence 需要在合理的時間內發出訊號,因此永遠不能依賴於沒有這種限制的 memory fence。
非同步 VM_BIND 操作的目的是使使用者模式驅動程式能夠流水線化交錯的 gpu_vm 修改和 exec 函式。對於長時間執行的工作負載,不允許流水線化繫結操作,並且任何 in-fence 都需要同步等待。原因有兩方面。首先,任何受長時間執行的工作負載控制並用作 VM_BIND 操作的 in-syncobj 的 memory fence 都需要同步等待(如上所述)。其次,任何用作長時間執行工作負載的 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_BINDS,使用者模式驅動程式通常應選擇 memory fence 作為 out-fence,因為這為核心模式驅動程式提供了更大的靈活性,可以將其他操作注入到繫結/取消繫結操作中。例如,將斷點插入到批處理緩衝區中。然後可以使用 memory 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 更新 syncobject,而同步 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];
};