【Android】Framework笔记——BufferQueue


之前的一篇文章 【Android】Framework笔记——View的绘制流程(二)中提到了 Surface 的创建过程中 createSurfaceLocked 方法创建 SurfaceControl 的过程中会在 SurfaceFlinger 中创建对应的 BufferLayer,并且通过 Binder 传输了 BufferLayer 中的 IGraphicBufferProducer 到 Surface 所处的 Client 进程。之后通过该 GraphicBufferProducer 在 getSurface 方法中创建了 Native 层的 Surface 对象。那么 IGraphicBufferProducer 到底是什么呢?Surface 又是如何拿到这个 Buffer 的呢?

什么是 IGraphicBufferProducer

其实 Surface 与 SurfaceFlinger 构成了一个生产者消费者模型,如图所示:

image-20190724173430023

其中 BufferQueue 代表一个 Buffer 的队列,我们在 Surface 上进行绘制所需要用到的 Buffer 就是从 BufferQueue 中获取,处于 SurfaceFlinger 服务所在的进程。

对它来说,Surface 所在进程就是对应了生产者,而 SurfaceFlinger 所在进程 则对应了消费者。它们三者之间通过 Buffer 产生联系。

进行屏幕绘制时,会发生下列步骤:

  • Surface 首先将 Buffer 从 BufferQueue 中通过 dequeueBuffer 取出,通过 Canvas 提供给上层
  • 上层绘制完成后 Surface 会通过 enqueueBuffer 放回 BufferQueue。
  • SurfaceFlinger 所在进程会通过 acquireBuffer 拿走这个 Buffer,将各个 Surface 的 Buffer 进行合成,再将其交给屏幕显示。
  • SurfaceFlinger 所在进程使用完这个 Buffer 之后就会通过 releaseBuffer 将其放回 BufferQueue 从而实现 Buffer 的循环使用。

而我们知道,Surface 是处于应用所在进程的(客户端),而 BufferQueue 是处于 SurfaceFlinger 所处进程的(服务端),因此这里其实是需要一个 IPC 的过程的,这个 IPC 过程其实就是通过 Binder 实现的,而前面提到的 IGraphicBufferProducer 其实是一个 Binder 接口,通过它可以与 BufferQueue 进行交互,它在客户端的实现是 BpGraphicBufferProducer 类,而在服务端的实现则是 BnGraphicBufferProducer 类。

BufferQueueProducer 的创建

我们现在回到 SurfaceControl 的创建部分,研究一下 IGraphicBufferProducer 究竟是何时创建的。

在创建 BufferLayer 的 createBufferLayer 方法中,实际上是调用了 getProducer 方法来返回 producer 的:

而它的创建时机则是 Layer 第一次被使用的时候:

它首先调用了 BufferQueue 的 createBufferQueue 方法创建了 producer、consumer 这两个变量,之后通过 MonitoredProducer 这个包装类包装后作为了当前的 Producer。

我们先看到createBufferQueue 方法:

这里可以看出,其在服务端真正的实现类是 BufferQueueProducer,它继承了 BnGraphicBufferProduer

Buffer

BufferSlot

我们知道,BufferQueue 是 IGraphicBufferProducer 在服务端的实现,因此它实现了其中的各种虚函数。这个类里面有一个非常重要的数组——BufferSlot mSlots[NUM_BUFFER_SLOTS],而在 Surface 中其实也有一个定义相同的数组,但其实它们内部存储的数据结构并不一样

Surface 中的 BufferSlot

BufferQueue 中的 BufferSlot

这里解释一下 BufferQueue 中涉及到的 BufferSlot 中各个成员变量的意义:

  • mGraphicBuffer:记录了该 Slot 所涉及的缓冲区。
  • mBufferState:跟踪了该 Slot 对应的缓冲区的状态。

可以理解为 BufferSlot 是一个在 BufferQueue 中记录了缓冲区相关信息的类,而之前我们所提到的缓冲区 Buffer,实际上都是在说这里面的成员变量 mGraphicBuffer

