热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入解析Android4.4中的Fence机制及其应用

在Android4.4中,Fence机制是处理缓冲区交换和同步问题的关键技术。该机制广泛应用于生产者-消费者模式中,确保了不同组件之间高效、安全的数据传输。通过深入解析Fence机制的工作原理和应用场景,本文探讨了其在系统性能优化和资源管理中的重要作用。

Android4.4 fence机制分析

 在任何一个系统中,无可避免的都会跟各种buffers打交道,最经典的模式就是消费-生产者模式,一个独立的buffer在它们之间的交换等操作都需要一个机制来控制每个buffer的“生命周期”,即ALLOCATION RELEASE ,此外还要考虑到同步性问题,什么时候可以read bufferwrite buffer都需要听从调遣。

  android中的fence就是这样一个为了解决同步性而出现的机制。首先从fence的语义角度来分析一下它的基本原理:

Fence即栅栏,栅栏的角色与它的名字非常类似.一组线程可以使用栅栏来集体进行相互同步;在本质上,每个线程在到达某种周知的状态时调用栅栏的wait()方法,阻塞起来,以等待其它所有参与线程调用wait()方法表明它们也到达了这个状态.一旦所有的线程都到达栅栏,它们就会集体解除阻塞,并一起继续执行;引起程序调用栅栏的wait()方法进行阻塞的那个状态叫做栅栏状态。

接下来分析fenceandroid中的应用,这里主要涉及SurfaceFlinger中绘制buffer及显示中的相关方面。

确切的说fenceproducerconsumerbuffer处理的过程中是如何协调他们同步的工作,从而保证buffer内容的准确性,而不会被篡改。

   首先我们知道一个buffer有以下几种状态:

FREE->DEQUEUED->QUEUED->ACQUIRED-FREE

  FREE状态时,producer就可以申请他了吗?答案是错的,他需要等一个signal,也就是NO_FENCE这个信号,因为有可能上一次申请的buffer正在被consumer作业中,所以要等待consumer发出finish的信号,而此时FREE状态下的buffer就好像被栅栏拦住了,这里是用Fencewait()或者waitForever()方法,等一个NO_FENCCE信号,栅栏就会打开。进入到下一流程。

   DEQUEUED是指producer已经申请了一个buffer从队列中出来了,还没有入队列或者取消buffer,这个状态下的bufferproducer想对其进行修改也就是填入UI数据时,必须等一个NO_FENCE信号,因为有可能其他owner正在对它进行操作。当信号一到,poducer就可以对其进行操作,操作完成后发出一个NO_FENCE信号。

   QUEUED状态下,也就是把buffer入队列,不过在这个操作前需要等一个NO_FENCE信号,就比如上一步dequeueBuffer完成之后发的NO_FENCE.收到信号后才进行入队列操作或者取消buffer操作。这个时候它的owner就变成BufferQueue了。

   ACQUIRED状态也就是producer已经对buffer填充完毕,与前面一样它也要等到一个NO_FENCE信号,然后consumer才能对其进行操作。操作完成后会释放buffer,然后发出一个NO_FENCE 信号。

 

所有的fence都是在kernel层实现的,androidHAL层只是把底层的一些接口的封装及扩展。

Surfaceflinger在绘制surface过程主要是以下流程:

Surfaceflinger将计算好的layers交由给HWC,HWC根据具体情况选择对应的绘制路径。

因为openGL实现代码没有开源,所以也就不知道openGL那边对fence是如何的应用了,所以从hwcomposer入手,其实到最后发现机制是一样的,只是看不到它实现的部分罢了。

Fence到底是怎么应用的呢,它和buffer是不相关的,不能把fence看成buffer的一部分,简单说它就是一个允不允许的问题。

这里我先从大方面分析一下我对fence机制流程的理解,首先fence有好几类,它们有不同的作用,但几乎都是成对存在的。这里分析一下acquireFence 和 releaseFence,还有retire fence。

每一个layer都有一个acquire 和release fence,每一个系列layes都有一个retirefence,注意这边的是layers!多个layer。

 

