普法GraphicBuffer诞生以及跨进程传递

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;  

image

一般来说,我们描述一块buffer,需要知道它在kernel中对饮的fd,虚拟地址,物理地址,offset,size等等信息,后面我们在private_handle_t中就可以看到这些字段。Android的gralloc模块负责从fb设备或者gpu中分配meomory,所以我们在gralloc中就可以找到native_handle的具体实现,gralloc中对buffer的描述就和具体的平台相关了,我们以aosp中最基本的gralloc为例,来看下gralloc中对native_handle是如何使用的.

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;
};

image

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。这个一切的起源那就说来话长了,但是木得办法,吃这个饭的,再苦再累也得受着。哎!

我们在分析以前,看几个重磅的类图!

image

上图是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的实现也是类似的。

image

在看具体实现前,我们先看几个重要的定义:

//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
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值