• 网站刚刚上线,难免有不足的地方,敬请谅解!欢迎提出宝贵意见!

  •    3年前 (2015-07-10)  专业技术 安卓框架 |   6 条评论  395 
    文章评分 2 次,平均分 5.0

    Gralloc模块是从Android Eclair(android 2.1)开始加入的一个HAL模块,Gralloc的含义为是Graphics Alloc(图形分配)。他对上为libui提供服务,为其分配显存,刷新显示等。对下对framebuffer进行管理。

    gralloc代码通常位于hardware/libhardware/modules/gralloc目录下。包含以下几个文件:

    Android.mk  framebuffer.cpp  gralloc.cpp  gralloc_priv.h  gr.h  mapper.cpp

    另外,与其相关的头文件位于hardware/libhardware/include/hardware,涉及fb.h和gralloc.h。

    下面从gralloc的调用开始学习gralloc的代码。代码基于android4.4。

    gralloc的调用是从FramebufferNativeWindow.cpp的构造函数开始的。FramebufferNativeWindow实现FrameBuffer的管理,它主要被SurfaceFlinger使用,也可以被OpenGL Native程序使用。在本质上,它在Framebuffer之上实现了一个ANativeWindow,目前它只管理两个buffers:front and back buffer。

    如下所示(FramebufferNativeWindow.cpp):

    这里会先根据gralloc的module ID来得到hw_module_t结构。hw_get_module->hw_get_module_by_class。在hw_get_module_by_class里面,首先根据平台配置找到gralloc动态库的位置,默认使用gralloc.default.so。
    参见以下代码(hardware.c):

    找到gralloc库的路径后,会调用load函数,在load函数中使用dlopen打开找到的库,并根据HAL_MODULE_INFO_SYM_AS_STR(其值为HMI)获取到hw_module_t(即HAL_MODULE_INFO_SYM)结构体指针,以及把dlopen返回的handle保存在hw_module_t中。而hw_module_t HMI
    结构是一个全局结构,在gralloc.cpp中已经得到初始化了。这也是为什么每一个HAL模块都要定义并初始化一个名字为HAL_MODULE_INFO_SYM的hw_module_t结构

    回过头,回到FramebufferNativeWindow的构造函数出,接下来调用了err = framebuffer_open(module, &fbDev);framebuffer_open定义在fb.h中,是一个inline函数,其实最终调用了就是上面结构体中初始化的open函数,open函数指向gralloc_device_open,其实现为(gralloc.cpp):

    fb_device_open的定义如下所示(framebuffer.cpp):

    接下来的gralloc_open也是调用了gralloc_device_open,只不过name参数一个是GRALLOC_HARDWARE_GPU0,而另外一个是GRALLOC_HARDWARE_FB0,这两个函数分别得到alloc_device_t 和 framebuffer_device_t结构。到现在为止,gralloc模块的三个主要结构体,gralloc_module_t,alloc_device_t,framebuffer_device_t都已经获取到了。其中在fb_device_open函数中会获取实际的framebuffer设备(通常是/dev/graphics/fb0)的一些重要参数以及能力,比如分辨率信息以及支持多少个缓冲等,另外会把framebuffer映射到内测的地址保存到alloc_module_t中。android一般使用的都是双缓冲机制。具体代码如下(framebuffer.cpp),其中涉及到对private_module_t中一些成员的完善,涉及到gralloc_module_t以及private_handle_t等,其定义在gralloc_priv.h中,这两个结构中都保存了framebuffer的一些私有信息。

    由上面函数看出,mapFrameBufferLocked主要做了下面几件事情:
    1. 打开framebuffer设备

    2. 获取 fb_fix_screeninfo and fb_var_screeninfo

    3. refill fb_var_screeninfo

    4. 判断是否支持PAGE_FLIP

    5. 计算刷新率

    6. 打印gralloc信息

    7. 填充private_module_t

    8. mmap the framebuffer
    看之前的HAL模块比如Camera模块,有一个hw_module_t结构和一个hw_device_t结构,而这里的gralloc模块却包含了两个hw_device_t结构,一个alloc_device_t和一个framebuffer_device_t结构。先看framebuffer_device_t定义:

    framebuffer_device_t(fb.h):

    从这个结构看以看出,framebuffer_device_t里面主要保存了framebuffer相关的一些信息,例如分辨率,刷新率,framebuffer的数量等,另外,里面定义了一些操作framebuffer的函数,一下简单介绍其中几个函数。

    1. static int fb_setSwapInterval(struct framebuffer_device_t* dev, int interval)
    这个函数基本没有用,因为maxSwapInterval=minSwapInterval= 1;
    2. int (*setUpdateRect)(struct framebuffer_device_t* window, int left, int top, int width, int height);
    这个函数是局部刷新用的,默认没有启用。和平台有关。
    3. int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer);
    这个是最关键的函数。用来将图形缓冲区buffer的内容渲染到帧缓冲区中去,即显示在设备的显示屏中去。函数实现如下:

    从fb_post的函数定义可以看出,其实现方式有两种方式,第一种方式是把Framebuffer的后buffer切为前buffer,然后通过IOCTRL机制告诉FB驱动切换DMA源地地址。

    具体原理是这样的:当private_handle_t结构体hnd所描述的图形缓冲区是在系统帧缓冲区中分配的时候,即这个图形缓冲区的标志值flags的PRIV_FLAGS_FRAMEBUFFER位等于1的时候,我们是不需要将图形缓冲区的内容拷贝到系统帧缓冲区去的,因为我们将内容写入到图形缓冲区的时候,已经相当于是将内容写入到了系统帧缓冲区中去了。虽然在这种情况下,我们不需要将图形缓冲区的内容拷贝到系统帧缓冲区去,但是我们需要告诉系统帧缓冲区设备将要渲染的图形缓冲区作为系统当前的输出图形缓冲区,这样才可以将要渲染的图形缓冲区的内容绘制到设备显示屏来。例如,假设系统帧缓冲区有2个图形缓冲区,当前是以第1个图形缓冲区作为输出图形缓冲区的,这时候如果我们需要渲染第2个图形缓冲区,那么就必须告诉系统帧绘冲区设备,将第2个图形缓冲区作为输出图形缓冲区。这个实现方式的前提是Linux内核必须分配至少两个缓冲区大小的物理内存和实现切换的ioctrol,这个比较快速。

    设置系统帧缓冲区的当前输出图形缓冲区是通过IO控制命令FBIOPUT_VSCREENINFO来进行的。IO控制命令FBIOPUT_VSCREENINFO需要一个fb_var_screeninfo结构体作为参数。从前面第3部分的内容可以知道,private_module_t结构体m的成员变量info正好保存在我们所需要的这个fb_var_screeninfo结构体。有了个m->info这个fb_var_screeninfo结构体之后,我们只需要设置好它的成员变量yoffset的值(不用设置成员变量xoffset的值是因为所有的图形缓冲区的宽度是相等的),就可以将要渲染的图形缓冲区设置为系统帧缓冲区的当前输出图形缓冲区。fb_var_screeninfo结构体的成员变量yoffset保存的是当前输出图形缓冲区在整个系统帧缓冲区的纵向偏移量,即Y偏移量。我们只需要将要渲染的图形缓冲区的开始地址hnd->base的值减去系统帧缓冲区的基地址m->framebuffer->base的值,再除以图形缓冲区一行所占据的字节数m->finfo.line_length,就可以得到所需要的Y偏移量。

    在执行IO控制命令FBIOPUT_VSCREENINFO之前,还会将作为参数的fb_var_screeninfo结构体的成员变量activate的值设置FB_ACTIVATE_VBL,表示要等到下一个垂直同步事件出现时,再将当前要渲染的图形缓冲区的内容绘制出来。这样做的目的是避免出现屏幕闪烁,即避免前后两个图形缓冲区的内容各有一部分同时出现屏幕中。

    第二种方式是利用copy的方式来实现,比较耗时。当private_handle_t结构体hnd所描述的图形缓冲区是在内存中分配的时候,即这个图形缓冲区的标志值flags的PRIV_FLAGS_FRAMEBUFFER位等于0的时候,我们就需要将它的内容拷贝到系统帧缓冲区中去了。这个拷贝的工作是通过调用函数memcpy来完成的。在拷贝之前,我们需要三个参数。第一个参数是要渲染的图形缓冲区的起址地址,这个地址保存在参数buffer所指向的一个private_handle_t结构体中。第二个参数是要系统帧缓冲区的基地址,这个地址保存在private_module_t结构体m的成员变量framebuffer所指向的一个private_handle_t结构体中。第三个参数是要拷贝的内容的大小,这个大小就刚好是一个屏幕像素所占据的内存的大小。屏幕高度由m->info.yres来描述,而一行屏幕像素所占用的字节数由m->finfo.line_length来描述,将这两者相乘,就可以得到一个屏幕像素所占据的内存的大小。

    在将一块内存缓冲区的内容拷贝到系统帧缓冲区中去之前,需要对这两块缓冲区进行锁定,以保证在拷贝的过程中,这两块缓冲区的内容不会被修改。这个锁定的工作是由Gralloc模块中的函数gralloc_lock来实现的。从前面第1部分的内容可以知道,Gralloc模块中的函数gralloc_lock的地址正好就保存在private_module_t结构体m的成员变量base所描述的一个gralloc_module_t结构体的成员函数lock中。
    在调用函数gralloc_lock来锁定一块缓冲区之后,还可以通过最后一个输出参数来获得被锁定的缓冲区的开始地址,因此,通过调用函数gralloc_lock来锁定要渲染的图形缓冲区以及系统帧缓冲区,就可以得到前面所需要的第一个和第二个参数。

    将要渲染的图形缓冲区的内容拷贝到系统帧缓冲区之后,就可以解除前面对它们的锁定了,这个解锁的工作是由Gralloc模块中的函数gralloc_unlock来实现的。从前面第1部分的内容可以知道,Gralloc模块中的函数gralloc_unlock的地址正好就保存在private_module_t结构体m的成员变量base所描述的一个gralloc_module_t结构体的成员函数unlock中。

    以上是framebuffer_device_t结构相关的一些内容,其主要作用是渲染图形缓冲区来显示内容。下面在看看alloc_device_t的内容。
    alloc_device_t结构的内容如下:

    从其结构体成员可以看出,其主要作用是为请求者分配图形缓冲区。先看alloc函数的实现(gralloc.cpp):

    参数format用来描述要分配的图形缓冲区的颜色格式,描述一个像素需要几个字节来表示。参数w表示要分配的图形缓冲区所保存的图像的宽度,将它乘以bpp,就可以得到保存一行像素所需要使用的字节数。我们需要将这个字节数对齐到4个字节边界,最后得到一行像素所需要的字节数就保存在变量bpr中。

    参数h表示要分配的图形缓冲区所保存的图像的高度,将它乘以bpr,就可以得到保存整个图像所需要使用的字节数。将变量bpr的值除以变量bpp的值,就得到要分配的图形缓冲区一行包含有多少个像素点,这个结果需要保存在输出参数pStride中,以便可以返回给调用者。

    参数usage用来描述要分配的图形缓冲区的用途。如果是用来在系统帧缓冲区中渲染的,即参数usage的GRALLOC_USAGE_HW_FB位等于1,那么就必须要系统帧缓冲区中分配,否则的话,就在内存中分配。注意,在内存中分配的图形缓冲区,最终是需要拷贝到系统帧缓冲区去的,以便可以将它所描述的图形渲染出来。函数gralloc_alloc_framebuffer用来在系统帧缓冲区中分配图形缓冲区,而函数gralloc_alloc_buffer用来在内存在分配图形缓冲区,接下来我们就来看看这两个函数的实现。

    gralloc_alloc_framebuffer最终调用了gralloc_alloc_framebuffer_locked(gralloc.cpp):

    变量bufferMask用来描述系统帧缓冲区的使用情况,而变量numBuffers用来描述系统帧缓冲区可以划分为多少个图形缓冲区来使用,另外一个变量bufferSize用来描述设备显示屏一屏内容所占用的内存的大小。如果系统帧缓冲区只有一个图形缓冲区大小,即变量numBuffers的值等于1,那么这个图形缓冲区就始终用作系统主图形缓冲区来使用。在这种情况下,我们就不能够在系统帧缓冲区中分配图形缓冲区来给用户空间的应用程序使用,因此,这时候就会转向内存中来分配图形缓冲区,即调用函数gralloc_alloc_buffer来分配图形缓冲区。注意,这时候分配的图形缓冲区的大小为一屏内容的大小,即bufferSize。

    如果bufferMask的值大于等于((1LU<<numBuffers)-1)的值,那么就说明系统帧缓冲区中的图形缓冲区全部都分配出去了,这时候分配图形缓冲区就失败了。例如,假设图形缓冲区的个数为2,那么((1LU<<numBuffers)-1)的值就等于3,即二制制0x11。如果这时候bufferMask的值也等于0x11,那么就表示第一个和第二个图形缓冲区都已经分配出去了。因此,这时候就不能再在系统帧缓冲区中分配图形缓冲区。

    假设此时系统帧缓冲区中尚有空闲的图形缓冲区的,接下来函数就会创建一个private_handle_t结构体hnd来描述这个即将要分配出去的图形缓冲区。注意,这个图形缓冲区的标志值等于PRIV_FLAGS_FRAMEBUFFER,即表示这是一块在系统帧缓冲区中分配的图形缓冲区。

    接下来的for循环从低位到高位检查变量bufferMask的值,并且找到第一个值等于0的位,这样就可以知道在系统帧缓冲区中,第几个图形缓冲区的是空闲的。注意,变量vadrr的值开始的时候指向系统帧缓冲区的基地址,在下面的for循环中,每循环一次它的值都会增加bufferSize。从这里就可以看出,每次从系统帧缓冲区中分配出去的图形缓冲区的大小都是刚好等于显示屏一屏内容大小的。最后分配出去的图形缓冲区的开始地址就保存在前面所创建的private_handle_t结构体hnd的成员变量base中,这样,用户空间的应用程序就可以直接将要渲染的图形内容拷贝到这个地址上去,这就相当于是直接将图形渲染到系统帧缓冲区中去。
    在将private_handle_t结构体hnd返回给调用者之前,还需要设置它的成员变量offset,以便可以知道它所描述的图形缓冲区的起始地址相对于系统帧缓冲区的基地址的偏移量。

    gralloc_alloc_buffer(gralloc.cpp)的实现如下:

    它首先调用函数ashmem_create_region来创建一块匿名共享内存,接着再在这块匿名共享内存上分配一个图形缓冲区。注意,这个图形缓冲区也是使用一个private_handle_t结构体来描述的,不过这个图形缓冲区的标志值等于0,以区别于在系统帧缓冲区中分配的图形缓冲区。其中mapBuffer又把hnd所描述的一个图形缓冲区映射到当前进程的地址空间来。

    以上内容就是alloc_device_t的相关内容。在private_module_t中有一个registerBuffer的函数指针,此函数是干什么的呢?在Android系统中,所有的图形缓冲区都是由SurfaceFlinger服务分配的,而当一个图形缓冲区被分配的时候,它会同时被映射到请求分配的进程的地址空间去,即分配的过程同时也包含了注册的过程。但是对用户空间的其它的应用程序来说,它们所需要的图形缓冲区是在由SurfaceFlinger服务分配的,因此,当它们得到SurfaceFlinger服务分配的图形缓冲区之后,还需要将这块图形缓冲区映射到自己的地址空间来,以便可以使用这块图形缓冲区。这个映射的过程即为我们接下来要分析的图形缓冲区注册过程。

    由于在系统帧缓冲区中分配的图形缓冲区只在SurfaceFlinger服务中使用,而SurfaceFlinger服务在初始化系统帧缓冲区的时候,已经将系统帧缓冲区映射到自己所在的进程中来了,因此,函数gralloc_map如果发现要注册的图形缓冲区是在系统帧缓冲区分配的时候,那么就不需要再执行映射图形缓冲区的操作了。

    如果要注册的图形缓冲区是在内存中分配的,即它的标志值flags的PRIV_FLAGS_FRAMEBUFFER位等于1,那么接下来就需要将它映射到当前进程的地址空间来了。由于要注册的图形缓冲区是在文件描述符hnd->fd所描述的一块匿名共享内存中分配的,因此,我们只需要将文件描述符hnd->fd所描述的一块匿名共享内存映射到当前进程的地址空间来,就可以将参数hnd所描述的一个图形缓冲区映射到当前进程的地址空间来。

    由于映射文件描述符hnd->fd得到的是一整块匿名共享内存在当前进程地址空间的基地址,而要注册的图形缓冲区可能只占据这块匿名共享内存的某一小部分,因此,我们还需要将要注册的图形缓冲区的在被映射的匿名共享内存中的偏移量hnd->offset加上被映射的匿名共享内存的基地址hnd->base,才可以得到要注册的图形缓冲区在当前进程中的访问地址,这个地址最终又被写入到hnd->base中去。

    参考文档:http://blog.csdn.net/luoshengyang/article/details/7747932

    关注微信公众平台:程序员互动联盟(coder_online),你可以第一时间获取原创技术文章,和(java/C/C++/Android/Windows/Linux)技术大牛做朋友,在线交流编程经验,获取编程基础知识,解决编程问题。程序员互动联盟,开发人员自己的家。

    Android GUI系统学习1:Gralloc

     

     

    本文原始地址:http://www.coderonline.net/android-gui-system-1gralloc.html

    本站所有文章,除特别注明外,均为本站原创,转载请注明出处来自http://www.coderonline.net/

    否则保留追究法律责任的权利!

    发表评论

    表情 格式
    1. 好高深的样子,慢慢研究

      angel 评论达人 LV.5 3年前 (2015-07-11) [0] [0]
    2. 真心不懂,不过留着慢慢看 嘿嘿

      qiugc123 评论达人 LV.3 3年前 (2015-07-10) [0] [0]
    3. 实用,不错

      ghost045 评论达人 LV.5 3年前 (2015-07-10) [0] [0]
    4. 好高深呀!

      Coder 评论达人 LV.5 3年前 (2015-07-10) [0] [0]
    5. 总结的不错

      raul 评论达人 LV.5 3年前 (2015-07-10) [0] [0]
    6. 自己顶一下。辛苦了

      麦子熟了 博 主 3年前 (2015-07-10) [0] [0]
    切换注册

    登录

    忘记密码 ?

    切换登录

    注册