5. 图形缓冲区的释放过程
前面提到,用户空间的应用程序用到的图形缓冲区是由Gralloc模块中的函数gralloc_free来释放的,这个函数实现在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
-
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);
-
if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {
-
// free this buffer
-
private_module_t* m = reinterpret_cast<private_module_t*>(
-
dev->common.module);
-
const size_t bufferSize = m->finfo.line_length * m->info.yres;
-
int index = (hnd->base - m->framebuffer->base) / bufferSize;
-
m->bufferMask &= ~(1<<index);
-
} else {
-
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;
-
}
要释放的图形缓冲区使用参数handle来描述。前面提到,从Gralloc模块中分配的图形缓冲区是使用private_handle_t结构体来描述的,因此,这里的参数handle应该指向一个private_handle_t结构体,这是通过调用private_handle_t类的静态成员函数validate来验证的。private_handle_t类的静态成员函数validate的实现可以参考前面第1部分的内容。
要释放的图形缓冲区有可能是在系统帧缓冲区分配的,也有可能是在内存中分配的,这可以通过检查它的标志值flags的PRIV_FLAGS_FRAMEBUFFER位是否等于1来确认。
如果要释放的图形缓冲区是在系统帧缓冲区中分配的,那么首先要知道这个图形缓冲区是系统帧缓冲区的第index个位置,接着再将变量m所描述的一个private_module_t结构体的成员变量bufferMask的第index位重置为0即可。我们只需要将要释放的图形缓冲区的开始地址减去系统帧缓冲区的基地址,再除以一个图形缓冲区的大小,就可以知道要释放的图形缓冲区是系统帧缓冲区的第几个位置。这个过程刚好是在系统帧缓冲区中分配图形缓冲区的逆操作。
如果要释放的图形缓冲区是内存中分配的,那么只需要调用另外一个函数terminateBuffer来解除要释放的图形缓冲区在当前进程的地址空间中的映射。
最后,这个函数还会将用来描述要释放的图形缓冲区的private_handle_t结构体所占用的内存释放掉,并且将要要释放的图形缓冲区所在的系统帧缓冲区或者匿名共享内存的文件描述符关闭掉。
函数terminateBuffer实现在文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
-
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来解除参数hnd所描述的一个图形缓冲区在当前进程的地址空间中的映射。后面在分析图形缓冲区的注销过程时,我们再详细分析函数gralloc_unmap的实现。
至此,图形缓冲区的释放过程就分析完成了,接下来我们继续分析图形缓冲区的注册过程。
6. 图形缓冲区的注册过程
前面提到,在Android系统中,所有的图形缓冲区都是由SurfaceFlinger服务分配的,而当一个图形缓冲区被分配的时候,它会同时被映射到请求分配的进程的地址空间去,即分配的过程同时也包含了注册的过程。但是对用户空间的其它的应用程序来说,它们所需要的图形缓冲区是在由SurfaceFlinger服务分配的,因此,当它们得到SurfaceFlinger服务分配的图形缓冲区之后,还需要将这块图形缓冲区映射到自己的地址空间来,以便可以使用这块图形缓冲区。这个映射的过程即为我们接下来要分析的图形缓冲区注册过程。
前面还提到,注册图形缓冲区的操作是由Gralloc模块中的函数gralloc_register_buffer来实现的,这个函数实现在文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
-
int gralloc_register_buffer(gralloc_module_t const* module,
-
buffer_handle_t handle)
-
{
-
if (private_handle_t::validate(handle) < 0)
-
return -EINVAL;
-
-
// if this handle was created in this process, then we keep it as is.
-
int err = 0;
-
private_handle_t* hnd = (private_handle_t*)handle;
-
if (hnd->pid != getpid()) {
-
void *vaddr;
-
err = gralloc_map(module, handle, &vaddr);
-
}
-
return err;
-
}
这个函数首先验证参数handle指向的一块图形缓冲区的确是由Gralloc模块分配的,方法是调用private_handle_t类的静态成员函数validate来验证,即如果参数handle指向的是一个private_handle_t结构体,那么它所指向的一块图形缓冲区就是由Gralloc模块分配的。
通过了上面的检查之后,函数gralloc_register_buffer还需要检查当前进程是否就是请求Gralloc模块分配图形缓冲区hnd的进程。如果是的话,那么当前进程在请求Gralloc模块分配图形缓冲区hnd的时候,就已经将图形缓冲区hnd映射进自己的地址空间来了,因此,这时候就不需要重复在当前进程中注册这个图形缓冲区。
真正执行注册图形缓冲区的操作是由函数gralloc_map来实现的,这个函数也是实现文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
-
static int gralloc_map(gralloc_module_t const* module,
-
buffer_handle_t handle,
-
void** vaddr)
-
{
-
private_handle_t* hnd = (private_handle_t*)handle;
-
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) {
-
LOGE("Could not mmap %s", strerror(errno));
-
return -errno;
-
}
-
hnd->base = intptr_t(mappedAddress) + hnd->offset;
-
//LOGD("gralloc_map() succeeded fd=%d, off=%d, size=%d, vaddr=%p",
-
// hnd->fd, hnd->offset, hnd->size, mappedAddress);
-
}
-
*vaddr = (void*)hnd->base;
-
return 0;
-
}
由于在系统帧缓冲区中分配的图形缓冲区只在SurfaceFlinger服务中使用,而SurfaceFlinger服务在初始化系统帧缓冲区的时候,已经将系统帧缓冲区映射到自己所在的进程中来了,因此,函数gralloc_map如果发现要注册的图形缓冲区是在系统帧缓冲区分配的时候,那么就不需要再执行映射图形缓冲区的操作了。
如果要注册的图形缓冲区是在内存中分配的,即它的标志值flags的PRIV_FLAGS_FRAMEBUFFER位等于1,那么接下来就需要将它映射到当前进程的地址空间来了。由于要注册的图形缓冲区是在文件描述符hnd->fd所描述的一块匿名共享内存中分配的,因此,我们只需要将文件描述符hnd->fd所描述的一块匿名共享内存映射到当前进程的地址空间来,就可以将参数hnd所描述的一个图形缓冲区映射到当前进程的地址空间来。
由于映射文件描述符hnd->fd得到的是一整块匿名共享内存在当前进程地址空间的基地址,而要注册的图形缓冲区可能只占据这块匿名共享内存的某一小部分,因此,我们还需要将要注册的图形缓冲区的在被映射的匿名共享内存中的偏移量hnd->offset加上被映射的匿名共享内存的基地址hnd->base,才可以得到要注册的图形缓冲区在当前进程中的访问地址,这个地址最终又被写入到hnd->base中去。
注册图形缓冲区的过程就是这么简单,接下来我们再分析图形缓冲区的注销过程。
本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/967092,如需转载请自行联系原作者