GUI系统之SurfaceFlinger(6)BufferQueue中的缓冲区分配

时间:2021-07-20 14:25:06

文章都是通过阅读源码分析出来的,还在不断完善与改进中,其中难免有些地方理解得不对,欢迎大家批评指正。
转载请注明:From LXS. http://blog.csdn.net/uiop78uiop78/

GUI系统之SurfaceFlinger章节目录:
blog.csdn.net/uiop78uiop78/article/details/8954508



1.1.1 BufferQueue中的缓冲区分配

我们知道,BufferQueue中有一个mSlots数组用于管理其内的各缓冲区,最大容量为32。从它的声明方式来看,这个mSlots在程序一开始就静态分配了32个BufferSlot大小的空间。不过这并不代表缓冲区也是一次性静态分配的,恰恰相反,从BufferSlot的内部变量指针mGraphicBuffer可以看出,缓冲区的空间分配应当是动态的(从下面的注释也能看出一些端倪):

// mGraphicBuffer points to the buffer allocated for this slot or isNULL if no buffer has been allocated.

       sp<GraphicBuffer> mGraphicBuffer;

现在的问题就转移为,在什么情况下会给一个Slot实际的分配空间呢?

首先能想到的就是dequeueBuffer。理由如下:

Ø 缓冲区的空间分配应该既要满足使用者的需求,又要防止浪费。后面这一点mSlots已经满足了,因为它并没有采取一开始就静态预分配的方式

Ø 既然Producer对buffer的操作是“主动”的,那么就意味着它是整个需求的发起者。换句话说,只要它没有dequeueBuffer,或者dequeueBuffer时能获取到可用的缓冲区,那当然就没有必要再重新分配空间了

 

来详细分析下这个函数:

/*frameworks/native/libs/gui/BufferQueue.cpp*/

status_t BufferQueue::dequeueBuffer(int *outBuf, uint32_t w,uint32_t h, uint32_t format, uint32_t usage) {…

status_t  returnFlags(OK);

    { // Scope for the lock

        Mutex::Autolocklock(mMutex); /*这里采用了自动锁,所以上面需要加个“{”,这样当lock变量生

                                   命期结束后锁也就自动释放了。这种写法非常常见*/

                   …

        int found = -1;

        int foundSync = -1;

        int dequeuedCount = 0;

        bool tryAgain = true;

        while (tryAgain) {/*Step1. 循环查找符合要求的slot*/

            …                        

            found = INVALID_BUFFER_SLOT;//初始值

            foundSync =INVALID_BUFFER_SLOT;

            dequeuedCount = 0;           

            for (int i = 0; i< mBufferCount; i++) {

                const intstate = mSlots[i].mBufferState;

                /*Step2.统计dequeued buffer数量,后面会用到*/

                if (state ==BufferSlot::DEQUEUED) {

                    dequeuedCount++;

                }

               

                if (false) {

                   //肯定不会走这边

                } else {

                    if (state== BufferSlot::FREE) { /*Step3.寻找符合要求的Slot*/                      

                        boolisOlder = mSlots[i].mFrameNumber <mSlots[found].mFrameNumber;

                        if(found < 0 || isOlder) {

                           foundSync = i;

                            found = i; //找到符合要求的Slot

                        }

                    }// if (state ==BufferSlot::FREE)结束

                }//if(false)else结束

            }// for循环结束

 

           /*Step4.如果Client没有设置buffer count的话,就不允许dequeue一个以上的buffer*/

            if(!mClientBufferCount && dequeuedCount) {

                ST_LOGE("dequeueBuffer:can't dequeue multiple buffers without setting the buffer count");

                return-EINVAL;

            }

            …

            /*Step5. 判断是否要重试*/

            tryAgain = found== INVALID_BUFFER_SLOT;

            if (tryAgain) {

                mDequeueCondition.wait(mMutex);

            }

        }//while循环结束

 

        if (found ==INVALID_BUFFER_SLOT) {

            /*因为前面while循环如果没找到的话是不会返回的,所以理论上不会出现这种情况*/

           ST_LOGE("dequeueBuffer: no available buffer slots");

            return -EBUSY;

        }

 

        const int buf = found;

        *outBuf = found; //返回值

       /*成功找到可用的Slot序号,接下来就开始对这个指定的Slot进行初始操作,及状态变迁等*/

                   …       

       mSlots[buf].mBufferState = BufferSlot::DEQUEUED;/*Step6.Buffer状态改变*/

        constsp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer);

        if ((buffer == NULL)|| (uint32_t(buffer->width)  != w) ||(uint32_t(buffer->height) != h) ||

           (uint32_t(buffer->format) != format) || ((uint32_t(buffer->usage)& usage) != usage))

        {  

            status_t error;

             /*Step7. 分配GraphicBuffer空间*/

           sp<GraphicBuffer> graphicBuffer(mGraphicBufferAlloc->createGraphicBuffer(

                            w,h, format, usage, &error));           

            …

            mSlots[buf].mGraphicBuffer = graphicBuffer; //这个Slot终于分配到空间了

            …

            returnFlags |=ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;

        }

                    …

    }  // 自动锁lock结束的地方

    …

    return returnFlags;

}

