GPU SVM 章節¶
一致的設計原則¶
- migrate_to_ram 路徑
僅依賴於核心 MM 概念(遷移 PTE、頁面引用和頁面鎖定)。
除了硬體互動的鎖之外,沒有驅動程式特定的鎖。 不需要這些鎖,並且通常來說,發明驅動程式定義的鎖來密封核心 MM 競爭是一個壞主意。
在修復 do_swap_page 以鎖定故障頁面之前,發生了一個驅動程式特定的鎖導致問題的示例。 如果足夠多的執行緒讀取故障頁面,migrate_to_ram 中的驅動程式獨佔鎖會產生穩定的活鎖。
支援部分遷移(即,嘗試遷移的頁面的子集實際上可以遷移,只有故障頁面保證可以遷移)。
驅動程式透過重試迴圈而不是鎖定來處理混合遷移。
- 驅逐
驅逐被定義為將資料從 GPU 遷移回 CPU,而無需虛擬地址來釋放 GPU 記憶體。
僅檢視物理記憶體資料結構和鎖,而不是檢視虛擬記憶體資料結構和鎖。
不檢視 mm/vma 結構或依賴於這些結構被鎖定。
以上兩點的理由是 CPU 虛擬地址可以隨時更改,而物理頁面保持穩定。
GPU 頁表無效化(需要 GPU 虛擬地址)透過可以訪問 GPU 虛擬地址的通知器來處理。
- GPU 故障端
mmap_read 僅用於核心 MM 函數週圍,這些函式需要此鎖,並且應努力僅在 GPU SVM 層中獲取 mmap_read 鎖。
大型重試迴圈用於處理與 gpu 頁表鎖/mmu 通知器範圍鎖/我們最終呼叫的任何東西下的 mmu 通知器的所有競爭。
不應透過嘗試持有鎖來在故障端處理競爭(尤其是與併發驅逐或 migrate_to_ram 的競爭);相反,應使用重試迴圈處理它們。 一個可能的例外是在初始遷移到 VRAM 期間持有 BO 的 dma-resv 鎖,因為這是一個明確定義的鎖,可以在 mmap_read 鎖下獲取。
上述方法的一個可能問題是,如果驅動程式具有嚴格的遷移策略,要求 GPU 訪問發生在 GPU 記憶體中。 併發 CPU 訪問可能導致由於無限重試而導致的活鎖。 雖然當前 GPU SVM 的使用者 (Xe) 沒有這樣的策略,但將來可能會新增。 理想情況下,這應該在核心 MM 端解決,而不是透過驅動程式側鎖解決。
- 物理記憶體到虛擬回指標
這不起作用,因為不應存在從物理記憶體到虛擬記憶體的指標。 mremap() 是核心 MM 更新虛擬地址而不通知驅動程式地址更改的一個示例,而驅動程式只接收到無效通知器。
物理記憶體回指標 (page->zone_device_data) 應從分配到頁面釋放保持穩定。 安全地針對併發使用者更新它將非常困難,除非頁面是空閒的。
- GPU 頁表鎖定
通知器鎖僅保護範圍樹,範圍的頁面有效狀態(而不是由於更廣泛的通知器導致的 seqno),頁表條目和 mmu 通知器 seqno 跟蹤,它不是防止競爭的全域性鎖。
如上所述,所有競爭都透過大型重試處理。
基線設計概述¶
直接渲染管理器 (DRM) 的 GPU 共享虛擬記憶體 (GPU SVM) 層是 DRM 框架的一個元件,旨在管理 CPU 和 GPU 之間的共享虛擬記憶體。 它透過允許 CPU 和 GPU 虛擬地址空間之間的記憶體共享和同步,為 GPU 加速的應用程式實現高效的資料交換和處理。
關鍵 GPU SVM 元件
- 通知器
用於跟蹤記憶體間隔並通知 GPU 更改,通知器的大小基於 GPU SVM 初始化引數,建議大小為 512M 或更大。 它們維護一個紅黑樹和一個屬於通知器間隔內的範圍列表。 通知器在 GPU SVM 紅黑樹和列表中被跟蹤,並隨著間隔內的範圍的建立或銷燬而動態地插入或刪除。
- 範圍
表示在 DRM 裝置中對映並由 GPU SVM 管理的記憶體範圍。 它們的大小基於塊大小陣列(它是 GPU SVM 初始化引數)和 CPU 地址空間。 在 GPU 故障時,選擇適合故障 CPU 地址空間的最大對齊塊作為範圍大小。 範圍預計會在 GPU 故障時動態分配,並在 MMU 通知器 UNMAP 事件時移除。 如上所述,範圍在通知器的紅黑樹中被跟蹤。
- 操作
定義驅動程式特定的 GPU SVM 操作的介面,例如範圍分配、通知器分配和無效化。
- 裝置記憶體分配
嵌入式結構,包含足夠的資訊供 GPU SVM 遷移到/從裝置記憶體。
- 裝置記憶體操作
定義驅動程式特定的裝置記憶體操作介面,包括釋放記憶體、填充 pfns 和複製到/從裝置記憶體。
該層提供用於在 CPU 和 GPU 之間分配、對映、遷移和釋放記憶體範圍的介面。 它處理所有核心記憶體管理互動(DMA 對映、HMM 和遷移),並提供驅動程式特定的虛擬函式 (vfuncs)。 該基礎架構足以構建 SVM 實現所需的預期驅動程式元件,如下詳述。
預期驅動程式元件
- GPU 頁面錯誤處理程式
用於基於故障地址建立範圍和通知器,可以選擇將範圍遷移到裝置記憶體,並建立 GPU 繫結。
- 垃圾回收器
用於取消對映和銷燬範圍的 GPU 繫結。 預計在通知器回撥中的 MMU_NOTIFY_UNMAP 事件時將範圍新增到垃圾回收器。
- 通知器回撥
用於使範圍的 GPU 繫結無效並 DMA 取消對映。
GPU SVM 處理核心 MM 互動的鎖定,即根據需要鎖定/解鎖 mmap 鎖。
GPU SVM 引入了一個全域性通知器鎖,它保護通知器的範圍 RB 樹和列表,以及範圍的 DMA 對映和序列號。 GPU SVM 管理所有必需的鎖定和解鎖操作,除了驅動程式提交 GPU 繫結時重新檢查範圍的頁面是否有效 (drm_gpusvm_range_pages_valid) 之外。 此鎖對應於異構記憶體管理 (HMM)中提到的 driver->update 鎖。 如果認為需要更細粒度的鎖定,未來的修訂版可能會從 GPU SVM 全域性鎖轉換為每個通知器鎖。
除了上述鎖定之外,驅動程式還應實現一個鎖來保護修改狀態的核心 GPU SVM 函式呼叫,例如 drm_gpusvm_range_find_or_insert 和 drm_gpusvm_range_remove。 在程式碼示例中,此鎖表示為“driver_svm_lock”。 還應該可以對單個 GPU SVM 中的併發 GPU 故障處理進行更細粒度的驅動程式側鎖定。 可以透過 drm_gpusvm_driver_set_lock 將“driver_svm_lock”新增到 GPU SVM 中。
遷移支援非常簡單,允許在 RAM 和裝置記憶體之間以範圍粒度進行遷移。 例如,GPU SVM 目前不支援在一個範圍內混合 RAM 和裝置記憶體頁面。 這意味著,在 GPU 故障時,整個範圍可以遷移到裝置記憶體,而在 CPU 故障時,整個範圍將遷移到 RAM。 如果需要,將來可能會新增在一個範圍內混合 RAM 和裝置記憶體儲存。
僅支援範圍粒度的原因是:它簡化了實現,並且範圍大小由驅動程式定義,應該相對較小。
範圍的部分取消對映(例如,CPU 取消對映 2M 中的 1M 導致 MMU_NOTIFY_UNMAP 事件)帶來了一些挑戰,主要挑戰是範圍的子集仍然具有 CPU 和 GPU 對映。 如果範圍的後備儲存位於裝置記憶體中,則後備儲存的子集具有引用。 一種選擇是拆分範圍和裝置記憶體後備儲存,但這的實現將非常複雜。 鑑於部分取消對映很少見,並且驅動程式定義的範圍大小相對較小,因此 GPU SVM 不支援拆分範圍。
由於不支援範圍拆分,因此在部分取消對映範圍後,預計驅動程式會使整個範圍無效並銷燬它。 如果範圍具有裝置記憶體作為其後備,則還應預期驅動程式將任何剩餘的頁面遷移回 RAM。
本節提供了三個關於如何構建預期驅動程式元件的示例:GPU 頁面錯誤處理程式、垃圾回收器和通知器回撥。
提供的通用程式碼不包括複雜遷移策略、最佳化無效化、細粒度驅動程式鎖定或其他可能需要的驅動程式鎖定(例如,DMA-resv 鎖)的邏輯。
GPU 頁面錯誤處理程式
int driver_bind_range(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range)
{
int err = 0;
driver_alloc_and_setup_memory_for_bind(gpusvm, range);
drm_gpusvm_notifier_lock(gpusvm);
if (drm_gpusvm_range_pages_valid(range))
driver_commit_bind(gpusvm, range);
else
err = -EAGAIN;
drm_gpusvm_notifier_unlock(gpusvm);
return err;
}
int driver_gpu_fault(struct drm_gpusvm *gpusvm, unsigned long fault_addr,
unsigned long gpuva_start, unsigned long gpuva_end)
{
struct drm_gpusvm_ctx ctx = {};
int err;
driver_svm_lock();
retry:
// Always process UNMAPs first so view of GPU SVM ranges is current
driver_garbage_collector(gpusvm);
range = drm_gpusvm_range_find_or_insert(gpusvm, fault_addr,
gpuva_start, gpuva_end,
&ctx);
if (IS_ERR(range)) {
err = PTR_ERR(range);
goto unlock;
}
if (driver_migration_policy(range)) {
mmap_read_lock(mm);
devmem = driver_alloc_devmem();
err = drm_gpusvm_migrate_to_devmem(gpusvm, range,
devmem_allocation,
&ctx);
mmap_read_unlock(mm);
if (err) // CPU mappings may have changed
goto retry;
}
err = drm_gpusvm_range_get_pages(gpusvm, range, &ctx);
if (err == -EOPNOTSUPP || err == -EFAULT || err == -EPERM) { // CPU mappings changed
if (err == -EOPNOTSUPP)
drm_gpusvm_range_evict(gpusvm, range);
goto retry;
} else if (err) {
goto unlock;
}
err = driver_bind_range(gpusvm, range);
if (err == -EAGAIN) // CPU mappings changed
goto retry
unlock:
driver_svm_unlock();
return err;
}
垃圾回收器
void __driver_garbage_collector(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_range *range)
{
assert_driver_svm_locked(gpusvm);
// Partial unmap, migrate any remaining device memory pages back to RAM
if (range->flags.partial_unmap)
drm_gpusvm_range_evict(gpusvm, range);
driver_unbind_range(range);
drm_gpusvm_range_remove(gpusvm, range);
}
void driver_garbage_collector(struct drm_gpusvm *gpusvm)
{
assert_driver_svm_locked(gpusvm);
for_each_range_in_garbage_collector(gpusvm, range)
__driver_garbage_collector(gpusvm, range);
}
通知器回撥
void driver_invalidation(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_notifier *notifier,
const struct mmu_notifier_range *mmu_range)
{
struct drm_gpusvm_ctx ctx = { .in_notifier = true, };
struct drm_gpusvm_range *range = NULL;
driver_invalidate_device_pages(gpusvm, mmu_range->start, mmu_range->end);
drm_gpusvm_for_each_range(range, notifier, mmu_range->start,
mmu_range->end) {
drm_gpusvm_range_unmap_pages(gpusvm, range, &ctx);
if (mmu_range->event != MMU_NOTIFY_UNMAP)
continue;
drm_gpusvm_range_set_unmapped(range, mmu_range);
driver_garbage_collector_add(gpusvm, range);
}
}
可能的未來設計特性¶
- 併發 GPU 故障
CPU 故障是併發的,因此併發 GPU 故障是有意義的。
透過驅動程式 GPU 故障處理程式中的細粒度鎖定應該是可能的。
不需要預期的 GPU SVM 更改。
- 具有混合系統頁面和裝置頁面的範圍
如果需要,可以相當容易地新增到 drm_gpusvm_get_pages。
- 多 GPU 支援
正在進行中,預計在最初登陸 GPU SVM 後會有補丁。
理想情況下,可以透過對 GPU SVM 進行很少或不進行任何更改來完成。
- 放棄範圍而支援 radix 樹
對於更快的通知器可能是可取的。
- 複合裝置頁面
Nvidia、AMD 和 Intel 都同意,遷移裝置層中昂貴的核心 MM 函式是效能瓶頸,擁有複合裝置頁面應透過減少這些昂貴呼叫的數量來幫助提高效能。
- 用於遷移的更高階 dma 對映
4k dma 對映對 Intel 硬體上的遷移效能產生不利影響,更高階 (2M) dma 對映應該有所幫助。
在 GPU SVM 之上構建通用的 userptr 實現
驅動程式側 madvise 實現和遷移策略
當這些落地時,從 Leon / Nvidia 中提取待處理的 dma-mapping API 更改