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重的客戶端中抽象出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章