acquireFence:

    禁止显示一个buffer的内容直到该fence被触发,而它是在H/W 被set up 前被发送的。

releaseFence:

     这个意味着属于这个layer的buffer已经不在被读取了,在一个buffer不在被读取的时候将会触发这个fence。

Retire fence:

     这个 scene或者 一系列的layers不再被显示到显示器上,当完成了一个frame的显示后触发这个fence。

到这里可以知道acquireFence, releaseFence是属于单个layer的,而Retire fence是属于多个layer即一个scene.那么在layer和layers对应的结构体必定有它们的影子:

在hardware/libhardware/include/hardware/hwcomposer.h中:

typedef struct hwc_layer_1 {

     ………

  int acquireFenceFd;

 

  int releaseFenceFd;

       ………

 

} hwc_layer_1_t;

可知在定义的一个layer中它们分别是两个整型变量,变量后都以Fd结尾,可想而知这将描述一个文件描述符。

同样:

typedef struct hwc_display_contents_1 {

  …………

  int retireFenceFd;

} hwc_display_contents_1_t;

 

 

介绍完上面的各种fence之后(当然还有其他种类的fence),我用一张图来描述下fence应用的机制:

  分析到这里都是从宏观上分析fence的,大概对fence机制框架有个清楚的认识,接下来看他到底是怎么实现的。

  之前说fence实现都是在kernel层,其实观其HAL层代码,Fence::wait() and waitForever(),mege()都是对kernel层的封装。Kernel层的fence相对来说比较复杂些,毕竟是实现原理,但是究其本质fence其实就是一个文件描述符,这也响应了linux中一切皆文件的说法。

  在kernel层有三个跟fence有关的结构体:

Sync_timeline , sync_pt , sync_fence.下面简单说一下它们的作用和定义:

Sync_timeline:

  顾名思义,是个时间轴,每个流程都有自己的timeline,代表着一个自动增加的计数器。

用图形形象的来描述它如下:

Sync_pt:

  其实就是sync point,同步点的概念,代表timeline上的一个特别的值。它有三种状态:active signalederror。

 

Sync_fence

  它是一系列sync_pt的集合,实际上是个文件描述符可以被传到用户空间,也就是这一个特性,让hal层fence和kernel扯上联系。

  

 上面就是这三个结构体的基本介绍,还有跟fence相关的API这里就不详细介绍,后面分析LCD时在细究。

 

  一开始就给出SF合成图像到显示的两个流程,这里重点分析hwc这条路径:

因为android一旦启动后,绘制图像就是一个循环的状态,所以为了方便研究,从android系统开机动画开始:

     第一步就是客户端请求一个buffer(这里暂不说成app),因为是刚开始所以一切的fence都属于初始化状态或者还没被创造,(从理论上来讲这个时候一切都是空闲的,无论是buffer还是其他什么的,所以我按照这种假设模式继续下去分析,事实是怎样有待考究)因此第一次dequeue一个Buffer的时候就不需要等待display来触发fence了,也不会担心SF是不是在对这个buffer进行计算合成,就这样一步步走向SF计算合成前,开始准备分派hwc渲染的时候,第一次对acquireFenceFd 和 releaseFenceFd还有retireFenceFd进行初始化,在setUpHWComposer中的createWorkList完成的:

 关键代结构码如下

其中hwc_layer_1 framebufferTarget;

    hwc_display_contents_1 list;

For(;dpy

{

  For(;numLayers;)

     disp.framebufferTarget->acquireFenceFd =-1;

     disp.framebufferTarget->releaseFenceFd= -1;

}

disp.list->retireFenceFd = -1;

}

 这样的初始化印证了之前所说的acq,rel分别对应每个layer,而retire对应的是layers。

 Set up之后,开始进行计算合成。最后走到postFramebuffer中的HWComposer::commit()---》set(…)---》hwc_set()

 在hwc_set中完成了渲染工作,然后通过ioctl交给了fb去显示,这里贴出hwc_set中:

一直运行到hwc_sync 会堵塞在这个函数中的wait里:

