在任何一个系统中,无可避免的都会跟各种buffers打交道,最经典的模式就是消费-生产者模式,一个独立的buffer在它们之间的交换等操作都需要一个机制来控制每个buffer的“生命周期”,即ALLOCATION和 RELEASE ,此外还要考虑到同步性问题,什么时候可以read buffer和write buffer都需要听从调遣。
在android中的fence就是这样一个为了解决同步性而出现的机制。首先从fence的语义角度来分析一下它的基本原理:
Fence即栅栏,栅栏的角色与它的名字非常类似.一组线程可以使用栅栏来集体进行相互同步;在本质上,每个线程在到达某种周知的状态时调用栅栏的wait()方法,阻塞起来,以等待其它所有参与线程调用wait()方法表明它们也到达了这个状态.一旦所有的线程都到达栅栏,它们就会集体解除阻塞,并一起继续执行;引起程序调用栅栏的wait()方法进行阻塞的那个状态叫做栅栏状态。
接下来分析fence在android中的应用,这里主要涉及SurfaceFlinger中绘制buffer及显示中的相关方面。
确切的说fence在producer和consumer对buffer处理的过程中是如何协调他们同步的工作,从而保证buffer内容的准确性,而不会被篡改。
首先我们知道一个buffer有以下几种状态:
FREE->DEQUEUED->QUEUED->ACQUIRED-FREE
FREE状态时,producer就可以申请他了吗?答案是错的,他需要等一个signal,也就是NO_FENCE这个信号,因为有可能上一次申请的buffer正在被consumer作业中,所以要等待consumer发出finish的信号,而此时FREE状态下的buffer就好像被栅栏拦住了,这里是用Fence中wait()或者waitForever()方法,等一个NO_FENCCE信号,栅栏就会打开。进入到下一流程。
DEQUEUED是指producer已经申请了一个buffer从队列中出来了,还没有入队列或者取消buffer,这个状态下的buffer,producer想对其进行修改也就是填入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<mDisplays.size();)
{
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; i<list->numHwLayers; 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,看名字就能猜出这是存放对应两个fence的fd,第一步是把之前初始化的每个layer的acqFenfd保存到数组中,接着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<RK30_MAX_LAYER_SUPPORT;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为-1时acqFenceFd会被触发。
当程序运行到这里的时候,由于只是当中的一个线程,所以前面客户端请求buffer的操作早已经开始了,而且已经在等待相关的fence了。触发了releasefence之后用户那边收到之后就开始dequeue一个buffer进行填充surface了。
用一张图来表示下这个过程: