SurfaceFlinger GraphicBuffer内存共享缓冲区机制

时间:2022-05-01 03:07:15
前两周比较忙,没时间写下这篇博客
GraphicBuffer 是 Surface 系统中用于GDI内存共享缓冲区管理类,封装了与硬件相关的细节,从而简化应用层的处理逻辑


SurfaceFlinger是个服务端,而每个请求服务的应用程序都对应一个Client端,Surface绘图由Client进行,而由SurfaceFlinger对所有Client绘制的图合成进行输出,那么这两者是如何共享这块图形缓冲区的内存呢?简要之就是利用mmap/ummap,那么这些在android系统中是如何构架完成的呢?

frameworks\base\include\ui\GraphicBuffer.h 类定义:
class GraphicBuffer
  : public EGLNativeBase<
      android_native_buffer_t, 
      GraphicBuffer, 
      LightRefBase<GraphicBuffer> >, public Flattenable


EGLNativeBase 是一个模板类:
template <typename NATIVE_TYPE, typename TYPE, typename REF>
class EGLNativeBase : public NATIVE_TYPE, public REF
类 GraphicBuffer  继承LightRefBase支持轻量级引用计数控制
   派生 Flattenable 用于数据序列化给Binder进行传输
   
我们来看下 android_native_buffer.h 文件,这个 android_native_buffer_t 结构:
typedef struct android_native_buffer_t
{
#ifdef __cplusplus
    android_native_buffer_t() { 
        common.magic = ANDROID_NATIVE_BUFFER_MAGIC;
        common.version = sizeof(android_native_buffer_t);
        memset(common.reserved, 0, sizeof(common.reserved));
    }
#endif

    struct android_native_base_t common;

    int width;
    int height;
    int stride;
    int format;
    int usage;
    
    void* reserved[2];

    buffer_handle_t handle;

    void* reserved_proc[8];
} android_native_buffer_t;


注意这里有个关键的变量: buffer_handle_t handle; 这个就是显示内存分配与管理的私有数据结构


1、 native_handle_t 对 private_handle_t 的包裹

typedef struct
{
    int version;        /* sizeof(native_handle_t) */
    int numFds;         /* number of file-descriptors at &data[0] */
    int numInts;        /* number of ints at &data[numFds] */
    int data[0];        /* numFds + numInts ints */  这里是利用GCC的无定参数传递的写法
} native_handle_t;


/* keep the old definition for backward source-compatibility */
typedef native_handle_t native_handle;
typedef const native_handle* buffer_handle_t;


native_handle_t 是上层抽象的用于进程间传递的数据结构,对于 Gralloc 而言其内容就是:
SurfaceFlinger GraphicBuffer内存共享缓冲区机制

data[0] 指向具体对象的内容,其中:
  static const int sNumInts = 8;
  static const int sNumFds = 1;


sNumFds=1表示有一个文件句柄:fd
sNumInts= 8表示后面跟了8个INT型的数据:magic,flags,size,offset,base,lockState,writeOwner,pid;


由于在上层系统不要关心buffer_handle_t中data的具体内容。在进程间传递buffer_handle_t(native_handle_t)
句柄是其实是将这个句柄内容传递到Client端。在客户端通过Binder读取readNativeHandle @Parcel.cpp新生成一个native_handle。

native_handle* Parcel::readNativeHandle() const
{
    int numFds, numInts;
    err = readInt32(&numFds);
    err = readInt32(&numInts);


    native_handle* h = native_handle_create(numFds, numInts);
    for (int i=0 ; err==NO_ERROR && i<numFds ; i++) {
        h->data[i] = dup(readFileDescriptor());
        if (h->data[i] < 0) err = BAD_VALUE;
    }
    err = read(h->data + numFds, sizeof(int)*numInts);
...
}
这里构造客户端的native_handle时,对于fd进行dup处理(不同进程),其它的直接读取复制使用.
magic,flags,size,offset,base,lockState,writeOwner,pid 等复制到了客户端,从而为缓冲区共享获取到相应的信息


对于fd的写入binder特殊标志 BINDER_TYPE_FD:告诉Binder驱动这是一个fd描述符

status_t Parcel::writeFileDescriptor(int fd)
{
    flat_binder_object obj;
    obj.type = BINDER_TYPE_FD;
    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    obj.handle = fd;
    obj.cookie = (void*)0;
    return writeObject(obj, true);
}


2、GraphicBuffer 内存分配
三种分配方式:
  GraphicBuffer();

  // creates w * h buffer
  GraphicBuffer(uint32_t w, uint32_t h, PixelFormat format, uint32_t usage);


  // create a buffer from an existing handle
  GraphicBuffer(uint32_t w, uint32_t h, PixelFormat format, uint32_t usage,
          uint32_t stride, native_handle_t* handle, bool keepOwnership);

其实最终都是通过函数:initSize
status_t GraphicBuffer::initSize(uint32_t w, uint32_t h, PixelFormat format,
        uint32_t reqUsage)
{
    if (format == PIXEL_FORMAT_RGBX_8888)
        format = PIXEL_FORMAT_RGBA_8888;


    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
    status_t err = allocator.alloc(w, h, format, reqUsage, &handle, &stride);
    if (err == NO_ERROR) {
        this->width  = w;
        this->height = h;
        this->format = format;
        this->usage  = reqUsage;
        mVStride = 0;
    }
    return err;
}


利用 GraphicBufferAllocator 类分配内存:
首先加载 libGralloc.hwXX.so 动态库,分配一块用于显示的内存,屏蔽掉不同硬件平台的区别。
GraphicBufferAllocator::GraphicBufferAllocator()
    : mAllocDev(0)
{
    hw_module_t const* module;
    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
    LOGE_IF(err, "FATAL: can't find the %s module", GRALLOC_HARDWARE_MODULE_ID);
    if (err == 0) {
        gralloc_open(module, &mAllocDev);
    }
}


