天天看點

DRM的GEM

GEM(Graphics Execution Manager)主要完成:記憶體申請釋放、指令執行、執行指令時的光圈管理(what?!)。緩存對象的申請主要與linux提供的shmem層相關。裝置相關操作如指令執行、pinning、buffer讀寫、映射、域所有權的轉移等,還是歸裝置驅動的ioctl。

初始化

使用GEM的驅動必須在struct drm_driver->driver_features置DRIVER_GEM位,DRM core将會在執行load操作前自動初始化GEM core,這就意味着會建構一個DRM記憶體管理器,這個DRM記憶體管理器為對象的申請提供位址空間池。

在KMS設定上,如果硬體需要的話,驅動在核心GEM初始化後,需要申請并初始化環形緩沖區,這部分一般不由GEM管理,而是需要獨立的初始化到自己的DRM MM對象。

建立GEM對象

GEM将建立GEM對象和其後的申請記憶體操作分成了兩個不同的操作。GEM對象由struct drm_gem_object表示。驅動一般需要用私有資訊來擴充GEM對象,是以struct drm_gem_object都是嵌入在驅動私有GEM結構體内的。建立一個GEM對象,驅動為自有GEM對象申請記憶體,并通過drm_gem_object_init(struct drm_device* ,struct drm_gem_object *,size_t )來初始化嵌入在其中的struct drm_gem_object。

GEM使用shmem來申請匿名頁記憶體,drm_gem_object_init将會根據傳入的size_t建立一個shmfs,并将這個shmfs file放到struct drm_gem_object的filp區。當圖形硬體使用系統記憶體,這些記憶體就會作為對象的主存儲直接使用,否則就會作為後備記憶體。驅動負責調用shmem_read_mapping_page_gfp()做實際實體頁面的申請,初始化GEM對象時驅動可以決定申請頁面,或者延遲到需要記憶體時再申請(需要記憶體時是指:使用者态通路記憶體發生缺頁中斷,或是驅動需要啟動DMA用到這段記憶體)。

匿名頁面記憶體并不是一定需要的,嵌入式裝置一般需要實體連續系統記憶體,驅動可以建立GEM對象兒沒有shmfs,并調用drm_gem_private_object_init()初始化此私有GEM對象,不過這樣的話,私有GEM對象的存儲管理就需要驅動自行完成。

GEM對象的生命周期

所有GEM對象都有GEM core做引用計數,drm_gem_object_get()加數,drm_gem_object_put()減數,執行drm_gem_object_get()調用者必須持有struct drm_device的struct_mutex鎖,而為了友善也提供了drm_gem_object_put_unlocked()也可以不持鎖操作。當最後一個對GEM對象的引用釋放時,GEM core調用struct drm_driver gem_free_object_unlocked,這一操作對于使能了GEM的驅動來說是必須,并且必須釋放所有相關資源。driver實作接口void (*gem_free_object)(struct drm_gem_object *obj),負責釋放所有GEM對象資源,這其中包括要調用drm_gem_object_release()來釋放GEM core建立的資源。

GEM對象命名

GEM對象有本地handle、全局名稱和檔案描述符,都是32bit數。

對于本地handle:drm_gem_handle_create()建立GEM對象handle,這個函數拿着DRM file的指針和GEM對象,算得一個局部唯一handle,這個handle可以通過drm_gem_handle_delete()來删除,drm_gem_object_lookup()可以由handle找出對應的GEM對象。handle僅是一個對GEM對象的引用,在handle銷毀時減去這一引用。

對于全局名稱:可以在程序之間傳遞,不過在DRM API中,全局名稱不能直接指到GEM對象,通過ioctl的DRM_IOCTL_GEM_FLINK (轉成對象)和DRM_IOCTL_GEM_OPEN(轉回全局名稱),DRM core做此轉換。GEM也支援通過PRIME dma-buf檔案描述符的緩存共享,基于GEM的驅動必須使用提供的輔助函數來實作exoprting和importing。共享檔案描述符比可以被猜測的全局名稱更安全,是以是首選的緩存共享機制。通過GEM全局名稱進行緩存共享僅在傳統使用者态支援。更進一步的說,PRIME由于其基于dma-buf,還允許跨裝置緩存共享。