因为这个函数很长,我们尽量只留下最核心的部分。从大的方向上看,Step1-Step5是在查找一个可用的Slot序号。从Step6开始,就针对这一指定的Slot进行操作了。下面我们分步来解释。

Step1@BufferQueue::dequeueBuffer。进入while循环,退出的条件是tryAgain为false。这个变量默认值是true,如果一轮循环结束后found的值不再是INVALID_BUFFER_SLOT,就会变成false,从而结束整个while循环。

循环的主要功能就是查找符合要求的Slot,其中found变量是一个int值,指的是这个BufferSlot在mSlots数组中的序号。

 

Step2@BufferQueue::dequeueBuffer。统计当前已经被dequeued的buffer数量,这将用于后面的判断,即假如Client没有设置buffer count的话,那么它会被禁止dequeue一个以上的buffer。

 

Step3@BufferQueue::dequeueBuffer。假如当前的buffer状态是FREE,那么这个Slot就可以进入备选了。为什么只是备选而不是直接返回这一结果呢?因为当前的mSlots中很可能有多个符合条件的Slot,当然需要挑选其中最匹配的。判断的依据是当前符合要求的Slot的mFrameNumber是否比上一次选中的最优Slot的mFrameNumber小。代码如下:

bool isOlder =mSlots[i].mFrameNumber <mSlots[found].mFrameNumber;

 

Step4@BufferQueue::dequeueBuffer。这里的判断来源于前面第二步的计算结果,一旦发现dequeue的数量“超标”,就直接出错返回

 

Step5@BufferQueue::dequeueBuffer。经过上述几个步骤,我们已经扫描了一遍mSlots中的所有成员了,这时就要决定是否可以退出循环。前面已经说过,如果成功找到有效的Slot就可以不用再循环查找了,否则tryAgain仍然是true。假如是后一种情况,证明当前已经没有FREE的Slot了,这时如果直接进入下一次循环,结果通常也是一样的,反而浪费了CPU资源。所以就需要使用条件锁,代码如下:

mDequeueCondition.wait(mMutex);

当有Buffer被释放时,这个锁的条件就会满足。

 

Step6@BufferQueue::dequeueBuffer。根据Buffer的状态迁移图,当处于FREE状态的Buffer被dequeue成功后,它将进入DEQUEUED,所以这里我们需要改变mBufferState。

 

Step7@BufferQueue::dequeueBuffer。通过上述几个步骤的努力,现在我们已经成功地寻找到可用的Slot序号了,但是这并不代表这个Slot可以直接使用,为什么?最明显的一个原因就是这个Slot可能还没有分配空间。

因为BufferSlot:: mGraphicBuffer初始值是NULL,假如我们是第一次使用它,必然是需要为它分配空间的。另外,即便mGraphicBuffer不为空,但如果用户所需要的Buffer属性(比如width、height、format等等)和当前这个不符合,那么也还是要重新分配。

分配空间使用的是mGraphicBufferAlloc这个Allocator,这里暂不深究其中的实现了。

如果重新分配了空间,那么最后的返回值中需要加上BUFFER_NEEDS_REALLOCATION标志。客户端在发现这个标志后,它还应调用requestBuffer()来取得最新的buffer地址。