分配方式有两种:
status_t GraphicBufferAllocator::alloc(uint32_t w, uint32_t h, PixelFormat format,
      int usage, buffer_handle_t* handle, int32_t* stride)
{
    if (usage & GRALLOC_USAGE_HW_MASK) {
        err = mAllocDev->alloc(mAllocDev, w, h, format, usage, handle, stride);
    } else {
        err = sw_gralloc_handle_t::alloc(w, h, format, usage, handle, stride);
    }
    ...
}


具体的内存分配方式如下:
SurfaceFlinger GraphicBuffer内存共享缓冲区机制

3、共享句柄的传递
frameworks\base\libs\surfaceflinger_client\ISurface.cpp


客户端请求处理:BpSurface 类:
  virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage)
  {
      Parcel data, reply;
      data.writeInterfaceToken(ISurface::getInterfaceDescriptor());
      data.writeInt32(bufferIdx);
      data.writeInt32(usage);
      remote()->transact(REQUEST_BUFFER, data, &reply);
      sp<GraphicBuffer> buffer = new GraphicBuffer();
      reply.read(*buffer);
      return buffer;

  }
  
 这里利用 sp<GraphicBuffer> buffer = new GraphicBuffer(); 然后reply.read(*buffer)将数据利用 unflatten反序化到这个buffer中并返回这个本地new出来的GraphicBuffer对象,而这个数据是在哪里写入进去的呢?
 
服务端呼应处理: BnSurface 类:
status_t BnSurface::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
        case REQUEST_BUFFER: {
            CHECK_INTERFACE(ISurface, data, reply);
            int bufferIdx = data.readInt32();
            int usage = data.readInt32();
            sp<GraphicBuffer> buffer(requestBuffer(bufferIdx, usage));
            if (buffer == NULL)
                return BAD_VALUE;
            return reply->write(*buffer);
        }  
 
requestBuffer函数服务端调用流程:
requestBuffer @ surfaceflinger\Layer.cpp
sp<GraphicBuffer> Layer::requestBuffer(int index, int usage)
{
buffer = new GraphicBuffer(w, h, mFormat, effectiveUsage);
...
return buffer;
}


如此的话,客户端利用new 的 GraphicBuffer() 对象从 Parcel中读取 native_handle 对象及其内容,而在服务端由同样由 requestBuffer 请求返回一个真正的GraphicBuffer对象。那么这两个数据如何序列化传递的呢?
flatten @ GraphicBuffer.cpp
status_t GraphicBuffer::flatten(void* buffer, size_t size,
        int fds[], size_t count) const
{
...
    if (handle) {
        buf[6] = handle->numFds;
        buf[7] = handle->numInts;
        native_handle_t const* const h = handle;
        memcpy(fds,     h->data,             h->numFds*sizeof(int));
        memcpy(&buf[8], h->data + h->numFds, h->numInts*sizeof(int));
    }
flatten的职能就是将GraphicBuffer的handle变量信息写到Parcel句中,接收端利用unflatten读取
status_t GraphicBuffer::unflatten(void const* buffer, size_t size,
        int fds[], size_t count)
{
        native_handle* h = native_handle_create(numFds, numInts);
        memcpy(h->data,          fds,     numFds*sizeof(int));
        memcpy(h->data + numFds, &buf[8], numInts*sizeof(int));
        handle = h;
}
经过以上操作,在客户端构造了一个对等的 GraphicBuffer对象,下面将继续讲两者如何操作相同的内存块


4、共享内存的管理 -- Graphic Mapper 功能

两个进程间如何共享内存,如何获取到共享内存?Mapper就是干这个得。需要利用到两个信息:共享缓冲区设备句柄,分配时的偏移量.客户端需要操作一块共享内存时,首先利用 registerBuffer 注册一个 buffer_handle_t,然后利用lock函数获取缓冲区首地址进行绘图,即利用lock及unlock对内存进行映射使用。


利用lock(mmap)及unlock(ummap)进行一个缓冲区的映射。
重要的代码如下:mapper.cpp 
static int gralloc_map(gralloc_module_t const* module,
      buffer_handle_t handle,
      void** vaddr){
     private_handle_t* hnd = (private_handle_t*)handle;
void* mappedAddress = mmap(0, size,
              PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);

      if (mappedAddress == MAP_FAILED) {
          LOGE("Could not mmap %s", strerror(errno));
          return -errno;
      }
      hnd->base = intptr_t(mappedAddress) + hnd->offset;
      *vaddr = (void*)hnd->base;      
     return 0;
}

static int gralloc_unmap(gralloc_module_t const* module,
      buffer_handle_t handle){
      private_handle_t* hnd = (private_handle_t*)handle;
      void* base = (void*)hnd->base;
      size_t size = hnd->size;
      munmap(base, size);
      hnd->base = 0;
      return 0;
}
利用buffer_handle_t与private_handle_t句柄完成共享进程数据的共享:

SurfaceFlinger GraphicBuffer内存共享缓冲区机制

总结:
Android在该节使用了共享内存的方式来管理与显示相关的缓冲区,他设计成了两层,上层是缓冲区管理的代理机构GraphicBuffer,
及其相关的native_buffer_t,下层是具体的缓冲区的分配管理及其缓冲区本身。上层的对象是可以在经常间通过Binder传递的,而在进程间并不是传递缓冲区本身,而是使用mmap来获取指向共同物理内存的映射地址。