Buffer 的状态

前面提到了通过生产者和消费者对 BufferQueue 的不同方法调用,Buffer 会经历一个状态的循环。其实在这个过程中每个 Buffer 都会经历四种状态,代表了这个 Buffer 所处的不同阶段:

  • FREE:表示当前 Buffer 是可用的,可以被 dequeue。此时该 Buffer 属于 BufferQueue。
  • DEQUEUED:表示当前 Buffer 已经被 dequeue,正在被进行绘制。此时该 Buffer 属于 Producer,也就是我们的应用程序,此时 BufferQueue 及 SurfaceFlinger 均不能操作此 Buffer。
  • QUEUED:表示当前 Buffer 已经被 queue,但还不能对它进行 dequeue,但可以进行对其进行 acquire,此时该 Buffer 属于 BufferQueue。
  • ACQUIRED:此时 Buffer 已经被 acquire,可以被 release 返回 BufferQueue 变为 FREE 状态,此时该 Buffer 属于 Consumer,也就是 SurfaceFlinger 所在进程。

BufferQueue 中的生产者-消费者模型

现在让我们重新审视一下 BufferQueue 中的生产者-消费者模型。首先,BufferQueue 应该算是一个数据中心,生产者与消费者都需要通过它来对 Buffer 进行管理。

生产者

生产者也就是向我们的『数据中心』 产出数据的角色,也就是我们的 Surface 了。我们的应用程序会不断刷新 UI,每当要刷新 UI 时,它都会向 BufferQueue 通过 dequeueBuffer 方法申请 Buffer,此时 Buffer 就进入了 DEQUEUED 状态。之后便可以将其交给上层从而对 Buffer 进行写入操作。当写入完成后,它就会调用 enqueueBuffer 将 Buffer 交还给 BufferQueue,此时 Buffer 就会进入 QUEUED 状态。

消费者

消费者也就是对我们的『数据中心』中产出的数据进行消费的角色。它其实是 SurfaceFlinger 中的一个 SurfaceFlingerConsumer 对象。当 Buffer 进入了 QUEUED 状态,BufferQueue 会通过一种方式通知到我们的消费者,之后消费者就可以通过 dequeueBuffer 来对 Buffer 进行 acquire,此时 Buffer 就进入了 ACQUIRED 状态,此时消费者就可以对 Buffer 中的数据进行读取并进行渲染等操作。当操作结束后,它就会通过 enqueueBuffer 来将 Buffer 交还给 BufferQueue,此时 Buffer 就又回到了 FREE 状态。

BufferQueue 对消费者的通知

那么 BufferQueue 是如何对我们的消费者进行通知的呢?

其实 BufferQueue 中有一个名为 ConsumerListener 的结构体:

显然我们的消费者注册这个 Listener,这样每当一帧的数据准备就绪后,BufferQueue 就会调用 onFrameAvailable 方法通知消费者。

生产者

Buffer 的获取及Buffer 内存的分配

BufferQueue 中的 mSlots 数组的主要作用就是管理 BufferQueue 中的各个 Buffer,它的最大容量为 32。虽然它从一开始就分配了 32 个数组元素的空间,但每个 Slot 中的 GraphicSlot 并不是一开始就进行了分配的,它是一个动态的分配过程。那么一个 Buffer 的空间究竟是何时才会分配呢?

Buffer 的获取

这个分配过程其实是在获取 Buffer 的过程中实现的,我们先看到服务端的 BufferQueueProducer 的 dequeueBuffer 方法:

dequeueBuffer

dequeueBuffer 的代码挺长的,主要分为以下几步:

  1. 调用 mCore.waitWhileAllocatingLocked 等待内存分配结束
  2. 循环调用 waitForFreeSlotThenRelock 方法查找空闲 Slot
  3. 将找到的空闲 Slot 的 index 作为输出参数 outSlot 并修改 Buffer 的状态
  4. 判断是否需要重新分配内存,若需要则重新分配内存(buffer 为 null 或 buffer 内部参数不满足条件时才需要重新分配内存)
