在上一篇文章Android圖形顯示之硬體抽象層Gralloc介紹了Gralloc模組的定義,Gralloc模組中的fb裝置的開啟過程及gpu裝置的開啟過程。關於Gralloc模組的載入過程在Android硬體抽象Hardware庫載入過程源碼分析中已經詳細介紹過了,通過Android圖形顯示之硬體抽象層Gralloc的介紹,相信大家對Gralloc模組的設計有了大概的瞭解,本文在前文的基礎上繼續分析Android系統的圖形顯示內容。我們知道在Gralloc模組中定義了gpu裝置來分配圖形緩衝區,同時在上一篇文章中已經介紹了gpu裝置的開啟過程,在開啟gpu裝置時,註冊了圖形緩衝區的分配和釋放函數,本文針對圖形緩衝區的分配和釋放過程展開分析。Android為上層定義了一個工具類GraphicBufferAllocator,該類用來訪問Gralloc硬體抽象模組中的gpu裝置alloc_device_t,專門負責圖形緩衝區的分配與回收。
GraphicBufferAllocator::GraphicBufferAllocator() : mAllocDev(0){ hw_module_t const* module;//根據模組ID得到模組描述符的首地址 int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module); ALOGE_IF(err, "FATAL: can't find the %s module", GRALLOC_HARDWARE_MODULE_ID); if (err == 0) {//調用該硬體抽象層的open方法,建立hw_device_t對象,並且將hw_module_t註冊到hw_device_t中 gralloc_open(module, &mAllocDev); }}
hw_get_module函數用於載入硬體抽象模組,這部分內容請參考Android硬體抽象Hardware庫載入過程源碼分析。gralloc_open函數用於開啟gpu裝置,在Android圖形顯示之硬體抽象層Gralloc中已經有詳細的介紹。
static inline int gralloc_open(const struct hw_module_t* module, struct alloc_device_t** device) {//gralloc_device_open return module->methods->open(module, GRALLOC_HARDWARE_GPU0, (struct hw_device_t**)device);}
hw_module_t用於描述硬體抽象層模組,hw_device_t用於描述硬體裝置
int gralloc_device_open(const hw_module_t* module, const char* name,hw_device_t** device){ int status = -EINVAL;//開啟名為gpu0的模組 if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) { gralloc_context_t *dev; dev = (gralloc_context_t*)malloc(sizeof(*dev)); /* initialize our state here */ memset(dev, 0, sizeof(*dev)); /* initialize the procs */ dev->device.common.tag = HARDWARE_DEVICE_TAG; dev->device.common.version = 0; dev->device.common.module = const_cast<hw_module_t*>(module); dev->device.common.close = gralloc_close; dev->device.alloc = gralloc_alloc; dev->device.free = gralloc_free; *device = &dev->device.common; status = 0; } return status;}
通過開啟Gralloc模組中的gpu裝置即可得到描述gpu裝置的資料結構gralloc_context_t,並儲存在GraphicBufferAllocator類的成員變數mAllocDev中,而mAllocDev的類型為alloc_device_t,由於gralloc_context_t的第一個域變數device的類型為alloc_device_t,因此指向gralloc_context_t首地址的指標也指向alloc_device_t的首地址,這裡使用了C語言中的繼承方式。
前面介紹了Gralloc模組中的gpu裝置用於分配圖形緩衝區,GraphicBufferAllocator開啟gpu裝置後,就通過該gpu裝置來分配圖形緩衝區,因此GraphicBufferAllocator為上層提供訪問gpu裝置的介面。
GraphicBufferAllocator::alloc() -->mAllocDev->alloc() -->alloc_device_t->alloc()-->gralloc_alloc() 分配圖形緩衝區
GraphicBufferAllocator::free() -->mAllocDev->free() -->alloc_device_t->free()-->gralloc_free() 釋放圖形緩衝區
圖形緩衝區分配過程
status_t GraphicBufferAllocator::alloc(uint32_t w, uint32_t h, PixelFormat format, int usage, buffer_handle_t* handle, int32_t* stride){ ATRACE_CALL(); // make sure to not allocate a N x 0 or 0 x N buffer, since this is // allowed from an API stand-point allocate a 1x1 buffer instead. if (!w || !h) w = h = 1; status_t err; //調用alloc_device_t中的alloc函數來分配圖形緩衝區 err = mAllocDev->alloc(mAllocDev, w, h, format, usage, handle, stride);//圖形緩衝區分配成功,將該圖形緩衝區buffer_handle_t以鍵值對的形式儲存到成員變數sAllocList表中,用於dump出系統分配的所有圖形緩衝區資訊。 if (err == NO_ERROR) { Mutex::Autolock _l(sLock); KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList); int bpp = bytesPerPixel(format); if (bpp < 0) { // probably a HAL custom format. in any case, we don't know // what its pixel size is. bpp = 0; } alloc_rec_t rec; rec.w = w; rec.h = h; rec.s = *stride; rec.format = format; rec.usage = usage; rec.size = h * stride[0] * bpp; list.add(*handle, rec); } return err;}
這裡直接調用alloc_device_t中的alloc函數來分配圖形緩衝區,在開啟gpu裝置時,已經為alloc_device_t註冊了圖形緩衝區分配函數:dev->device.alloc = gralloc_alloc;因此函數gralloc_alloc將被調用。
hardware\libhardware\modules\gralloc\gralloc.cpp
static int gralloc_alloc(alloc_device_t* dev,//gpu裝置描述符 int w, //映像寬度int h, //映像高度int format, //圖形格式int usage, //圖形緩衝區的使用類型 buffer_handle_t* pHandle, //即將被分配的圖形緩衝區int* pStride)//分配的圖形緩衝區一行包含有多少個像素點{ if (!pHandle || !pStride) return -EINVAL; size_t size, stride; int align = 4; int bpp = 0; switch (format) { case HAL_PIXEL_FORMAT_RGBA_8888: case HAL_PIXEL_FORMAT_RGBX_8888: case HAL_PIXEL_FORMAT_BGRA_8888://一個像素需要使用32位來表示,即4個位元組 bpp = 4; break; case HAL_PIXEL_FORMAT_RGB_888://一個像素需要使用24位來描述,即3個位元組 bpp = 3; break; case HAL_PIXEL_FORMAT_RGB_565: case HAL_PIXEL_FORMAT_RGBA_5551: case HAL_PIXEL_FORMAT_RGBA_4444://一個像需要使用16位來描述,即2個位元組 bpp = 2; break; default: return -EINVAL; }//w表示要分配的圖形緩衝區所儲存的映像的寬度,w*bpp就可以得到儲存一行像素所需要使用的位元組數,並且對齊到4個位元組邊界 size_t bpr = (w*bpp + (align-1)) & ~(align-1);//h表示要分配的圖形緩衝區所儲存的映像的高度,bpr* h就可以得到儲存整個映像所需要使用的位元組數 size = bpr * h;//要分配的圖形緩衝區一行包含有多少個像素點 stride = bpr / bpp; int err;//要分配的圖形緩衝區一行包含有多少個像素點 if (usage & GRALLOC_USAGE_HW_FB) {//系統框架緩衝區中分配圖形緩衝區 err = gralloc_alloc_framebuffer(dev, size, usage, pHandle); } else {//從記憶體中分配圖形緩衝區 err = gralloc_alloc_buffer(dev, size, usage, pHandle); } if (err < 0) { return err; } *pStride = stride; return 0;}
根據請求分配的圖形緩衝區的用途來選擇不同的分配方式,圖形緩衝區可以從系統框架緩衝區中分配,也可以直接從記憶體中分配,當請求分配的圖形緩衝區的用途標誌位被設定為GRALLOC_USAGE_HW_FB,表示該圖形緩衝區從FrameBuffer系統框架緩衝區中分配。
系統框架緩衝區分配Buffer
gralloc_alloc_framebuffer函數用於從FrameBuffer中分配圖形緩衝區。
static int gralloc_alloc_framebuffer(alloc_device_t* dev, size_t size, int usage, buffer_handle_t* pHandle){ private_module_t* m = reinterpret_cast<private_module_t*>(dev->common.module); pthread_mutex_lock(&m->lock); int err = gralloc_alloc_framebuffer_locked(dev, size, usage, pHandle); pthread_mutex_unlock(&m->lock); return err;}
為了保證分配過程中安全執行緒,這裡使用了private_module_t中的lock來同步多線程。
static int gralloc_alloc_framebuffer_locked(alloc_device_t* dev, size_t size, int usage, buffer_handle_t* pHandle){//根據alloc_device_t尋找到系統框架緩衝區描述體 private_module_t* m = reinterpret_cast<private_module_t*>(dev->common.module); /** *從系統框架緩衝區分配圖形緩衝區之前,首先要對系統框架緩衝區進行過初始化。mapFrameBufferLocked函數用於系統框架緩衝區的初始化,在系統框架緩衝區初始化時,用private_ha*ndle_t來描述整個系統框架緩衝區資訊,並保持到private_module_t的成員framebuffer中,如果該成員變數framebuffer為空白,說明系統框架緩衝區還為被初始化。**/ if (m->framebuffer == NULL) {//系統框架緩衝區還未初始化//初始化系統框架緩衝區,映射到當前進程的虛擬位址空間來 int err = mapFrameBufferLocked(m); if (err < 0) { return err; } }//得到系統框架緩衝區的使用方式 const uint32_t bufferMask = m->bufferMask;//系統框架緩衝區可以劃分為多少個圖形緩衝區來使用 const uint32_t numBuffers = m->numBuffers;//裝置顯示屏一屏內容所佔用的記憶體的大小 const size_t bufferSize = m->finfo.line_length * m->info.yres;//如果系統框架緩衝區只有一個圖形緩衝區大小,即變數numBuffers的值等於1,那麼這個圖形緩衝區就始終用作系統主圖形緩衝區來使用 if (numBuffers == 1) {//不能夠在系統框架緩衝區中分配圖形緩衝區,從記憶體中來分配圖形緩衝區 int newUsage = (usage & ~GRALLOC_USAGE_HW_FB) | GRALLOC_USAGE_HW_2D; return gralloc_alloc_buffer(dev, bufferSize, newUsage, pHandle); }//系統框架緩衝區中的圖形緩衝區全部都分配出去了 if (bufferMask >= ((1LU<<numBuffers)-1)) { return -ENOMEM; } //指向系統框架緩衝區的基地址 intptr_t vaddr = intptr_t(m->framebuffer->base);//建立一個private_handle_t結構體hnd來描述這個即將要分配出去的圖形緩衝區 private_handle_t* hnd = new private_handle_t(dup(m->framebuffer->fd), size,private_handle_t::PRIV_FLAGS_FRAMEBUFFER); //從bufferMask中尋找閒置圖形緩衝區 for (uint32_t i=0 ; i<numBuffers ; i++) { if ((bufferMask & (1LU<<i)) == 0) { m->bufferMask |= (1LU<<i); break; }//每次從系統框架緩衝區中分配出去的圖形緩衝區的大小都是剛好等於顯示屏一屏內容大小的 vaddr += bufferSize; }//分配出去的圖形緩衝區的開始地址儲存在建立的private_handle_t結構體hnd的成員變數base中 hnd->base = vaddr;//分配出去的圖形緩衝區的起始地址相對於系統框架緩衝區的基地址的位移量 hnd->offset = vaddr - intptr_t(m->framebuffer->base); *pHandle = hnd; return 0;}
記憶體中分配Buffer
static int gralloc_alloc_buffer(alloc_device_t* dev,size_t size, int usage, buffer_handle_t* pHandle){ int err = 0; int fd = -1;//分配的圖形緩衝區大小頁對齊 size = roundUpToPageSize(size); //建立一塊指定大小的匿名共用記憶體地區 fd = ashmem_create_region("gralloc-buffer", size); if (fd < 0) { ALOGE("couldn't create ashmem (%s)", strerror(-errno)); err = -errno; } if (err == 0) {//建立一個private_handle_t結構體hnd來描述這個即將要分配出去的圖形緩衝區 private_handle_t* hnd = new private_handle_t(fd, size, 0); gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(dev->common.module);//映射分配的圖形緩衝區 err = mapBuffer(module, hnd); if (err == 0) { *pHandle = hnd; } } ALOGE_IF(err, "gralloc failed err=%s", strerror(-err)); return err;}
該函數首先建立一塊名為"gralloc-buffer"的匿名共用記憶體,並根據匿名共用記憶體的資訊來構造一個private_handle_t對象,該對象用來描述分配的圖形緩衝區,然後將建立的匿名共用記憶體映射到當前進程的虛擬位址空間。在系統框架緩衝區分配圖形buffer時並沒有執行地址空間映射,而這裡卻需要執行映射過程,為什麼呢?這是因為在執行mapFrameBufferLocked函數初始化系統框架緩衝區時,已經將系統的整個框架緩衝區映射到當前進程地址空間中了,在系統框架緩衝區中分配buffer就不在需要重複映射了,而在記憶體中分配buffer就不一樣,記憶體中分配的buffer是一塊匿名共用記憶體,該匿名共用記憶體並沒有映射到當前進程地址空間,因此這裡就需要完成這一映射過程,從而為以後應用程式進程直接存取這塊buffer做好準備工作。
int mapBuffer(gralloc_module_t const* module,private_handle_t* hnd){ void* vaddr; return gralloc_map(module, hnd, &vaddr);}
gralloc_map將參數hnd所描述的一個圖形緩衝區映射到當前進程的地址空間來。
static int gralloc_map(gralloc_module_t const* module,buffer_handle_t handle,void** vaddr){ private_handle_t* hnd = (private_handle_t*)handle;//如果當前buffer不是從系統框架緩衝區中分配的 if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) { size_t size = hnd->size; void* mappedAddress = mmap(0, size,PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0); if (mappedAddress == MAP_FAILED) { ALOGE("Could not mmap %s", strerror(errno)); return -errno; } hnd->base = intptr_t(mappedAddress) + hnd->offset; } *vaddr = (void*)hnd->base; return 0;}
在初始化系統框架緩衝區的時候,已經將系統框架緩衝區映射到進程地址空間了,因此如果發現要註冊的圖形緩衝區是在系統框架緩衝區分配的,那麼就不需要再執行映射圖形緩衝區的操作了。
圖形緩衝區釋放過程
status_t GraphicBufferAllocator::free(buffer_handle_t handle){ ATRACE_CALL(); status_t err;//調用alloc_device_t中的free函數來釋放圖形緩衝區 err = mAllocDev->free(mAllocDev, handle); ALOGW_IF(err, "free(...) failed %d (%s)", err, strerror(-err)); if (err == NO_ERROR) { Mutex::Autolock _l(sLock); KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList); list.removeItem(handle); } return err;}
GraphicBufferAllocator類的釋放圖形buffer
static int gralloc_free(alloc_device_t* dev, buffer_handle_t handle){ if (private_handle_t::validate(handle) < 0) return -EINVAL; private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(handle);//如果當前釋放的buffer是從系統框架緩衝區中分配的 if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) { // alloc_device_t -->hw_device_t -->hw_module_t -->private_module_t private_module_t* m = reinterpret_cast<private_module_t*>(dev->common.module);//計算顯示屏一屏內容所佔用的記憶體的大小 const size_t bufferSize = m->finfo.line_length * m->info.yres;//計算當前釋放的buffer在系統框架緩衝區中的索引,系統框架緩衝區被劃分為bufferSize大小的塊 int index = (hnd->base - m->framebuffer->base) / bufferSize; m->bufferMask &= ~(1<<index); } else { //當前釋放的buffer是從系統記憶體中分配的// alloc_device_t -->hw_device_t -->hw_module_t -->gralloc_module_t gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(dev->common.module);// terminateBuffer(module, const_cast<private_handle_t*>(hnd)); } close(hnd->fd); delete hnd; return 0;}
該函數同樣分別處理系統框架緩衝區中分配的buffer及記憶體中分配的buffer,對於系統框架緩衝區中分配的buffer釋放過程僅僅是清空bufferMask中的指定位。而對於記憶體中分配的buffer,則調用函數terminateBuffer來釋放。
int terminateBuffer(gralloc_module_t const* module,private_handle_t* hnd){ if (hnd->base) { // this buffer was mapped, unmap it now gralloc_unmap(module, hnd); } return 0;}
該函數調用gralloc_unmap來取消映射buffer到當前進程地址空間
static int gralloc_unmap(gralloc_module_t const* module,buffer_handle_t handle){ private_handle_t* hnd = (private_handle_t*)handle; if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) { void* base = (void*)hnd->base; size_t size = hnd->size; if (munmap(base, size) < 0) { ALOGE("Could not unmap %s", strerror(errno)); } } hnd->base = 0; return 0;}
對於記憶體中分配的圖形緩衝區buffer的釋放,首先是將圖形緩衝區buffer從當前進程地址空間中取消映射,然後才調用close(fd)來釋放這塊buffer,由於在記憶體中分配的buffer是一塊匿名共用記憶體,因此當調用close(fd)時,匿名共用記憶體驅動函數ashmem_release自動被調用,該函數就是用來回收建立的匿名共用記憶體。對於從系統框架緩衝區分配的buffer,只需要清空bufferMask中的指定位就可以了。