关于GraphicBuffer的使用,前几天再查看一个内存泄漏的问题时,了解了一些关于Android上的GraphicBuffer使用的细节。以下的这个sample就是为了测试GraphicBuffer对应的gralloc如何分配内/释放内存,以及对于不同的线程或者是进程Gralloc如何处理这块GraphicBuffer的生命周期。以及不同的GraphicBuffer::HandleWrapMethod,对应的如何处理这个GraphicBuffer的构造和析构
1. GraphicBuffer的使用的sample 1:
对于初学者,可以参考frameworks/native/ui/tests的目录下的测试用例,写一些简单的GraphicBuffer的sample。
#include <ui/GraphicBuffer.h>
#include <utils/Thread.h>
#include <utils/Log.h>
using namespace android;
constexpr uint32_t kTestWidth = 1024;
constexpr uint32_t kTestHeight = 200;
constexpr uint32_t kTestLayerCount = 1;
constexpr uint64_t kTestUsage = GraphicBuffer::USAGE_SW_WRITE_OFTEN;
constexpr PixelFormat format = PIXEL_FORMAT_RGBA_8888;
//填充buffer的数据,以及参数设置
void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
const size_t PIXEL_SIZE = 4;
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
off_t offset = (y * stride + x) * PIXEL_SIZE;
for (int c = 0; c < 4; c++) {
int parityX = (x / (1 << (c+2))) & 1;
int parityY = (y / (1 << (c+2))) & 1;
buf[offset + c] = (parityX ^ parityY) ? 231 : 35;
}
}
}
}
class MyThread : public Thread {
Mutex mLock;
sp<GraphicBuffer> buf;
virtual bool threadLoop() {
Mutex::Autolock l(mLock);
usleep(1000);
//uint8_t* img = nullptr;
//buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
// fillRGBA8Buffer(img, buf->getWidth(), buf->getHeight(), buf->getStride());
//buf->unlock();
//buf.clear();
return false;
}
public:
explicit MyThread(const native_handle_t* inHandle) {
//buf = new GraphicBuffer(inHandle, GraphicBuffer::WRAP_HANDLE, kTestWidth, kTestHeight, format,
kTestLayerCount, kTestUsage, kTestWidth);
buf = new GraphicBuffer(inHandle, GraphicBuffer::CLONE_HANDLE, kTestWidth, kTestHeight, format, kTestLayerCount, kTestUsage, kTestWidth);
ALOGE("test MyThread create buffer successfully");
buf->initCheck();
}
~MyThread() {
ALOGE("test ~MyThread successfully");
}
};
int main (void) {
sp<GraphicBuffer> buf(new GraphicBuffer(kTestWidth, kTestHeight, format, kTestLayerCount, kTestUsage,std::string("test")));
buf->initCheck();
uint8_t* img = nullptr;
buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
MyThread* th = new MyThread(buf->getNativeBuffer()->handle);
th->run("Ruilitest");
fillRGBA8Buffer(img, buf->getWidth(), buf->getHeight(), buf->getStride());
buf->unlock();
}
说明: 以上的sample,大致的意思是,在主线程先创建一个GraphicBuffer,然后把这个native_handle_t传递给MyThread,这个MyThread会创建一个新的GraphicBuffer,那这些GraphicBuffer的构造函数中有一个关键的参数GraphicBuffer::HandleWrapMethod.
2. 关于参数GraphicBuffer::HandleWrapMethod的说明
头文件GraphicBuffer.h中说明如下:
enum HandleWrapMethod : uint8_t {
// Wrap and use the handle directly. It assumes the handle has been
// registered and never fails. The handle must have a longer lifetime
// than this wrapping GraphicBuffer.
//
// This can be used when, for example, you want to wrap a handle that
// is already managed by another GraphicBuffer.
WRAP_HANDLE,
// Take ownership of the handle and use it directly. It assumes the
// handle has been registered and never fails.
//
// This can be used to manage an already registered handle with
// GraphicBuffer.
TAKE_HANDLE,
// Take onwership of an unregistered handle and use it directly. It
// can fail when the buffer does not register. There is no ownership
// transfer on failures.
//
// This can be used to, for example, create a GraphicBuffer from a
// handle returned by Parcel::readNativeHandle.
TAKE_UNREGISTERED_HANDLE,
// Make a clone of the handle and use the cloned handle. It can fail
// when cloning fails or when the buffer does not register. There is
// never ownership transfer.
//
// This can be used to create a GraphicBuffer from a handle that
// cannot be used directly, such as one from hidl_handle.
CLONE_HANDLE,
};
2.2 CLONE_HANDLE
这个method在整个Android的工程中,用的比较多,因为这个比较安全,但是这个也是需要按照一定的规则使用,因为上层调用这个在使用CLONE_HANDLE创建出来的GraphicBuffer。
比如说有些gpu的芯片方实现driver的时候,要求上层要保证gpu使用的时候这个GraphicBuffer的生命周期有效,这个buffer的引用计数不能减到0,因为如果上层还有在用这个handle的时候,gpu释放,根据不同的厂家gpu的实现,出现不同的问题。有些gpu释放GraphicBuffer的时候,对应的handle被lock的话,有些gpu的driver是实现不会处理这个error的信息,只抛一个warning,最种可能导致gralloc上内存泄漏。
接着说CLONE_HANDLE的作用,这个method的使用,是为了保证,进程或者是线程之间传递这个native_handle_t的时候,防止这个handle对应的实际物理内存被别的进程或者线程释放掉,需要调用
native_handle_create 创建一个新的native_handle_t,对这个物理buffer引用+1
其实也是调用了dup的方法,复制出来一个这个handle对应的fd,就是在kernel那边注册下,告诉kernel或者是实际物理内存管理者,这个内存我还在用,需要把这个buffer的引用计数+1,不能被释放。等我不用的时候,把这个
native_handle_close
native_handle_delete的时候,再把这个buffer的引用计数-1。
注:那这块实际的buffer释放,是不确定的,谁最后调用了引用计数-1,正好踩到计数0的话,就可能会真的析构这个物理buffer。
2.1 WRAP_HANDLE
这个method使用需要保证严格的同步, 比如说:
App创建了A handle -> 传递给Hal的线程使用 –> 再返回给App使用
这三个步骤严格统一的话,是可以使用这个method的。
这个method的,不需要重新创建对应的native_handle_t相关的数据结构,也不用去做
native_handle_create –>
native_handle_close((native_handle_t *)psNativeHandle) ->
native_handle_delete((native_handle_t *)psNativeHandle)
因为GraphicBuffer的构造和析构会根据method,决定是否去创建新的native_handle_t,WRAP_HANDLE是表示,只使用这个native_handle_t,不管理这个native_handle_t对应的内存释放。
3. GraphicBuffer的使用的sample 2:
Sample2 简化为三步:
第一步:sp<GraphicBuffer> test1 = new GraphicBuffer(…) -> 创建GraphicBuffer,应该是调用下面的模板类生成sp的指针
sp的模板类如下:
template<typename T>
sp<T>& sp<T>::operator =(T* other) {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
if (other) other->incStrong(this); // 这边调用这个GraphicBuffer的引用+1
if (oldPtr) oldPtr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();//如果在这个sp的类外面有多线程的访问的话,有可能会有另外的线程也调用了赋值的这个函数,会把oldPtr给修改掉这样就有race的问题,因为sp没有在赋值的地方做锁的保护,所以需要在用的地方做线程的同步。
m_ptr = other;
return *this;
}
第二步: test1.clear()的调用如下:
template<typename T>
void sp<T>::clear() {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
if (oldPtr) {
oldPtr->decStrong(this); //在这边会有引用计数-1的操作
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();//同样也会有冲突的check
m_ptr = nullptr;
}
}
第三步:test1 = nullptr的话,跟第一个sp指针赋值的过程相同,同样也会调用引用计数-1
总之:谁踩到了减到0 的点,谁就会接着调用GraphicBuffer的析构,这个跟物理buffer的析构不是一个引用,因为物理buffer由驱动或者kernel管理生命周期。
4. GraphicBuffer::getNativeBuffer()的函数说明
ANativeWindowBuffer* GraphicBuffer::getNativeBuffer() const
{
return static_cast<ANativeWindowBuffer*>(const_cast<GraphicBuffer*>(this));
}
打印GraphicBuffer的指针和ANativeWindowBuffer的指针,但是返回的地址不是相同的,会有一定的偏移。
GraphicBuffer=0xef9701e0, ANativeWindowBuffer= 0xef9701e8
这个应该是c++中类继承的时候强转的计算,要做偏移之后才能指向AANativeWindowBuffer
5. GraphicBuffer::free_handle()函数说明
void GraphicBuffer::free_handle()
{
ALOGE("GFX GraphicBuffer::free_handle() this = %p, handle = %p", this, handle);
if (mOwner == ownHandle) { // 对应CLONE_HANDLE的流程
mBufferMapper.freeBuffer(handle);
} else if (mOwner == ownData) {//这个看code里面没有调用的地方
GraphicBufferAllocator& allocator(GraphicBufferAllocator::get());
allocator.free(handle); //这边也是会调用到mBufferMapper.freeBuffer(handle)
}
handle = nullptr;
}
6. GraphicBuffer::lock / GraphicBuffer::unlock
读写这个buffer之前需要先lock这个buffer,map出来一个虚拟地址,写完或者是读完之后,需要unlock释放。
lock和unlock的使用是需要配对,否则会有虚拟内存异常的问题或者是gpu的driver异常。
总结:
GraphicBuffer是可以进程间共享的buffer,进程间传递的时候,除了一些GraphicBuffer中宽高参数传递之外,还有一个重要的参数,就是这个buffer对应的fd。GraphicBuffer对应的物理buffer,在kernel中有对应的管理,也就是物理buffer的引用计数,对于上层不同的应用,每个应用都可以用一个fd对应这个物理buffer,这个fd在不同的进程中也不一定相同。上面说的native_handle_create 就是生成一个新的fd,也就是对这个物理buffer的引用计数+1。(这个是从别的博客和跟同事讨论中获取的一些信息,可能也表述的不够精确,但是fd大概的意思就这样,嘿嘿)