Android Fence Sync

在DMA流水线上,一块缓冲区的消费者需要知道生产者何时完成内容的生成,同理,生产者也需要知道消费者何时完成缓冲区的使用以便生产者再次使用该缓冲区。这时就需要有一种机制来实现同步。Android sync框架提供了一套API来实现消费者和生产者之间的同步,同时也允许各个生产厂商实现自己的同步方法,sync在kernel中的实现为drivers/staging/android/sync.c。

Kernel中涉及到的sync相关对象包括:
  • sync_timeline:
一个sync_timeline可看做是一个单调递增的计数器,一般地,每一个驱动或硬件设备会有一个sync_timeline。
Image
  • sync_pt:
sync_pt可看做是sync_timeline上的一个点,每个sync_pt有唯一的一个sync_timeline parent。sync_pt有三种状态:active,signaled以及error。初始时,sync_pt处于active状态,当满足条件时,向另外两个状态转移(仅一次)。
Image [1]
  • sync_fence:
sync_fence是驱动程序同步缓冲区时最主要的元素。sync_fence由一系列sync_pt组成,这些sync_pt可以具有相同或不相同的timeline parent。一个sync_pt只能存在于一个sync_fence中,而sync_fence一经创建,其包含的sync_pt列表将不可改变。在等待sync_fence时,可以同步等待,也可以异步等待。两个sync_fence对象可以合并成一个新的sync_fence对象,该新的sync_fence对象包含了原来两个fence对象的sync_pt。为了使用户态程序可以配合sync框架,每个fence有一个对应的文件描述符。和sync_pt一样,fence也有三个状态:active,signaled以及error 。初始时,fence处于active状态,当其sync_pt列表中所有对象处于signaled状态时,fence转换至signaled状态;只要有一个sync_pt处于error状态,则fence转换至error状态。
Image [2]
fence的合并(merge):
before:
Image [3]
After:
Image [4]
理解sync kernel APIs :

sync_timeline_create / sync_timeline_destroy / sync_timeline_free
创建/释放/销毁一个timeline,一般地,一个设备只需创建一个timeline。
在创建sync_timeline时,需要传递struct sync_timeline_ops,该结构体主要包含了sync_pt相关回调方法,如dup sync_pt,检查sync_pt的状态,比较两个sync_pt在timeline中signaled的先后顺序,释放sync_pt时做的操作等。
sync_timeline会维护两个链表,一个链表用于保存children list,当创建sync_pt时,会将该sync_pt添加至children list;另一个链表用于保存active list,当创建sync_fence的时候,如果指定的sync_pt还处于active状态,则将该sync_pt加入timeline active链表。
void sync_timeline_signal(struct sync_timeline *obj)
signal sync_timeline obj。会检查timeline active list上的sync_pt是否满足signaled条件,如果是则移出timeline active list。并进而检查sync_pt对应的fence的状态,如果fence中所有sync_pt都是signaled,则将fence也设置为signaled。
struct sync_pt *sync_pt_create(struct sync_timeline *parent, int size)
创建一个sync_pt,第一个参数为timeline parent,创建sync_pt的同时,会将sync_pt加入到sync_timeline维护的child_list链表中,这样,sync_pt和sync_timeline之间就建立了联系。第二个参数为分配内存大小,该大小必须大于struct sync_pt的大小,之所以有这样的参数,是因为需要给各个实现提供空间,比如可以按照如下方式实现自己的sync_pt:
struct my_own_unclez_sync_pt
{
    struct sync_pt pt;
    int attribute1;
    int attribute2;
};
需要注意的时,在自己的实现中,必须将sync_pt放在结构体的第一个成员位置,因为kernel也会根据该结构体的首地址访问sync_pt成员,而kernel是不可能知道我们自己扩展的数据结构的,如果sync_pt不在第一个成员位置,则kernel会访问出错。这一点可以用面向对象中继承的观点来理解。
void sync_pt_free(struct sync_pt *pt)
释放sync_pt。
struct sync_fence *sync_fence_create(const char *name, struct sync_pt *pt)
创建sync_fence对象,并将输入sync_pt添加至fence的sync_pt列表。
void sync_fence_install(struct sync_fence *fence, int fd)
Install fence to fd.
struct sync_fence *sync_fence_merge(const char *name, struct sync_fence *a, struct sync_fence *b)
首先拷贝并dup sync_fence a 中的sync_pt到新的sync_fence,然后合并sync_fence b到新的sync_fence,合并时,如果sync_pt属于同一个timeline,则仅保留最后一个signal的sync_pt。
int sync_fence_wait_async(struct sync_fence *fence, struct sync_fence_waiter *waiter)
异步等待fence,注册一个当fence signal时调用的回调函数。
int sync_fence_cancel_async(struct sync_fence *fence, struct sync_fence_waiter *waiter)
取消异步等待。
int sync_fence_wait(struct sync_fence *fence, long timeout)
同步等待fence signaled。
驱动/硬件怎样实现(扩展)sync框架:

理解了Kernel函数接口,也就不难理解该怎样在自己的驱动中实现sync机制。其中的一个关键函数是sync_timeline_signal(),当该函数被调用时,Kernel会检查timeline上sync_pt的状态,并进一步检查fence的状态。那么,如果将驱动对应的(硬件)消息和sync_timeline_sync()函数的调用绑定在一起,则可以在得到硬件消息,如buffer使用完毕时,间接地通知fence。
一般,可以按照如下步骤实现sync:
a) 创建sync_timeline,在has_signaled方法中实现硬件消息和sync_pt是否signaled检查的绑定;
b) 创建sync_pt,及sync_fence;
c) 在驱动对应的(硬件)消息发生时,调用sync_timeline_signal()。
Reference:
  1. kernel/Document/sync.txt
  2. “Android Sync”, presentation by Riley Andrews

Leave a Reply

Your email address will not be published. Required fields are marked *