0. 原理简介
在Android平台上,提供了一种共享内存的机制——Ashmem。该机制内部其实复用了Linux的共享内存机制。Ashmem机制使用linux的mmap系统调用,可以将同一段物理内存映射到不同进程各自的虚拟地址空间,从而实现高效的进程间共享。
在linux上“一切皆文件”,一块共享内存当然也不例外。因此,在用户态,我们能看到的重要概念就是共享内存的“文件描述符”,文件描述符可以对应一个内核态的ashmem file。file中又可以管理自己的逻辑数据(ashmem_area)。不同进程里的不同文件描述符可以对应同一个内核态的file,这就是跨进程共享的基础。当我们对这个文件描述符做完mmap操作后,就可以对这段内存进行读写操作,进而反应到另一个mmap此文件的进程。
大概原理如下图所示,Ashmem能够实现跨进程内存共享的一个关键点在于文件描述符的跨进程传输,为此安卓封装了一个native_handle 数据结构用于文件描述符在Binder驱动中传递,Binder在传递文件描述符时会转换为目标进程可用的文件描述符。
native_handle的结构如下,其中data[0]一般就是文件描述符fd。不同模块对data[0] 的填充方式不一样,hidl_memory 中直接填充为fd, surface则填充为private_handle_t,private_handle_t 结构第一个字段也是fd。因此,只要正确的使用了native_handle,也就正确使用了Ashmem。
/system/core/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] */
int data[0]; /* numFds + numInts ints */
} native_handle_t;
1. 接口使用(C++)
1.0 接口简介
0x00 数据结构
-
typedef const native_handle_t* buffer_handle_t; native_handle.h
-
hidl_handle是HAL层对native_handle的一个类封装,本质上等同于native_handle,可以使用getNativeHandle()方法拿到对应的native_handle。HidlSupport.cpp
-
hidl_memory是对hidl_handle的包装。HidlSupport.cpp
这三种数据就够本质上都是native_handle的一个封装,便于在各个场景使用。
0x01 ashmem_create_region()
ashmem_create_region() 是用户态的最后一层实现,其他创建共享内存的接口最终都是调用该接口实现。安卓Q以前版本的共享内存设备都是/dev/ashmem,但是安卓Q及以上版本新增了一个memfd实现。这块没太看懂,也不明白有什么区别,希望了解的大佬可以指点一二。
int ashmem_create_region(const char *name, size_t size)
{
int ret, save_errno;
if (has_memfd_support()) { // 如果NDK版本大于29(安卓Q)并且sys.use_memfd=ture并且系统支持
return memfd_create_region(name ? name : "none", size);
// syscall(__NR_memfd_create, name, MFD_ALLOW_SEALING)
}
int fd = __ashmem_open(); //open /dev/ashmem[boot-id],传统ashmem方式,即字符设备。
if (fd < 0) {
return fd;
}
ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, name));
...
ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));
...
return fd;
}
0x02 IAllocator
IAllocator 是安卓HAL层提供的一个用于分配ashmem的服务,最常使用的allocateallocate(uint64_t size, allocate_cb _hidl_cb)方法,实际调用的就是ashmem_create_region(“name”,size)分配ashmem内存。并通过回调返回hidl_memory对象。
Return<void> AshmemAllocator::allocate(uint64_t size, allocate_cb _hidl_cb)
{
hidl_memory memory = allocateOne(size); //调用实际功能函数
_hidl_cb(memory.handle() != nullptr /* success */, memory);
cleanup(std::move(memory));
return Void();
}
static hidl_memory allocateOne(uint64_t size)
{
int fd = ashmem_create_region("AshmemAllocator_hidl", size); //分配内存
native_handle_t *handle = native_handle_create(1, 0); //创建 native_handle_t对象并填充data[0]域
handle->data[0] = fd;
return hidl_memory("ashmem", handle, size);
}
0x03 IMemory
HAL层对内存的实际更改必须通过 IMemory 对象完成(要么在创建 mem 的一端完成,要么在通过 HIDL RPC 接收它的一端完成)。具体实现为AshmemMemory,但是除了析构会unmap之外,实际没做其他的操作,不知道是否有其他的实现。
struct AshmemMemory : public IMemory { ... };
AshmemMemory::AshmemMemory(const hidl_memory &memory, void *data)
: mMemory(memory),mData(data){}
AshmemMemory::~AshmemMemory() // TODO: Move implementation to mapper class
{ munmap(mData, mMemory.size()); }
Return<void> AshmemMemory::commit() // NOOP (since non-remoted memory)
{ return Void(); }
Return<void *> AshmemMemory::getPointer(){return mData;}
Return<uint64_t> AshmemMemory::getSize(){return mMemory.size();}
0x04 mapMemory
// Methods from ::android::hidl::memory::V1_0::IMapper follow.
Return<sp<IMemory>> AshmemMapper::mapMemory(const hidl_memory &mem)
{
if (mem.handle()->numFds == 0) {
return nullptr;
}
// If ashmem service runs in 32-bit (size_t is uint32_t) and a 64-bit
// client process requests a memory > 2^32 bytes, the size would be
// converted to a 32-bit number in mmap. mmap could succeed but the
// mapped memory's actual size would be smaller than the reported size.
if (mem.size() > SIZE_MAX) {
ALOGE("Cannot map %" PRIu64 " bytes of memory because it is too large.", mem.size());
android_errorWriteLog(0x534e4554, "79376389");
return nullptr;
}
int fd = mem.handle()->data[0];
void *data = mmap(0, mem.size(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
// mmap never maps at address zero without MAP_FIXED, so we can avoid
// exposing clients to MAP_FAILED.
return nullptr;
}
return new AshmemMemory(mem, data);
}
1.1 简单用法
0x00 内存分配
内存分配一般使用Allocator服务,通过lambda闭包获取分配的hidl_memory对象,基本形式如下。
#include <android/hidl/allocator/1.0/IAllocator.h>
#include <android/hidl/memory/1.0/IMemory.h>
#include <hidlmemory/mapping.h>
using ::android::hidl::allocator::V1_0::IAllocator;
using ::android::hidl::memory::V1_0::IMemory;
using ::android::hardware::hidl_memory;
....
sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem");
ashmemAllocator->allocate(2048, [&](bool success, const hidl_memory& mem) {
if (!success) { /* error */ }
// now you can use the hidl_memory object 'mem' or pass it around
}));
0x01内存读写
对共享内存的修改基本都是先mapMemory()函数map对应的hidl_memory,通过getPointer()获取首地址,继而进行读写操作。谷歌建议读写内存之前,先进行update()操作,写完内存之后进行commit()操作。基本形式如下:
// Same includes as above
sp<IMemory> memory = mapMemory(mem);
void* data = memory->getPointer();
memory->update();
// update memory however you wish after calling update and before calling commit
data[0] = 42;
memory->commit();
// …
memory->update(); // the same memory can be updated multiple times
// …
memory->commit();
0x02 内存共享
通过hidl接口传递共享内存的一般形式如下,其中memory对应的hidl类型是hidl_memory, handle对应hidl_handle。一般建议使用第二种,因为谷歌从安卓R才开始有memory的java支持(HidlMemory),而至少安卓Q开始就有handle的Java支持(NativeHandle)。
/**
* get hidl memory fd
*/
@callflow(next={"*"})
getMemory(int32_t size) generates(memory mem);
/*
* get hidl memory fd
*/
@callflow(next={"*"})
getMemory(int32_t size) generates(handle memory_ptr);
同样也是通过匿名函数返回hidl_handle对象。向目标进程传递mhandle, binder驱动在传递hidl_handle 类型时,会将hidl_handle 内包含的文件句柄转换为目标进程的,可通过dup() 函数延长文件句柄 fd 的声明周期。dup函数的具体意义还没有研究清楚 ,ԾㅂԾ,
Return<void> Foo::getMemory(int32_t size,getMemory_cb _hidl_cb){
native_handle_t* mHidlHandle = native_handle_clone(mem.handle()); // 获取native_hendle
//buffer_handle_t mHidlHandle = native_handle_clone(mem.handle());
hidl_handle mhandle(mHidlHandle);
_hidl_cb(mhandle);
//_hidl_cb(mHidlHandle);
return Void();
}
目标进程拿到hidl_handle 后就可以进行map操作,进而操作共享内存。
hidl_memory mHidlHeap = hidl_memory("ashmem", mhandle->getNativeHandle(), size);
sp<IMemory> mHidlHeapMemory = mapMemory(mHidlHeap);
if (mHidlHeapMemory == nullptr) {
ALOGE("memory map failed!");
native_handle_close(mHidlHandle); // close FD for the shared memory
native_handle_delete(mHidlHandle);
mHidlHeap = hidl_memory();
mHidlHandle = nullptr;
return;
}
直接调用ashmem_get_size_region() 和mmap() 的方式。
const native_handle_t *native_handle = mhandle.getNativeHandle();
mFd = native_handle->data[0];
memcpy(data, get_fd_data(mFd, data_size), data_size);
char *get_fd_data(int fd, int len)
{
int error = 0;
char *data = NULL;
int size = ashmem_get_size_region(fd);
LOGD("size: %d ,len: %d", size, len);
if (size < 0 || size < en) {
LOGE("error share memory size");
}
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
error = errno;
LOGE("mmap failed: %s", strerror(error));
} else {
data = (char *)ptr;
//LOGD("data: %s", data);
}
return data;
}
2. 接口使用(Java)
2.0 接口简介
0x00 HidlMemory、NativeHandle
安卓R java层开始支持handle 和 memory,使用方法同c++ 基本类似。其实handle 类型在Q就已经支持。
Android R 引入了对 handle 和 memory 类型的 Java 支持。这两种类型分别转换为 android.os.NativeHandle 和 android.os.HidlMemory。null 句柄会被视为有效,而 null 内存则不会。
在生成的服务器代码中,接收的内存和句柄参数仅在方法调用范围内有效。如果服务器实现希望延长其生命周期,则必须使用其各自的 dup() 方法进行复制。返回的实例可在方法调用范围之外使用,且应在使用后正确关闭。
在生成的客户端代码中,作为调用的方法的输入参数发送的句柄和内存实例无需进行复制,也无需在方法返回后保持有效状态。但是,作为输出参数接收的句柄和内存实例将由自动生成的代码自动复制,且必须在复制完成后正> 确关闭。无论这些返回参数显示为方法的返回值(在存在单个返回值的情况下),还是使用同步回调样式(用于存在多个返回值的情况),均是如此。
如需详细了解复制与关闭,请参阅 Java 类文档。
使用方法同C++很类似,通过 getFileDescriptor 获取文件句柄fd,然后 map 即可,HidlMemory 的用法可参考源码。其中也包含了NativeHandle 的用法。
0x01 SharedMemory 和 MemoryFlie
安卓java层为共享内存封装了一个SharedMemory 类,其中包含了共享内存常用的接口,MemoryFlie是对SharedMemory 的一个封装,谷歌建议直接使用SharedMemory 。
实际使用中一般是调用SharedMemory.create()方法创建一块共享内存,create()实现如下,ncreate()是一个静态方法,对应的C++实现为SharedMemory_nCreate(),最终也是调用了ashmem_create_region()接口创建共享内存。
public static @NonNull SharedMemory create(@Nullable String name, int size)throws ErrnoException {
if (size <= 0)
{
throw new IllegalArgumentException("Size must be greater than zero");
}
return new SharedMemory(nCreate(name, size));
}
private SharedMemory(FileDescriptor fd)
{
// This constructor is only used internally so it should be impossible to hit any of the
// exceptions unless something goes horribly wrong.
...
mFileDescriptor = fd;
mSize = nGetSize(mFileDescriptor);
if (mSize <= 0) {
throw new IllegalArgumentException("FileDescriptor is not a valid ashmem fd");
}
mMemoryRegistration = new MemoryRegistration(mSize);
mCleaner = Cleaner.create(mFileDescriptor,
new Closer(mFileDescriptor, mMemoryRegistration));
}
jobject SharedMemory_nCreate(JNIEnv *env, jobject, jstring jname, jint size)
{ ...
int fd = ashmem_create_region(name, size);
...
return jniCreateFileDescriptor(env, fd);
}
2.1 简单使用
SharedMemory()并没有以FileDescriptor为参数的构造方法,所以如果要使用FileDescriptor构造一个SharedMemory()对象的话,只能使用反射的方式