voidhwc_sync(hwc_display_contents_1_t  *list)

{

 for (int i=0; inumHwLayers; i++)

 {

     if(list->hwLayers[i].acquireFenceFd>0)

     {

 sync_wait(list->hwLayers[i].acquireFenceFd,500);       ALOGV("fenceFd=%d,name=%s",list->hwLayers[i].acquireFenceFd,list->hwLayers[i].LayerName);

     }

 }

}

由上面的红色代码行可知他在等acquireFence这个信号。

 

if (layer->acquireFenceFd>0)

{

    g_sync.acq_fence_fd[k] =layer->acquireFenceFd;

}

ioctl(context->fbFd,RK_FBIOSET_CONFIG_DONE, &g_sync);

list->hwLayers[0].releaseFenceFd= g_sync.rel_fence_fd[0];

list->hwLayers[1].releaseFenceFd= g_sync.rel_fence_fd[1];

//list->retireFenceFd =g_sync.ret_fence_fd;

close(g_sync.ret_fence_fd);   

list->retireFenceFd = -1;

  首先这里有两个数组  acq_fence_fd和rel_fence_fd看名字就能猜出这是存放对应两个fencefd,第一步是把之前初始化的每个layeracqFenfd保存到数组中,接着display就开始显示了,ioctl映射到内核中fd驱动程序的ioctl

接下来分析fb驱动中跟fence相关的代码:

 

首先定义了跟fence相关的一些变量:

struct sync_fence *release_fence;

         structsync_fence *retire_fence;

 

         structsync_pt *release_sync_pt;

         structsync_pt *retire_sync_pt;

 

         structsync_fence *layer2_fence;

         structsync_pt *layer2_pt;

 

其中fence有三类 releaseretire layer2

接着寻找没有被用过的fd保存到rel_fence_fd中:

 

dev_drv->win_data.rel_fence_fd[0]=  get_unused_fd();

dev_drv->win_data.rel_fence_fd[1]=  get_unused_fd();

 

然后开始创建fence

release_sync_pt= sw_sync_pt_create(dev_drv->timeline, dev_drv->timeline_max);

                                      release_fence= sync_fence_create("rel_fence", release_sync_pt);

                                      sync_fence_install(release_fence,dev_drv->win_data.rel_fence_fd[0]);

 

                                      layer2_pt= sw_sync_pt_create(dev_drv->timeline, dev_drv->timeline_max);

                                      layer2_fence=sync_fence_create("rel2_fence", layer2_pt);

                                      sync_fence_install(layer2_fence,dev_drv->win_data.rel_fence_fd[1]);

 

                                      retire_sync_pt= sw_sync_pt_create(dev_drv->timeline, dev_drv->timeline_max);

                                      retire_fence= sync_fence_create("ret_fence", retire_sync_pt);

                                      sync_fence_install(retire_fence,dev_drv->win_data.ret_fence_fd);

 

创建过程这里省略掉,fence在这里被创建完之后就阻塞触发了(等待一个条件:当buffer被显示后马上触发),触发的函数在sync_fence_create中的sync_fence_signal_pt(pt);在这里是一整个过程fence第一次被触发。

触发的是releaseFence retiredfence,接着往下走:

程序下一步会运行:

if (dev_drv->wait_fs == 1) { //wait for new frame start in kernel

                                               rk_fb_update_reg(dev_drv,regs);

                                               kfree(regs);

                                               mutex_unlock(&dev_drv->update_regs_list_lock);

                                      }

接着看rk_fb_update_reg(dev_drv,regs)中的关键代码:

         sw_sync_timeline_inc(dev_drv->timeline,1);

         if(dev_drv->win_data.acq_fence_fd[0]>= 0)

         {

                   for(i=0;i

                            if(dev_drv->win_data.acq_fence_fd[i]> 0){

                                     put_unused_fd(dev_drv->win_data.acq_fence_fd[i]);

                                     printk("acq_fd=%d\n",dev_drv->win_data.acq_fence_fd[i]);

                            }       

                            rk_fb_free_dma_buf(®s->dma_buf_data[i]);

                   }

         }