我们前一小节SurfaceTextureClient::dequeueBuffer()的Step3就是其中一个例子,这里统一解释一下为什么。为了方便阅读,再把这部分代码简单地列出来:

int SurfaceTextureClient::dequeueBuffer(android_native_buffer_t**buffer) {…

/*Step2. dequeueBuffer得到一个缓冲区*/

    status_t result =mSurfaceTexture->dequeueBuffer(&buf, reqW, reqH,mReqFormat, mReqUsage);

    …

   sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);//注意buf只是一个int值,代表的是mSlots数组序号

/*Step3. requestBuffer*/

    if ((result &ISurfaceTexture::BUFFER_NEEDS_REALLOCATION) || gbuf == 0) {

        result =mSurfaceTexture->requestBuffer(buf, &gbuf);

        …

    }

    *buffer = gbuf.get();

    return OK;

}

当mSurfaceTexture->dequeueBuffer成功返回后,buf得到了mSlots中可用数组成员的序号(对应这一小节的found变量)。但一个很显然的问题是,既然客户端和BufferQueue运行于两个不同的进程中,那么它们两者中的mSlots[buf]会指向同一块物理内存吗?

先来看下BpSurfaceTexture中是如何发起binder申请的:

/*frameworks/native/libs/gui/ISurfaceTexture.cpp*/

class BpSurfaceTexture : public BpInterface<ISurfaceTexture>

{ …

