Thursday, June 25, 2009

一千零一夜之 GEM Object

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。


GEM object 是一種 buffer,一種可以在 A 程式被配置,在 B 程式被使用的 buffer。當 userspace 產生一個 GEM object 時,以 i915 為例,它會呼叫 drm_gem_object_alloc。從這個函數的頭幾行

struct drm_gem_object *obj;

BUG_ON((size & (PAGE_SIZE - 1)) != 0);

obj = kcalloc(1, sizeof(*obj), GFP_KERNEL);

obj->dev = dev;
obj->filp = shmem_file_setup("drm mm object", size, VM_NORESERVE);
if (IS_ERR(obj->filp)) {
kfree(obj);
return NULL;
}


可以看出,它只是一塊 shared memory。就這個方式來看,GEM object 其實不是新的東西。

但這塊 buffer 除了 A、B 程式會用到外,它還另外有一個使用者,這個使用者是 GPU。之所以把 shared memory 包裝成 GEM object 最主要的原因是,kernel 需要同步 CPU 和 GPU 對這塊 buffer 的存取。很明顯地,如果讓 CPU 與 GPU 同時寫入同一塊記憶體,出來的結果一定無法預期。在 GEM object 裡,處理同步的方法是引入 domain 的機制。所有人在存取 GEM object 前,必須先把它的 domain 設好。如果是 GPU 要讀取或寫入,那就需要先把 read domain 或 write domain 設成 GPU;相對的,如果是 CPU 要讀取或寫入,則需要把 read/write domain 設成 CPU。DRM 保證的是,只要正確地指定 domain,存取到的資料也會是正確的。

為了解 domain 跟同步的關係,我們可以看一下當我們把 read domain 指定為 CPU 時,kernel 會幫我們做什麼處理。這部份完整的程式碼可以在 i915_gem_object_set_to_cpu_domain 看到。

首先,kernel 要確定之前所下的 GPU 指令已經全部執行完畢,而且 GPU cache 要 flush 掉

i915_gem_object_flush_gpu_write_domain(obj);
ret = i915_gem_object_wait_rendering(obj);


GPU 的運作方式是會依序執行某個指定的 ring buffer 裡面的指令,而開發者要做的是把指令放到這個 buffer。為了達到目的,kernel 會在 ring buffer 最後加上 flush 跟 interrupt 指令,並且開始等待中斷。我們可以想像,當 kernel 收到中斷時,也就表示之前的指令都已經執行,包含它自己加上的 flush 指令。

事實上,CPU 除了直接存取 shared memory 外,也可以從 AGP aperture 去存取,這在 i915 裡稱做 GTT domain。所以 kernel 也要確定這邊沒有資料被 cache 住

i915_gem_object_flush_gtt_write_domain(obj);


這些動作可能造成記憶體上的資料被改變,而,這時候 CPU 還不知道這件事。所以只要再確保 CPU 可以知道這些變動

i915_gem_object_set_to_full_cpu_read_domain(obj);
if ((obj->read_domains & I915_GEM_DOMAIN_CPU) == 0) {
i915_gem_clflush_object(obj);
obj->read_domains |= I915_GEM_DOMAIN_CPU;
}


我們就可以說 GEM object 已經被移到 CPU read domain。

GEM object 是 shared memory 的特性,讓 X server 可以直接把 buffer 傳給 client 做 DRI。它同時也可以當做到 OpenGL 裡頭各種 buffer object 的 storage。除了 Intel 外,在 2.6.31 也可以看到初步的 ATI Radeon KMS 支援。雖然從使用者的經驗來看,似乎只是進入另一個黑暗時期。但是在領袖的帶領下,我們好像也可以看到光了!

No comments: