GraphicBuffer诞生以及跨进程传递重认识
引言
对于Android的Graphics图形堆栈这块,自我感觉看了蛮多的博客啊文档(不管是比较老的还是新一点的)。但是仅仅只是看了而已,都是蜻蜓点水,没有进行记录也没有总结。所以每次哪怕阅读过程中产业了很多疑问,也就是产生了疑问而已。这次我要说不,我要把我产生的疑问都统统的解决掉,或者说绝大部分的疑问都做到在能力范围之内去解决或者找到答案。那么这篇博客重点要记录的是GraphicBuffer诞生以及跨进程传递的本质是什么,特别是App端怎么能刚好importer到SF端产生的GraphicBuffer(Android 12以前的默认方式)。并且这块的博客也比较少,所以希望能用一篇博客阐述明白这个问题!
一 重新认识GraphicBuffer
在学习一个知识点之前,我们需要做的是先认识,再了解,再理解。所以我们在阅读SurfaceFlinger HardwareComposer以及gralloc相关代码的过程中,我们经常会遇到native_handle private_handle_t ANativeWindowBuffer ANativeWindow GraphicBuffer Surface等等一系列和memory相关的struct和class,他们相互之间到底是什么区别,又有什么联系呢?
本文从struct/class的结构角度分析下上述类型之间的关联.概括来说,
-
native_handle private_handle_t ANativeWindowBuffer GraphicBuffer这四个struct/class所描述的是一块memory,
-
而ANativeWindow 和Surface所描述的是一系列上述memeory的组合和对buffer的操作方法.有的struct/class在比较低的level使用,和平台相关,而另外一些在比较高的level使用,和平台无关,还有一些介于低/高level之间,用以消除平台相关性,让Android可以方便的运行在不同的平台上.
下面让我们依次来看下上述struct/class的定义:
1.1 native_handle_t
system/core/libcutils/include/cutils/native_handle.h
typedef struct native_handle
{
int version; //设置为结构体native_handle的大小,用来标识结构体native_handle_t的版本
int numFds; //标识结构体native_handle_t所包含的文件描述符的个数,这些文件描述符保存在成员变量data所指向的一块缓冲区中
int numInts; //表示结构体native_handle_t所包含的整数值的个数,这些整数保存在成员变量data所指向的一块缓冲区中
int data[0]; //指向的一块缓冲区
} native_handle_t;
1.2 private_handle_t
private_handle_t用来描述一块缓冲区,Android对缓冲区的定义提供了C和C++两种方式,C语言编译器下的定义:
hardware/libhardware/modules/gralloc/gralloc_priv.h
struct private_handle_t {
struct native_handle nativeHandle;
enum {
PRIV_FLAGS_FRAMEBUFFER = 0x00000001
};
int fd; //指向一个文件描述符,这个文件描述符要么指向帧缓冲区设备,要么指向一块匿名共享内存
int magic;
int flags;//用来描述一个缓冲区的标志,当一个缓冲区的标志值等于PRIV_FLAGS_FRAMEBUFFER的时候,就表示它是在帧缓冲区中分配的。
int size;//用来描述一个缓冲区的大小
int offset;//用来描述一个缓冲区的偏移地址
int base;//用来描述一个缓冲区的实际地址
int pid;//用来描述一个缓冲区的创建者的PID
// 因为native_handle的data成员是一个大小为0的数组,所以data[0]其实就是指向了fd,data[1]指向magic,以此类推.
// 上面提到我们可以把native_handle看成是一个纯虚的基类,那么在private_handle_t这个派生类中,numFds=1 numInts=6.
};
C++编译器下的定义:
struct private_handle_t : public native_handle {
enum {
PRIV_FLAGS_FRAMEBUFFER = 0x00000001
};
int fd; //指向一个文件描述符,这个文件描述符要么指向帧缓冲区设备,要么指向一块匿名共享内存
int magic;//指向一个魔数,它的值由静态成员变量sMagic来指定,用来标识一个private_handle_t结构体。
int flags;//用来描述一个缓冲区的标志,它的值要么等于0,要么等于PRIV_FLAGS_FRAMEBUFFER
int size;//用来描述一个缓冲区的大小。
int offset;//用来描述一个缓冲区的偏移地址。
int base;//用来描述一个缓冲区的实际地址,它是通过成员变量offset来计算得到的。
int pid;//用来描述一个缓冲区的创建者的PID。
static const int sNumInts = 6; //包含有6个整数
static const int sNumFds = 1; //包含有1个文件描述符
static const int sMagic = 0x3141592;
};
gralloc分配的buffer都可以用一个private_handle_t来描述,同时也可以用一个native_handle来描述.在不同的平台的实现上,private_handle_t可能会有不同的定义,所以private_handle_t在各个模块之间传递的时候很不方便,而如果用native_handle的身份来传递,就可以消除平台的差异性.在HardwareComposer中,由SurfaceFlinger传给hwc的handle即是native_handle类型,而hwc作为平台相关的模块,他需要知道native_handle中各个字段的具体含义,所以hwc往往会将native_handle指针转化为private_handle_t指针来使用。
二 native_handle_t和private_handle_t的相互转换在GraphicBuffer跨进程中的作用
地球人都知道,GraphicBuffer跨进程传输的杀手锏依肯的是Binder机制然后结合它自己实现的unflatten和flatten。这个一切的起源那就说来话长了,但是木得办法,吃这个饭的,再苦再累也得受着。哎!
我们在分析以前,看几个重磅的类图!
上图是Android系统定的义GraphicBuffer数据类型来描述一块图形buffer,该对象可以跨进程传输!
由于IGraphicBufferProducer是基于Binder进程间通信框架设计的,因此该类的实现分客户端进程和服务端进程两方面。从上图可以知道,客户端进程通过BpGraphicBufferpRroducer对象请求服务端进程中的GraphicBufferProducer对象来创建GraphicBuffer对象。
那如何获取IGraphicBufferProducer的远程Binder代理对象呢?IGraphicBufferProducer被定义为无名Binder对象,并没有注册到ServiceManager进程中,但SurfaceFlinger是有名Binder对象,因此可以通过SurfaceFlinger创建IGraphicBufferProducer的本地对象GraphicBufferProducer,并返回其远程代理对象BpGraphicBuffer Producer给客户端进程。那客户端进程又是如何请求SurfaceFlinger创建IGraphicBufferProucer的本地对象的呢?客户端进程首先从ServiceManager进程中查询SurfaceFlinger的远程代理对象BpSurfaceComposer:
用一句话来概括总结IGraphicBufferProducer代理端在App端的获取是通过传递匿名Binder实现的,它是Activity在申请创建对应的Surface的时候被创建的。这个不是本篇博客的重点。
二.GraphicBuffer是如何跨进程传递的
在Android中GraphicBuffer跨进程传递,通常牵涉到如下几个进程之间的传递App到SurfaceFlinger,然后到grallc hal之间,下面我们分别来介绍GraphicBuffer如何在这个进程中传递的。
2.1 App端通过IGraphicBufferProducer申请GraphicBuffer
通过前面我们的吧唧吧唧应该知道了,在App端通过createSurface在SF端创建对应的Layer并且返回IGraphBufferProducer代理端以后,App端就可以申请对应的GraphicBuffer图元了。
图形内存的分配核心在于 Surface.dequeueBuffer流程,它大概可以概括为如下几个步骤:
-
Surface.dequeueBuffer会调用 BufferQueueProducer.dequeueBuffer 去 SurfaceFlinger 端获取BufferSlot数组中可用Slot的下标值
- 这个 BufferSlot 如果没有GraphicBuffer,就会去new一个,并在构造函数中申请图形缓存,并把图形缓存映射到当前进程
- 同时把 BufferQueueProducer::dequeueBuffer 返回值的标记位设置为 BUFFER_NEEDS_REALLOCATION
-
BufferQueueProducer.dequeueBuffer 的返回值如果带有 BUFFER_NEEDS_REALLOCATION标记,会调用 BufferQueueProducer.requestBuffer 获取 GraphicBuffer,同时把图形缓存映射到当前进程
其调用过程,我们就不过多的一行行分析,我们简易的看下伪代码:
Surface.dequeueBuffer(...)//App端进程
| BpGraphicBufferProducer.dequeueBuffer(...)//接口层
| BufferQueueProducer.dequeueBuffer(...)//SF进程端
| 1.dequeueBuffer 函数参数outSlot指针带回一个BufferSlot数组的下标 返回值返回标记位,但并未返回 GraphicBuffer
| 2.dequeueBuffer 函数中,在获取的 BufferSlot 没有GraphicBuffer时,会new一个GraphicBuffer,同时返回值的标记为 BUFFER_NEEDS_REALLOCATION
| new GraphicBuffer(...)//GraphicBuffer 构造函数中会调用initWithSize,内部调用分配图形缓存的代码
| initWithSize(...)
| GraphicBufferAllocator.allocate(...)
| allocateHelper(...)
| GrallocXAllocator.allocate(...)//hwbinder调用
| IAllocator::getService()->allocate(...)//此时还在SF进程
| 1.之后的代码需要看厂家的具体实现,最后无非是调用到内核驱动层分配内存,比如调用ion驱动层分配ion内存
| 2.SF 和 IAllocator 服务怎么传递 共享内存的,转“进程间图传递图形buffer详解
| 3.回调函数中调用 IMapper.importBuffer(...)//将申请的到的buffer通过mmap把ion内存映射到SF所在的进程
| 在 dequeueBuffer 返回值的标记为 BUFFER_NEEDS_REALLOCATION 时
| App端需要调用 requestBuffer,获取 GraphicBuffer 对象
| 同时,把 SurfaceFlinger 分配的图形缓存,映射到App进程
| BpGraphicBufferProducer->requestBuffer(buf, &gbuf)//此时在App端的接口层
| BufferQueueProducer.requestBuffer(...)//SF进程,请求返回 GraphicBuffer 对象
|
上面的传递过程涉及到两个很重要的跨进程传输,分别是SF和gralloc service,以及App和SF端。这里我们重点关注App和SF端的GraphicBuffer的跨进程传递逻辑:
-
SurfaceFlinger 进程端 requestBuffer 代码非常简单,仅仅是把 dequeueBuffer 过程中分配的对象赋值给参数 gbuf ,传递给 App
-
那么,图形缓存的 fd 是怎么传到App端的,App又是怎么映射的图形缓存呢?
核心在 BpGraphicBufferProducer.requestBuffer 函数中 GraphicBuffer 对象的构建过程:status_t result =remote()->transact(REQUEST_BUFFER, data, &reply); 接下来GraphicBuffer 传输过程,见App进程和SF间图传递图形buffer详解 *buf = new GraphicBuffer(); result = reply.read(**buf); read 过程会调用 GraphicBuffer.unflatten GraphicBuffer.unflatten 函数内部调用了 GraphicBufferMapper.importBuffer
-
内部也是调用IMapper.importBuffer,最终使用 mmap 把内存映射到当前进程
调用 mmap 过程
2.2 SF进程和IAllocator服务之间传递GraphicBuffer
IAllocator服务全称为android.hardware.graphics.allocator@4.0::IAllocator/default(这个也不一定,需要看具体版本或者ROM的策略),这个在高通平台上的进程名为:vendor.qti.hardware.display.allocator-service。接下来我们一步步分析GraphicBuffer是怎么在SF和IAllocator服务之间传递的。
2.2.1 SF向IAllocator通过Allocate申请构建GraphicBuffer
GraphicBuffer::GraphicBuffer(...)
GraphicBuffer::initWithSize(...)
GraphicBufferAllocator& allocator = GraphicBufferAllocator::get()
allocator.allocate(...)
GraphicBufferAllocator::allocate(...)//GraphicBufferAllocator.cpp
GraphicBufferAllocator::allocateHelper(...)
Gralloc2Allocator::allocate(...)//Gralloc2.cpp
// frameworks/native/libs/ui/Gralloc2.cpp
status_t Gralloc2Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
android::PixelFormat format, uint32_t layerCount,
uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
buffer_handle_t* outBufferHandles, bool importBuffers) const {
//...
//===================关键代码============
auto ret = mAllocator->allocate(descriptor, bufferCount,
[&](const auto& tmpError, const auto& tmpStride,
const auto& tmpBuffers) {
// const auto& tmpBuffers 是个 hidl_handle 类型
error = static_cast<status_t>(tmpError);
if (tmpError != Error::NONE) {
return;
}
if (importBuffers) {
for (uint32_t i = 0; i < bufferCount; i++) {
error = mMapper.importBuffer(tmpBuffers[i],
&outBufferHandles[i]);
if (error != NO_ERROR) {
for (uint32_t j = 0; j < i; j++) {
mMapper.freeBuffer(outBufferHandles[j]);
outBufferHandles[j] = nullptr;
}
return;
}
}
} else {
//....
}
*outStride = tmpStride;
});
//...
return (ret.isOk()) ? error : static_cast<status_t>(kTransactionError);
}
上述的代码核心有两点:
- SF向Allocate申请构建buffer_handle_t,此时的SF还不能使用这块buffer
- SF通过importBuffer将这块buffer映射到自己的进程,然后SF就阔以使用这块了
2.2.2 allocator服务端的hidl接口实现
关于hidl中binder的实现,可以类比Binder,HWBinder的实现也是类似的。
在看具体实现前,我们先看几个重要的定义:
//system/libhidl/base/include/hidl/HidlSupport.h
struct hidl_handle {
hidl_handle();
~hidl_handle();
hidl_handle(const native_handle_t *handle);
// copy constructor.
hidl_handle(const hidl_handle &other);
// move constructor.
hidl_handle(hidl_handle &&other) noexcept;
// assignment operators
hidl_handle &operator=(const hidl_handle &other);
hidl_handle &operator=(const native_handle_t *native_handle);
hidl_handle &operator=(hidl_handle &&other) noexcept;
void setTo(native_handle_t* handle, bool shouldOwn = false);
const native_handle_t* operator->() const;
// implicit conversion to const native_handle_t*
operator const native_handle_t *() const;
// explicit conversion
const native_handle_t *getNativeHandle() const;
// offsetof(hidl_handle, mHandle) exposed since mHandle is private.
static const size_t kOffsetOfNativeHandle;
private:
void freeHandle();
details::hidl_pointer<const native_handle_t> mHandle;
bool mOwnsHandle;
uint8_t mPad[7];
};
//system/libhidl/base/HidlSupport.cpp
// explicit conversion
const native_handle_t *hidl_handle::getNativeHandle() const {
return mHandle;
}
这里我们不看具体的allocate函数实现,重点看数据传输过程:
//out/soong/.intermediates/hardware/interfaces/graphics/allocator/2.0/android.hardware.graphics.allocator@2.0_genc++_headers/gen/android/hardware/graphics/allocator/2.0/IAllocator.h
using allocate_cb = std::function<void(::android::hardware::graphics::mapper::V2_0::Error error, uint32_t stride, const