waitForFreeSlotThenRelock

我们接着看到 waitForFreeSlotThenRelock 方法:

这里主要是调用了 getFreeBufferLocked 方法进行寻找空闲 Buffer,若找不到则会重试。

并且这里还有个比较细节的地方,在 while 循环进行查找时,若这一轮找不到,则会调用 mDequeueCondition.wait 来进行阻塞。因为此时如果进入下一轮循环,也仍然很难查找到空闲的 Buffer。那肯定会是一种资源的浪费,因此它在这一轮找不到时会调用此方法进行阻塞,只有当 Buffer 被释放时,才满足了这个锁的条件,此时程序就可以继续查找可用的 Slot。

getFreeBufferLocked

我们接着看到 getFreeBufferLocked 方法:

它此处实际上就是从 mCore 的 mFreeSlots 中取出了第一个 Slot。

Buffer 的内存共享机制

我们先不关注 Buffer 的内存分配,先来思考一个问题:

上面的 dequeueBuffer 中返回的并不是 Buffer 对象,仅仅只是 Buffer 在 mSlots 中的 index。但这就存在一个问题了,我们的 Surface 所处的进程与 BufferQueue 所处的进程并不是同一个,即使我们得到了 Buffer 在 mSlots 数组中的 index,通过 mSlots[index] 拿到了对应的 Slot,但这个 Slot 中的 Buffer 与 BufferQueue 中对应的 Slot 中的 Buffer 所处的内存空间肯定是不同的。这样是没办法获取到我们需要的 GraphicBuffer 对象的。

其实 Buffer 通过一种内存的同步机制巧妙地使得两个不同进程的 Slot 中的 GraphicBuffer 共享了同一块内存,它的核心实现是 requestBuffer 方法。让我们看看这是怎么做到的。

我们先看到 Surface 如何发起 dequeueBuffer 的 Binder 请求的:

可以看到,它在获取了空闲的 Buffer 在 mSlots 中的 index 后,调用到了客户端 BpGraphicBufferProducerrequestBuffer 方法,这个方法就是实现内存共享机制的核心方法。同时注意这里的 gbuf 是一个指向 mSlot[buf].buffer,也就是客户端对应的 Buffer 的指针:

我们知道,这里的 bufferIdx 实际上就是空闲的 Slot 的 index,而 buf 则是一个指向了客户端的 Buffer 的指针的指针。最后调用到了服务端 BnGraphicBufferProduceronTransact 方法:

而在服务端这边,首先读取了 Slot 的序号,之后调用了服务端的 requestBuffer 方法(注意,这里不是 BpGraphicBufferProducer 的 requestBuffer 方法,而是 BufferQueueProducer 的),并传入了前面生成的 GraphicBuffer 指针。

在服务端的 requestBuffer 方法中,会将服务端对应的 Buffer 写入到刚刚指针指向的地址。

之后,服务端调用了 reply.write(*buffer) 将服务端的 Buffer 传入。

然后看到客户通过 reply.read(**buf) 将服务端传输的 Buffer 读入。

这样看来好像是将服务端的 Buffer 复制到了客户端的 Buffer,但 Google 官方文档上对 BufferQueue 的文档上是这样描述的:

BufferQueue 永远不会复制缓冲区内容(移动如此多的数据是非常低效的操作)。相反,缓冲区始终通过句柄进行传递。

也就是说这里是我们的 Buffer 并没有被拷贝,而是采用了一种句柄(handle)来对 Buffer 进行传递。那么它究竟是在哪里实现的呢?

我们思考一下,其实 GraphicBuffer 是一个对象,它通过 Binder 传递其实是需要实现 Flattenable 接口的,其实它的这个内存共享的机制就是在序列化的过程中实现的。

当服务端调用 reply.write 的时候,会调用到 GraphicBuffer.flatten 函数,进行序列化

