Ashmem匿名共享内存是Android的Linux内核实现的一个驱动,它以驱动程序的形式实现在内核空间,用于进程间数据共享。
Binder内存缓冲区空间最大是4M,无法满足一次性大数据的传输,当AndroidUI渲染时,我们需要将待渲染的Surface数据发送到SurfaceFlinger进程进行绘制,就用到Ashmem。
Ashmem 初始化
Ashmem的初始化是通过ashmen_init完成的
// kernel/goldfish/mm/ashmem.c
static struct kmem_cache *ashmem_area_cachep __read_mostly;
// Ashmem 驱动初始化
static int __init ashmem_init(void)
{
int ret;
// 创建了 kmem_cache, 用于分配 ashmem_area 结构体的内存
ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
sizeof(struct ashmem_area),
0, 0, NULL);
.......
return 0;
}
通过kmem_cache_create函数创建了一个kmem_cache结构体对象。kmem_cache描述物理内存缓冲区,(内核中描述进程的task_struct也是通过kmem_cache来快速分配物理页面的)。
ashmem_cache的定义
// kernel/goldfish/mm/ashmem.c
#define ASHMEM_NAME_PREFIX "dev/ashmem/"
#define ASHMEM_NAME_PREFIX_LEN (sizeof(ASHMEM_NAME_PREFIX) - 1)
#define ASHMEM_FULL_NAME_LEN (ASHMEM_NAME_LEN + ASHMEM_NAME_PREFIX_LEN)
/**
* ashmem_area - 描述一块共享内存区域的结构体
* Lifecycle: From our parent file's open() until its release()
* Locking: 受到 `ashmem_mutex' 互斥锁的保护
* Big Note: Mappings do NOT pin this structure; it dies on close()
*/
struct ashmem_area {
// 描述共享内存的名字, 名字会显示 /proc/<pid>/maps 文件中
// pid 表示打开这个共享内存文件的进程ID
char name[ASHMEM_FULL_NAME_LEN];
// 描述一个链表头, 它把这块共享内存中所有被解锁的内存块连接在一起
struct list_head unpinned_list;
// 描述这个共享内存在临时文件系统 tmpfs 中对应的文件
// 在内核决定要把这块共享内存对应的物理页面回收时,就会把它的内容交换到这个临时文件中去
struct file *file;
// 描述共享内存块的大小
size_t size;
// 描述这块共享内存的访问保护位
unsigned long prot_mask;
};
Ashmem open系统调用
// system/core/libcutils/ashmem-dev.cpp
static int __ashmem_open()
{
int fd;
pthread_mutex_lock(&__ashmem_lock);
fd = __ashmem_open_locked();
pthread_mutex_unlock(&__ashmem_lock);
return fd;
}
#define ASHMEM_DEVICE "/dev/ashmem"
/* logistics of getting file descriptor for ashmem */
static int __ashmem_open_locked()
{
......
// 调用了 open 函数获取文件描述符
int fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR | O_CLOEXEC));
......
return fd;
}
我们在用户空间调用了open函数打开设备文件 ASHMEM_DEVICE 时,该设备文件的路径为 "/dev/ashmem"。最终会触发Linux内核中的ashmem_open的调用
// kernel/goldfish/mm/ashmem.c
#define ASHMEM_NAME_PREFIX "dev/ashmem/"
#define ASHMEM_NAME_PREFIX_LEN (sizeof(ASHMEM_NAME_PREFIX) - 1)
// 驱动初始化时, 创建的缓冲区
static struct kmem_cache *ashmem_area_cachep __read_mostly;
static int ashmem_open(struct inode *inode, struct file *file)
{
struct ashmem_area *asma;
......
// 1. 通过 kmem_cache 创建一个 ashmem_area 结构体
asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
// 设置 ashmem_area 的其他字段
// 初始化链表头
INIT_LIST_HEAD(&asma->unpinned_list);
// 给 name 属性设置固定前缀 "dev/ashmem/"
memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);
......
// 将结构体保存在设备文件的 private_data 中
file->private_data = asma;
return 0;
}
ashmem_open主要完成:
- 通过kmem_cache创建一个ashmem_area结构体,用于描述这个ashmem共享内存
- 将这个Ashmem共享内存描述保存到设备文件的private_data中
Ashmem mmap系统调用
ashmem_mmap函数的定义
// kernel/goldfish/mm/ashmem.c
static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
// 1. 从这个驱动文件的 private_data 中获取 ashmem_area 结构体
struct ashmem_area *asma = file->private_data;
int ret = 0;
mutex_lock(&ashmem_mutex);
......
if (!asma->file) {
char *name = ASHMEM_NAME_DEF;
struct file *vmfile;
if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')
name = asma->name;
// 2. 通过 shmem_file_setup 创建一个共享内存文件
vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
// 3. 将这个共享内存文件保存在 asma 对象中
asma->file = vmfile;
}
get_file(asma->file);
// 4. 将这个共享内存文件 映射到 用户的虚拟地址空间上
if (vma->vm_flags & VM_SHARED)
shmem_set_file(vma, asma->file);
else {
if (vma->vm_file)
fput(vma->vm_file);
vma->vm_file = asma->file;
}
......
mutex_unlock(&ashmem_mutex);
return ret;
}
ashmem_mmap 函数主要事情:
- 获取ashmem_area结构体对象(用于描述匿名共享内存)
- 通过ashmem_file_setup创建共享内存文件 vmfile
- 将创建的共享文件 vmfile 保存在 ashmem_area 的file内
- 将这个共享文件 映射到用户态的虚拟地址空间上
ashmem_mmap 操作完成后,用户空间就可以通过往文件中读写,从而将数据写入到共享内存文件中了
如何使用匿名共享内存? Native端 参考MMKV 的 MemoryFile
#pragma mark - ashmem
#include "native-bridge.h"
#include <dlfcn.h>
#define ASHMEM_NAME_LEN 256
#define __ASHMEMIOC 0x77
#define ASHMEM_SET_NAME _IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN])
#define ASHMEM_GET_NAME _IOR(__ASHMEMIOC, 2, char[ASHMEM_NAME_LEN])
#define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t)
#define ASHMEM_GET_SIZE _IO(__ASHMEMIOC, 4)
void *loadLibrary() {
auto name = "libandroid.so";
static auto handle = dlopen(name, RTLD_LAZY | RTLD_LOCAL);
if (handle == RTLD_DEFAULT) {
MMKVError("unable to load library %s", name);
}
return handle;
}
typedef int (*AShmem_create_t)(const char *name, size_t size);
int ASharedMemory_create(const char *name, size_t size) {
int fd = -1;
// Android 8.0 以上使用 libandroid.so 的 ASharedMemory_create 创建
if (g_android_api >= __ANDROID_API_O__) {
static auto handle = loadLibrary();
static AShmem_create_t funcPtr =
(handle != nullptr)
? reinterpret_cast<AShmem_create_t>(dlsym(handle, "ASharedMemory_create"))
: nullptr;
if (funcPtr) {
fd = funcPtr(name, size);
if (fd < 0) {
MMKVError("fail to ASharedMemory_create %s with size %zu, errno:%s", name, size,
strerror(errno));
}
} else {
MMKVWarning("fail to locate ASharedMemory_create() from loading libandroid.so");
}
}
// Android 8.0 以下, 直接操作 "dev/ashmem" 驱动文件
if (fd < 0) {
fd = open(ASHMEM_NAME_DEF, O_RDWR);
if (fd < 0) {
MMKVError("fail to open ashmem:%s, %s", name, strerror(errno));
} else {
// 设置共享内存区域的名称
if (ioctl(fd, ASHMEM_SET_NAME, name) != 0) {
MMKVError("fail to set ashmem name:%s, %s", name, strerror(errno));
}
// 设置共享内存区域的大小
else if (ioctl(fd, ASHMEM_SET_SIZE, size) != 0) {
MMKVError("fail to set ashmem:%s, size %zu, %s", name, size, strerror(errno));
}
}
}
return fd;
}
因为文件是多个进程共享的,只要两个进程都通过mmap映射到这个文件,那么两个进程就可以通过共享内存进行通信了。
Java端
// 创建 10 MB 的 Ashmem 共享内存
val memoryFile = MemoryFile("test shared memory", 10 * 1024 * 1024)
// 反射获取实现对象 mSharedMemory, 继承自 Parcelable
val sharedMemory = reflectObject(memoryFile, "mSharedMemory")
// 通过 Binder 驱动, 将 mSharedMemory 发送到另一个进程即可使用
Ashmem 总结
Ashmem与Binder一样,都是通过驱动的形式存在于Linux内核中,Ashmem主要操作如下:
- init : 创建用于分配小内存的kmem_cache(有多大?),方便进行ashmem_area结构体的内存分配
- open : 打开驱动设备文件,创建ashmem_area描述一块匿名共享内存,将其保存到共享内存驱动 fd 的private中
- ioctl
- ASHMEM_SET_NAME : 为这块匿名共享内存设置文件名(只能在mmap之前调用)
- ASHMEM_SET_SIZE : 为这块匿名共享内存设置文件大小 (只能在mmap之前调用)
- mmap
- 创建一个基于内存文件系统的共享内存文件 vmfile
- 将这个 vmfile 文件映射到用户空间的虚拟地址上