核心功能大概就是让之前保存在acq_fence_fd数组中的fd无效,看似简单的一个操作,好像对acqFenceFd只是单纯的赋值为-1,但是从源代码中定义acqFenceFd的说明:

  /*Sync fence object that will be signaled when the buffer's

            * contents are available. May be -1 if the contents are already

            * available.*/

上面是源代码中的解释,由此可以看出当fd-1acqFenceFd会被触发。

 

当程序运行到这里的时候,由于只是当中的一个线程,所以前面客户端请求buffer的操作早已经开始了,而且已经在等待相关的fence了。触发了releasefence之后用户那边收到之后就开始dequeue一个buffer进行填充surface了。

 

用一张图来表示下这个过程:


推荐阅读
  • Linux环境下进程间通信:深入解析信号机制
    本文详细探讨了Linux系统中信号的生命周期,从信号生成到处理函数执行完毕的全过程,并介绍了信号编程中的注意事项和常见应用实例。通过分析信号在进程中的注册、注销及处理过程,帮助读者理解如何高效利用信号进行进程间通信。 ... [详细]
  • 在编译BSP包过程中,遇到了一个与 'gets' 函数相关的编译错误。该问题通常发生在较新的编译环境中,由于 'gets' 函数已被弃用并视为安全漏洞。本文将详细介绍如何通过修改源代码和配置文件来解决这一问题。 ... [详细]
  • 本文介绍如何利用栈数据结构在C++中判断字符串中的括号是否匹配。通过顺序栈和链栈两种方式实现,并详细解释了算法的核心思想和具体实现步骤。 ... [详细]
  • 本题要求在一组数中反复取出两个数相加,并将结果放回数组中,最终求出最小的总加法代价。这是一个经典的哈夫曼编码问题,利用贪心算法可以有效地解决。 ... [详细]
  • 本文深入探讨了UNIX/Linux系统中的进程间通信(IPC)机制,包括消息传递、同步和共享内存等。详细介绍了管道(Pipe)、有名管道(FIFO)、Posix和System V消息队列、互斥锁与条件变量、读写锁、信号量以及共享内存的使用方法和应用场景。 ... [详细]
  • 本文详细解析了2019年西安邀请赛中的一道树形动态规划题目——J题《And And And》。题目要求计算树中所有子路径异或值为0的集合数量,通过深入分析和算法优化,提供了高效的解决方案。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • Linux环境下C语言实现定时向文件写入当前时间
    本文介绍如何在Linux系统中使用C语言编程,实现在每秒钟向指定文件中写入当前时间戳。通过此示例,读者可以了解基本的文件操作、时间处理以及循环控制。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • 本文介绍了如何在 C# 和 XNA 框架中实现一个自定义的 3x3 矩阵类(MMatrix33),旨在深入理解矩阵运算及其应用场景。该类参考了 AS3 Starling 和其他相关资源,以确保算法的准确性和高效性。 ... [详细]
  • 二叉树的链表实现
    本文介绍了一种使用链表结构表示二叉树的方法。通过定义节点结构和相关操作函数,可以方便地创建、插入和遍历二叉树。 ... [详细]
  • 主调|大侠_重温C++ ... [详细]
  • CentOS 6.8 上安装 Oracle 10.2.0.1 的常见问题及解决方案
    本文记录了在 CentOS 6.8 系统上安装 Oracle 10.2.0.1 数据库时遇到的问题及解决方法,包括依赖库缺失、操作系统版本不兼容、用户权限不足等问题。 ... [详细]
  • HDU 2871 内存管理问题(线段树优化)
    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2871。本题涉及内存管理操作,包括重置、申请、释放和查询内存块。通过使用线段树进行高效管理和维护。 ... [详细]
  • KMP算法是处理字符串匹配的一种高效算法它首先用O(m)的时间对模板进行预处理,然后用O(n)的时间完成匹配。从渐进的意义上说,这样时间复 ... [详细]
author-avatar
php小盗
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有