上一篇文章介绍了 GraphicBuffer 初始化的 initWithSize() 函数中的申请内存流程,这里我们看一下另一个比较重要的函数,GraphicBufferMapper. getTransportSize 获取内存信息。该函数通常在需要了解缓冲区的实际内存占用情况时调用,例如在调试内存使用情况或优化性能时。
一、函数解析
GraphicBufferMapper 的 getTransportSize 方法是用于获取一个 GraphicBuffer 实例的实际传输大小。这是因为图形缓冲区的实际大小可能由于对齐、填充等因素而大于其逻辑大小(即 width * height * 像素大小)。getTransportSize 方法返回的是实际用于传输的字节数,这对于理解缓冲区的内存占用以及在进行 DMA(直接内存访问)操作时特别有用。
1、GraphicBufferMapper
源码位置:/frameworks/native/libs/ui/GraphicBufferMapper.cpp
void GraphicBufferMapper::getTransportSize(buffer_handle_t handle, uint32_t* outTransportNumFds, uint32_t* outTransportNumInts)
{
mMapper->getTransportSize(handle, outTransportNumFds, outTransportNumInts);
}
源码位置:/frameworks/native/libs/ui/include/ui/GraphicBufferMapper.h
std::unique_ptr<const GrallocMapper> mMapper;
可以看到这里的 mMapper 是 GrallocMapper,而 Gralloc4Mapper 继承 GrallocMapper。
2、Gralloc4
源码位置:/frameworks/native/libs/ui/include/ui/Gralloc4.h
#include <android/hardware/graphics/mapper/4.0/IMapper.h>
class Gralloc4Mapper : public GrallocMapper {
……
sp<hardware::graphics::mapper::V4_0::IMapper> mMapper;
}
源码位置:/frameworks/native/libs/ui/Gralloc4.cpp
void Gralloc4Mapper::getTransportSize(buffer_handle_t bufferHandle, uint32_t* outNumFds,
uint32_t* outNumInts) const {
*outNumFds = uint32_t(bufferHandle->numFds);
*outNumInts = uint32_t(bufferHandle->numInts);
Error error;
auto buffer = const_cast<native_handle_t*>(bufferHandle);
auto ret = mMapper->getTransportSize(buffer, [&](const auto& tmpError, const auto& tmpNumFds,
const auto& tmpNumInts) {
error = tmpError;
if (error != Error::NONE) {
return;
}
*outNumFds = tmpNumFds;
*outNumInts = tmpNumInts;
});
error = (ret.isOk()) ? error : kTransactionError;
ALOGE_IF(error != Error::NONE, "getTransportSize(%p) failed with %d", buffer, error);
}
这里的 mMapper 是一个 4.0 版本的 IMapper 接口,而 GrallocMapper 又继承自 IMapper,所以这里调用的其实就是 GrallocMapper 中的 getTransportSize() 函数。
3、GrallocMapper
源码位置:/hardware/google/gchips/gralloc4/src/4.x/GrallocMapper.h
class GrallocMapper : public IMapper
源码位置:/hardware/google/gchips/gralloc4/src/4.x/GrallocMapper.cpp
Return<void> GrallocMapper::getTransportSize(void *buffer, getTransportSize_cb hidl_cb)
{
common::getTransportSize(buffer, hidl_cb);
return Void();
}
这里有调用了通用类中的 getTransportSize() 函数。
4、Mapper
源码位置:hardware/google/gchips/GrallocHAL/src/hidl_common/Mapper.h
void getTransportSize(void *buffer, IMapper::getTransportSize_cb hidl_cb);
源码位置:hardware/google/gchips/GrallocHAL/src/hidl_common/Mapper.cpp
void getTransportSize(void* buffer, IMapper::getTransportSize_cb hidl_cb)
{
/* 检查缓冲区注册状态,缓冲区必须是由Gralloc分配的 */
buffer_handle_t bufferHandle = gRegisteredHandles->get(buffer);
……
// 验证缓冲区句柄
if (private_handle_t::validate(bufferHandle) < 0)
{
MALI_GRALLOC_LOGE("Buffer %p is corrupted", buffer);
hidl_cb(Error::BAD_BUFFER, -1, -1);
return;
}
// 返回传输大小信息
hidl_cb(Error::NONE, bufferHandle->numFds, bufferHandle->numInts);
}
该函数是用于查询通过 Gralloc 分配的缓冲区的传输大小信息的,主要目的是为了在 HIDL 环境中获取缓冲区的文件描述符数量和整型元数据的数量,这对于跨进程共享缓冲区非常重要。
bufferHandle->numFds:表示缓冲区关联的文件描述符数量,这通常是共享内存区域的标识符。
bufferHandle->numInts:表示缓冲区关联的整型元数据数量,这些元数据可能包含了缓冲区的尺寸、格式等关键信息。
其中 buffer_handle_t 是 Gralloc 中用于表示缓冲区句柄的类型,而 private_handle_t 是其具体实现之一,包含了缓冲区的详细信息,如文件描述符和元数据。他们都继承自 native_handle_t 。
private_handle_t
源码位置:/hardware/google/gchips/gralloc4/src/mali_gralloc_buffer4.h
#include <cutils/native_handle.h>
struct private_handle_t;
struct private_handle_t : public native_handle
{
private_handle_t(
int _flags,
uint64_t _alloc_sizes[MAX_BUFFER_FDS],
uint64_t _consumer_usage, uint64_t _producer_usage,
int _fds[MAX_FDS], int _fd_count,
int _req_format, uint64_t _alloc_format,
int _width, int _height, int _stride,
uint64_t _layer_count, plane_info_t _plane_info[MAX_PLANES])
: private_handle_t()
{
flags = _flags; // 标记位
fd_count = _fd_count; // 文件描述符数量
width = _width; // 缓冲区宽度
height = _height; // 缓冲区高度
req_format = _req_format; // 请求格式
producer_usage = _producer_usage; // 生产者的使用模式
consumer_usage = _consumer_usage; // 消费者的使用模式
stride = _stride;
alloc_format = _alloc_format; // 分配格式
layer_count = _layer_count;
version = sizeof(native_handle);
// 设置文件描述符的数量
set_numfds(fd_count);
memcpy(plane_info, _plane_info, sizeof(plane_info_t) * MAX_PLANES);
// fds和alloc_sizes数组的内存复制
if (_fds)
memcpy(fds, _fds, sizeof(fds));
if (_alloc_sizes)
memcpy(alloc_sizes, _alloc_sizes, sizeof(alloc_sizes));
memset(bases, 0, sizeof(bases));
memset(ion_handles, 0, sizeof(ion_handles));
}
}
可以看到这里定义了 private_handle_t 结构体,它是 native_handle 的一个子类,这意味着它继承了native_handle 的所有属性和功能,并添加了一些特定于图形缓冲区管理的成员变量。
native_handle
源码位置:/system/core/libcutils/include/cutils/native_handle.h
typedef struct native_handle
{
int version; /* sizeof(native_handle_t) */
int numFds; /* number of file-descriptors at &data[0] */
int numInts; /* number of ints at &data[numFds] */
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wzero-length-array"
#endif
int data[0]; /* numFds + numInts ints */
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
} native_handle_t;
native_handle_t 结构体是 Android 系统中实现跨进程通信(IPC)的关键组件,特别是在需要传递文件描述符或共享资源的情况下。native_handle_t 结构体的设计充分考虑了跨进程通信的需求和现代编译器的特性,是 Android 系统中实现高效、安全的资源共享和通信的重要基石。
二、跨进程通信
前面说过 GraphicBuffer 的数据太大,没有办法进行 Binder 通信,那么这里如何实现跨进程通信的呢?我们先去图元生产者的基类 IGraphicBufferProducer 的远程端。
1、IGraphicBufferProducer.cpp
源码位置:/frameworks/native/libs/gui/IGraphicBufferProducer.cpp
class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
{
public:
explicit BpGraphicBufferProducer(const sp<IBinder>& impl)
: BpInterface<IGraphicBufferProducer>(impl)
{
}
~BpGraphicBufferProducer() override;
virtual status_t requestBuffer(int bufferIdx, sp<GraphicBuffer>* buf) {
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
data.writeInt32(bufferIdx);
status_t result = remote()->transact(REQUEST_BUFFER, data, &reply);
if (result != NO_ERROR) {
return result;
}
bool nonNull = reply.readInt32();
if (nonNull) {
*buf = new GraphicBuffer();
result = reply.read(**buf);
if(result != NO_ERROR) {
(*buf).clear();
return result;
}
}
result = reply.readInt32();
return result;
}
……
}
其实就是在 App 进程中创建 GraphicBuffer 对象,但是这个对象通常不会立即向 ION(内存分配器)申请内存。而是调用了read 的方法,继续解压缩 reply 返回的数据包。因为 GraphicBuffer 是一个序列化(Flatten)对象,可以通过 flatten 方法转化为字节流,然后通过 Binder 通信发送。在接收端(App进程),通过 unflatten 方法从字节流中恢复 GraphicBuffer 对象。因此会走到 GraphicBuffer 的 unflatten 方法。
2、GraphicBuffer
源码位置:/frameworks/native/libs/ui/GraphicBuffer.cpp
status_t GraphicBuffer::unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count) {
……
int const* buf = static_cast<int const*>(buffer);
……
if (numFds || numInts) {
……
native_handle* h = native_handle_create(static_cast<int>(numFds), static_cast<int>(numInts));
……
memcpy(h->data, fds, numFds * sizeof(int));
memcpy(h->data + numFds, buf + flattenWordCount, numInts * sizeof(int));
handle = h;
} else {
……
}
mId = static_cast<uint64_t>(buf[7]) << 32;
mId |= static_cast<uint32_t>(buf[8]);
mGenerationNumber = static_cast<uint32_t>(buf[9]);
mOwner = ownHandle;
if (handle != nullptr) {
buffer_handle_t importedHandle;
status_t err = mBufferMapper.importBuffer(handle, uint32_t(width), uint32_t(height),
uint32_t(layerCount), format, usage, uint32_t(stride), &importedHandle);
……
native_handle_close(handle);
native_handle_delete(const_cast<native_handle_t*>(handle));
handle = importedHandle;
mBufferMapper.getTransportSize(handle, &mTransportNumFds, &mTransportNumInts);
}
buffer = static_cast<void const*>(static_cast<uint8_t const*>(buffer) + sizeNeeded);
size -= sizeNeeded;
fds += numFds;
count -= numFds;
return NO_ERROR;
}
这里获得 buffer_handle_t 句柄,接着调用 importBuffer 方法将这个句柄进一步转换为 private_handle_t。此时一般是 deqeue 方法中调用,GraphicBuffer 对象被从队列中取出,但还没有调用 lock 方法,因此 App 进程尚未直接访问共享内存,我们来看看 Mapper 的 lock() 方法。
3、Mapper
源码位置:/hardware/google/gchips/gralloc4/src/hidl_common/Mapper.cpp
void lock(void* buffer, uint64_t cpuUsage, const IMapper::Rect& accessRegion,
const hidl_handle& acquireFence, IMapper::lock_cb hidl_cb)
{
buffer_handle_t bufferHandle = gRegisteredHandles->get(buffer);
if (!bufferHandle || private_handle_t::validate(bufferHandle) < 0)
{
MALI_GRALLOC_LOGE("Buffer to lock: %p is not valid", buffer);
hidl_cb(Error::BAD_BUFFER, nullptr);
return;
}
int fenceFd;
if (!getFenceFd(acquireFence, &fenceFd))
{
hidl_cb(Error::BAD_VALUE, nullptr);
return;
}
void* data = nullptr;
const Error error = lockBuffer(bufferHandle, cpuUsage, accessRegion, fenceFd, &data);
hidl_cb(error, data);
}
lock方法是GraphicBuffer与共享内存关联的关键。在进行 mmap 共享内存绑定之前,会先查找已经在缓存的 buffer_handle_t 句柄,这个句柄关联了 App 进程和 SurfaceFlinger 进程中的 GraphicBuffer。通过 lock,App 进程可以将共享内存映射到自己的地址空间,从而直接访问图像数据。