GEM對象映射

因為映射操作費時,GEM更多使用ioctl将記憶體映射到使用者态,通過讀寫通路緩存;但是當随機通路緩存多的時候,如使用了軟渲染,直接通路GEM對象高效。mmap系統調用不能直接映射GEM對象,因為GEM對象沒有自己的檔案描述符。目前同時存在兩種map GEM對象到使用者态的辦法,一是用驅動自己的ioctl做映射操作,鈎子後面挂上do_mmap(),不鼓勵,比描述了;另一種方法是對DRM檔案描述符使用mmap直接映射GEM對象,雖然GEM對象沒有檔案描述符,但是通過mmap(void*addr,size_t length,int port,int flags,int fd, off_t offset)中的offset參數,DRM可以找出這個GEM對象,為了能識别這個offset,驅動必須先對這個GEM對象執行了drm_gem_create_mmap_offset(),這樣得到的offset值需要先通過驅動指定的方式傳給使用者态程式,然後就可以通過mmap傳下來,找到這個GEM對象。GEM core提供有一個輔助方法drm_gem_mmap()來處理對象映射,這個方法可以直接設定為mmap檔案操作的處理函數,它将會由offset查出GEM對象,然後設定VMA操作到struct drm_driver的gem_vm_ops;注意drm_gem_mmap()不映射記憶體到使用者态,需要依賴于驅動提供的缺頁處理函數做頁面的映射,是以要用drm_gem_mmap(),驅動必須将struct drm_driver的gem_vm_ops填入,也就是填好

struct vm_operations_struct{

    void (*open)(struct vm_area_struct* area);

    void (*close)(struct vm_area_struct* area);

    vm_fault_t (*fault)(struct vm_fault* vamp);

}

其中,open/close需要更新GEM對象的引用計數,驅動可以用drm_gem_vm_open()和drm_gem_vm_close()輔助函數直接作為open/close的處理函數;fault操作的處理函數負責在缺頁發生時映射獨立頁面到使用者态,驅動可以根據記憶體申請方案在缺頁發生時申請頁面,也可以在GEM對象建立時申請。對于沒有MMU的平台,還有其他考慮,但是不予介紹。

記憶體對齊

當一個對象的後備頁面映射到裝置或用于指令緩存,這個頁面就會被刷到記憶體并标記為CPU與GPU都會寫入,比如:GPU渲染到一個對象,渲染完後CPU通路這個對象,那麼這個對象就必須與CPU的記憶體視圖保持一緻,這就需要通過調用GPU緩存外刷等操作。這種CPU-GPU記憶體對齊的管理就由驅動指定的ioctl提供:看看對象目前域,必要的話要做重新整理或同步使對象轉到想要的域,不過對象可能會忙(比如正在被渲染),這時候就會發生阻塞,等待渲染完成,再做重新整理或同步。

指令執行

提供指令執行接口給client可能是GEM最重要的功能,client程式建構指令緩存并送出到GEM,而這個指令緩存會引用到很多已經申請過的記憶體對象;這時候,GEM小心翼翼的綁定所有的對象到GTT,執行這個指令緩存,然後在client們之間提供必要的同步。這個過程中一般還會有扔掉一些對象,以及再綁進一些對象(開銷很大),然後還要提供重映射将GTT的偏移量對client們隐藏起來。client們也要注意送出的指令緩存所引用的記憶體對象不要超過GTT的要求,如果超過則會被GEM拒絕,然後就不會有渲染操作執行了。一般來說,如果指令緩存中的這段渲染操作中的很多對象都需要申請fence寄存器,那麼就要注意不要超過可以提供給client使用的fence寄存器的總數。這些資源的管理都要從libdrm重的用戶端中抽象出來。