此时是将数据写入的过程,它首先写入了一些对方需要的数据,重点在 handle 处,它将 handle.data 中的 fd 部分通过 memcpy 写入了 fds,之后将 handle.data 中的 int 部分写入了 buf + 13 (其实就是 buf[13])这块内存中。

我们继续看到客户端的 unflatten 函数,看看它是如何还原出一个 GraphicBuffer 的:

可以看到,这里先通过 native_handle_create 方法创建了 Native 的 handle,也就是 native_handle 的实例 h,之后将数据通过 memcpy 将之前写入到 buffer 中的 handle.data 的 fd 部分及 int 部分还原到 h 中,从而还原服务端的 handle,最后赋值给了 handle。

之后调用了 mBufferMapper.importBuffer 方法,并传入了 handle 及创建的 buffer_handle_t。在 importBuffer 中就通过这个 handle 实现了两端的内存的共享,不过这部分都是交给 HAL 层去实现的了,由于目前实力有限,相关资料也比较少,就先不对这里分析了,这里猜测 HAL 层通过 handle 来辨别唯一的 Buffer,之后将客户端 Buffer 的内存映射到了这个 handle 对应的服务端 Buffer 的内存。

可以看出,就像 Google 官方文档中说到的,Buffer 并不会通过拷贝来传递,一般都是通过 handle 进行传递。

Buffer 的内存分配

我们前面看到,在 dequeueBuffer 调用时,会调用到 GraphicBuffer 的构造函数,从而分配 GraphicBuffer 的内存:

它对应的构造函数如下:

它调用到了 initWithSize 方法:

它首先通过 GraphicBufferAllocator.get 获取到了 GraphicBufferAllocator,然后调用了其 allocate 方法。看来 GraphicBufferAllocator 就是 GraphicBuffer 的统一内存分配中心了。

同时看到这里也有一个 handle 参数传入,看来这里 Buffer 也是通过 handle 来进行传递。

GraphicBufferAllocator.allocate

这里最后调用到了其 mAllocator.allocate 方法,而看到 GraphicBufferAllocator.h 中,这个 mAllocator就是一个 Gralloc2 对象,也就是这里又再次交给了 HAL 层进行其内存的分配,我们也不再进去看了

Buffer 的放回

可以看到,这里主要做的有下面几步:

  1. 获取对应 Slot 中的 Buffer
  2. 修改 Buffer 状态
  3. 根据其他 Buffer 状态判断要调用 ConsumerListener 的哪个方法
  4. 调用 ConsumerListener 对应方法(onFrameAvailable 或 onFrameReplaced)

消费者

那么这个 Consumer 究竟是谁呢?我们先看看这个 mCore.mConsumerListener 究竟是何时传入的。它实际上是在 BufferQueueConsumer 这个类的 connect 方法中传入的,这个类实际上是 IGraphicBufferConsumer 在服务端的实现。

可以看到,外部调用了它的 connect 方法并传入了这个 Listener,那它的 connect 方法又是何时调用的呢?

BufferQueueConsumer.consumerConnect 方法中调用了 connect 方法,而在 BufferQueueConsumer 的父类 ConsumerBase 的构造函数中则调用到了 consumerConnect 方法。可以看到其实现了 ConsumerListener。

BufferQueueConsumer 的创建则是在 SurfaceFlinger 的 Layer 初次被引用时,就会调用其构造函数进行创建

其实这里的注册只是躯壳,真正的注册是在 setContentsChangedListener 方法中(因为 consumerConnect 中传入的是一个代理对象,实际上它的真正监听者是 Layer)。Layer 实现了 onFrameAvailable 等方法:

可以看到,Layer 接收到 onFrameAvailable 回调之后先后调用了 SurfaceFlinger.Inteceptor.saveBufferUpdate 方法、SurfaceFlinger.signalLayerUpdate 方法,从而通知 SurfaceFlinger 对 Buffer 的内容进行处理。

参考资料

Android6.0 显示系统(三) 管理图像缓冲区

Android graphics 学习-生产者、消费者、BufferQueue介绍


Android Developer in GDUT