Android图形缓冲区分配过程源码分析

时间:2022-01-04 03:54:42

在上一篇文章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语言中的继承方式。

Android图形缓冲区分配过程源码分析

前面介绍了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;
}
Android图形缓冲区分配过程源码分析

内存中分配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中的指定位就可以了。