文章都是通过阅读源码分析出来的,还在不断完善与改进中,其中难免有些地方理解得不对,欢迎大家批评指正。 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保持数据上的高度一致。这也是后面它们能正确实施“生产者-消费者”模型的基础。 |
|
来自: 老匹夫 > 《Graphics》