                virtualstatus_t requestBuffer(int bufferIdx, sp<GraphicBuffer>* buf) {//函数入参有两个

        Parcel data, reply;

       data.writeInterfaceToken(ISurfaceTexture::getInterfaceDescriptor());

       data.writeInt32(bufferIdx);//只写入了bufferIdx,也就是BnSurfaceTexture实际上是看不到buf的

        status_t result=remote()->transact(REQUEST_BUFFER, data, &reply);

        …

        bool nonNull =reply.readInt32();//读取的是什么?我们可以去BnSurfaceTexture中去确认下

        if (nonNull) {

            *buf = newGraphicBuffer(); //生成一个GraphicBuffer,看到没,这是一个本地实例

            reply.read(**buf);/*buf是一个sp指针,那么**sp实际上得到的就是这个智能指针所指向的

                           对象。在这个例子中指的是mSlots[buf].buffer*/

        }

        result =reply.readInt32();/*读取结果*/

        return result;

    }

Native层的BpXXX/BnXXX与Java层的不同之处在于,后者通常都是依赖于aidl来自动生成这两个类,而前者则是手工完成的。也正因为是手工,使用起来才也更灵活。比如在ISurfaceTexture这个例子中,开发者就耍了点技巧——SurfaceTextureClient中调用了ISurfaceTexture::requestBuffer(intslot, sp<GraphicBuffer>* buf),这个函数虽然形式上有两个参数,但只有第一个是入参,后一个则是出参。在实际的binder通信中,只有slot这个int值传递给了对方进程,而buf则自始至终都是SurfaceTextureClient进程在处理,只不过从调用者的角度来讲,好像是由ISurfaceTexture的Server端完成了对buf的赋值。

从BpSurfaceTexture::requestBuffer这个函数实现中可以看到,Client端进程向server端请求了一个REQUEST_BUFFER服务,然后通过读取返回值来获得缓冲区信息。为了让大家能看清楚这其中的细节,我们有必要先分析下BnSurfaceTexture这边具体是如何响应这个服务请求的,如下所示:

/*frameworks/native/libs/gui/ISurfaceTexture.cpp*/

status_t BnSurfaceTexture::onTransact(uint32_t code, constParcel& data, Parcel* reply, uint32_t flags)

{       

    switch(code) {

        case REQUEST_BUFFER: {

           CHECK_INTERFACE(ISurfaceTexture, data, reply);

            int bufferIdx   =data.readInt32();//首先读取要处理的Slot序号

            sp<GraphicBuffer> buffer; //生成一个GraphicBuffer智能指针

            int result =requestBuffer(bufferIdx, &buffer);//调用本地端的实现

           reply->writeInt32(buffer != 0);//注意,第一个写入的值是判断buffer不为空,

                                    //也就是一个bool值

            if (buffer != 0) {

               reply->write(*buffer); //好,真正的内容在这里,后面我们详细解释

            }

           reply->writeInt32(result);//写入结果值

            return NO_ERROR;

        } break;

针对BnSurfaceTexture的写入顺序,显然BpSurfaceTexture必须要按照同样的顺序来读取。

(1)因而它首先获取一个int32值,赋予nonNull变量,这个值对应的是buffer != 0逻辑判断。假如确实不为空的话,那说明我们可以接着读取GraphicBuffer了

(2)两边对GraphicBuffer变量的写和读分别是:

reply->write(*buffer);//写入

reply.read(**buf);//读取

(3)读取result结果值

 

在第二步中,Server端写入的GraphicBuffer对象需要在Client中完整地复现出来。根据我们在binder章节的学习,具备这种能力的binder对象应该是继承了Flattenable。实际上呢?

class GraphicBuffer: publicANativeObjectBase<ANativeWindowBuffer, GraphicBuffer,

        LightRefBase<GraphicBuffer> >,public Flattenable

从GraphicBuffer声明来看,确实是证明了我们的猜测。

接下来我们只需要看下它是如何实现flatten和unflatten的,相信谜底就能揭晓了。

/*frameworks/native/libs/ui/GraphicBuffer.cpp*/

status_t GraphicBuffer::flatten(void* buffer, size_t size, intfds[], size_t count) const

{ …

    int* buf =static_cast<int*>(buffer);

    …

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

    }

    return NO_ERROR;

}

这个函数中,我们最关心的是handle这个变量的flatten,它实际上是GraphicBuffer中打开的一个ashmem句柄,因而也就是两边进程共享缓冲区的关键。与handle相关的分别是buf[6]-buf[8]以及fds,再来看下Client端是如何还原出一个GraphicBuffer的。

status_t GraphicBuffer::unflatten(void const* buffer, size_t size,int fds[], size_t count)

{…

    int const* buf =static_cast<int const*>(buffer);

    …  

    const size_t numFds  = buf[6];

    const size_t numInts =buf[7];

    …

    if (numFds || numInts) {…

        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;

    } else { …

    } …

    if (handle != 0) {

        mBufferMapper.registerBuffer(handle);

    }

    return NO_ERROR;

}

同样,unflatten中的操作依据的也是flatten时写入的格式。其中最重要的两个函数是native_handle_create()和registerBuffer()。前一个函数生成native_handle实例,并将相关数据拷贝到其内部。另一个registerBuffer则属于GraphicBufferMapper类中的实现,成员变量mBufferMapper是在GraphicBuffer在构造函数中生成的,它所承担的任务是和Gralloc打交道,代码如下:

GraphicBufferMapper::GraphicBufferMapper()

{

    hw_module_t const* module;

    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);

这里出现了我们熟悉的Gralloc的moduleid,不清楚的可以回头看下Gralloc这小节的介绍。GraphicBufferMapper::registerBuffer()只是一个中介作用,它会直接调用gralloc_module_t::registerBuffer(),那么后者究竟完成了什么功能?因为这个函数的实现与具体平台有关,我们以msm7k为例大概看下:

/*hardware/msm7k/libgralloc/Mapper.cpp*/

int gralloc_register_buffer(gralloc_module_t const* module,buffer_handle_t handle)

{…

    private_handle_t* hnd =(private_handle_t*)handle;

    …

    err =gralloc_map(module, handle, &vaddr);

    return err;

}

可以看到,通过handle句柄,Client端可以将指定的内存区域映射到自己的进程空间中,而这块区域与BufferQueue中所指向的物理空间是一致的,从而成功地实现了缓冲区的共享。

这样子在SurfaceTextureClient::dequeueBuffer()中,当遇到结果中包含有BUFFER_NEEDS_REALLOCATION的情况时,我们再通过requestBuffer()得到的结果来“刷新”Client这端mSlots[]所管辖的缓冲区信息,以保证SurfaceTextureClient与BufferQueue能在任何情况下都对32个BufferSlot保持数据上的高度一致。这也是后面它们能正确实施“生产者-消费者”模型的基础。