Ashmem: Anonymous Shared Memory 是 Android 提供的一种共享内存的机制,它基于 mmap 系统调用,不同进程可以将同一段物理内存映射到各自的虚拟地址控制实现共享,因此进程间不需要再拷贝数据。
特点
Android 系统提供了独特的匿名共享内存子系统 Ashmem,它以驱动程序的形式实现在内核空间中,有两个典型特点:
- 能够辅助内存管理系统来有效地管理不再使用的内存块
- 通过
Binder进程间通信机制来实现进程间的内存共享
Linux 共享内存通信效率非常高,进程间不需要传递数据,便可以直接访问,缺点也很明显,Linux 共享内存没有提供同步的机制,在使用时要借助其他的手段来处理进程间同步。所以在 Android 系统中实现的匿名共享内存 Ashmem 驱动中添加了互斥锁,另外通过传递文件描述符来实现共享内存的传递。
源码速查表
1 | Framework: |
System 层
函数列表
Ashmem 匿名共享内存,全都是通过 System 层和 Driver 层交互的。函数列表:
1 | // ashmem.h |
使用到的宏
函数列表中值使用到了这些 ioctl 命令宏:
1 |
驱动
文件路径
1 | ./drivers/staging/android/ashmem.c |
Ashmem 结构体
1 | /* |
name
表示这块共享内存的名字,这个名字会显示/proc/<pid>/maps文件中,<pid>表示打开这个共享内存文件的进程ID。unpinned_list
是一个列表头,它把这块共享内存中所有被解锁的内存块连接在一起,和内存块的锁定和解锁操作有关。file
表示这个共享内存在临时文件系统tmpfs中对应的文件,在内核决定要把这块共享内存对应的物理页面回收时,就会把它的内容交换到这个临时文件中去。size
表示这块共享内存的大小。prot_mask
表示这块共享内存的访问保护位。
关键函数
1 | // 打开 Ashmem 设备 |
匿名共享内存文件进行内存映射操作,对匿名内存文件内容的读写操作就像访问内存变量一样,驱动不用参与到读写操作中。
pin/unpin 锁定和解锁内存区
1 |
|
匿名共享内存的锁定和解锁操作,表示哪些内存块是正在使用的,需要锁定(pin);哪些内存是不需要使用了,解除锁定(unpin)。创建匿名共享内存时,默认所有的内存都是 pinned 状态的,用户先告诉 Ashmem 驱动程序要解锁某一块内存,内核可以将它对应的物理页面回收;因为 unpin 操作并不会改变已经 mmap 的地址空间,所以之后用户可以再告诉驱动程序要重新锁定某一块之前被解锁过的内块,从而修改这块内存的状态。也就是说,执行锁定前,目标对象必须是一块当前处于解锁状态的内存块。
匿名共享内存 Ashmem 机制是建立在 Linux 内核实现的共享内存的基础上的。同时它又向 Linux 内存管理系统的内存回收算法注册接口,系统内存不足时,会回收 Ashmem 区域中状态是 unpin 的对象内存块,如果不希望对象被回收,可以通过 pin 来保护它。
Java 层接口及应用
MemoryFile
所有 Java 代码都是通过 MemoryFile 来使用匿名共享内存 Ashmem 的,它是唯一的入口。
不过在 Andoid O 开始匿名共享内存推荐使用 SharedMemory。Api: Applications should generally prefer to use SharedMemory which offers more flexible access & control over the shared memory region than MemoryFile does.
重要字段:
1 | private FileDescriptor mFD; // ashmem file descriptor |
调用流程
App --> (Framework) MemoryFile.java --> (Native) android_os_MemoryFile.cpp --> (System) ashmem-dev.c --> (Driver)ashmem.c

序列图中可以看到,Java 接口文件直接通过 JNI 调用了系统 System 的函数,通过它来和驱动做数据交互。
如何使用
思路:通过 Binder 机制,传递匿名共享机制文件描述符,通过该描述符实现进程间的数据共享。
在 Linux 系统中,文件描述符其实就是一个整数。每一个进程在内核空间都有一个打开文件的数组,这个文件描述符的整数值就是用来索引这个数组的,而且这个文件描述符只是在本进程内有效,也就是说,在不同的进程中,相同的文件描述符的值,代表的可能是不同的打开文件。因此在进程间传输文件描述符时,并不是简要把一个文件描述符从一个进程传给另外一个进程,中间通过 Binder 机制做过转换,映射了相同的地址空间,使得它和源进程的文件描述符所对应的打开文件是一致的,这样才能保证共享。文件描述符进程间转换图:

AIDL文件
通过Binder机制,传递匿名共享机制文件描述符。1
2
3interface IAshmemFd {
ParcelFileDescriptor getParcelFileDescriptor();
}Server服务端
利用Ashmem机制,创建MemoryFile文件后,写入数据。注意:这里需要使用反射机制调用getFileDescriptor因为它是@hide的,也就是说,Android系统并不推荐这种使用方式。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24private ParcelFileDescriptor getParcelFileDescriptor(){
Log.d(TAG, "getParcelFileDescriptor: ");
ParcelFileDescriptor pfd = null;
try {
// method 1: Ashmem: MemoryFile.
MemoryFile file =new MemoryFile(ASHMEM_FILENAME,ASHMEM_LENGTH);
file.getOutputStream().write(testData);
Method method = MemoryFile.class
.getDeclaredMethod("getFileDescriptor");
FileDescriptor des = (FileDescriptor) method.invoke(file);
pfd = ParcelFileDescriptor.dup(des);
} catch (Exception e){
Log.e(TAG, "getParcelFileDescriptor: ", e);
}
return pfd;
}
private IBinder mBinder = new IAshmemFd.Stub() {
public ParcelFileDescriptor getParcelFileDescriptor()
throws RemoteException {
return AshmemService.this.getParcelFileDescriptor();
}
};Client客户端
客户端通过Binder拿到ParcelFileDescriptor文件描述符后,读取MemoryFile文件的内容并打印。1
2
3
4
5
6
7
8
9
10
11ParcelFileDescriptor pfd = mIAshmemFd.getParcelFileDescriptor();
FileInputStream fileInputStream =
new FileInputStream(pfd.getFileDescriptor());
byte[] content = new byte[10];
fileInputStream.read(content);
StringBuilder builder = new StringBuilder();
for (byte b : content){
builder.append(b);
}
String value = builder.toString();
Log.d(TAG, "getData: value = " + value);
总结
Android O 之后推荐使用 SharedMemory,之前的版本也几乎没有看到 MemoryFile 的应用。网上搜到的案例是 AIDL 传递文件描述符 ParcelFileDescriptor,而仅仅传递它其实并不需要使用 Ashmem,因为 ParcelFileDescriptor 中推荐直接使用 createPipe 创建管道。Server 服务端使用管道实现的示例:
1 | // method 2: Pipe |
Native 层接口及应用
类图结构
1 | IMemory.h: IMemoryHeap, BnMemoryHeap, IMemory, BnMemory |

可以看出,Native 层的 Ashmem 本身就是基于 Binder 机制的。
MemoryHeapBase
用于在进程间共享一个完整的匿名共享内存块。MemoryBase
用于在进程间共享一个匿名共享内存块中其中的一部分。MemoryBase接口是建立在MemoryHeapBase接口的基础上面的,它们都可以作为一个Binder对象来在进程间传输。
IMemoryHeap 定义的重要方法
1 | // 获得匿名共享内存块的文件描述符 |
MemoryHeapBase 服务端
MemoryHeapBase 继承了 BnMemoryHeap,是 IMemoryHeap 的本地实现类,实现了具体的函数功能,用于服务端。
1 | MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name) |
mFD:匿名共享内存文件描述符mBase:匿名共享内存起始地址mSize:匿名共享内存的大小

HeapCache 缓存
HeapCache 继承了 IBinder::DeathRecipient,维护生命周期。
1 | class HeapCache : public IBinder::DeathRecipient |
heap_info_t结构体heap保存了BpMemoryHeap对象;count引用计数,表示被引用了多少次,只有在等于 1 时,才能被释放。mHeapCache缓存KeyedVector容器类型,存储了以IBinder和heap_info_t的键值对。find_heap查找
在mHeapCache中查找,如果找不到则添加到mHeapCache中。get_heap获取
在mHeapCache中查找,如果找不到则直接将IBinder转换,并没有加入mHeapCache中。gHeapCache全局实例gHeapCache全局实例,维护了本进程中所有的BpMemoryHeap引用对象。
BpMemoryHeap 客户端
BpMemoryHeap 是 IMemoryHeap 的代理类,用于客户端。Binder 机制中客户端通过代理类访问服务端的具体实现。
1 | virtual int getHeapID() const; |
在获取当前匿名共享内存信息时,都会先执行 assertMapped/assertReallyMapped 断言函数,确保获取到服务端的匿名共享内存信息后并映射到了当前进程。
IMemory 的定义
IMemory 是用来管理进程间匿名共享内存块 IMemoryHeap 中的一部分,所以这个类中保存了 IMemoryHeap 代理实例,以及被管理的匿名共享内存这部分的基地址,大小,在整个 Ashmem 中的偏移量。
- 头文件定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14class IMemory : public IInterface
{
public:
...
// 获取匿名共享内存客户端代理实例 BpMemoryHeap
virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const = 0;
void* fastPointer(const sp<IBinder>& heap, ssize_t offset) const;
// 获取匿名共享内存的基地址
void* pointer() const;
// 获取匿名共享内存的大小
size_t size() const;
// 维护的这部分共享内存,在整个匿名共享内存中的偏移量
ssize_t offset() const;
}; - 具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 从缓存中快速获取被管理的 Ashmem 基地址:MemoryHeap的基地址 + 偏移量
void* IMemory::fastPointer(const sp<IBinder>& binder, ssize_t offset) const
{
sp<IMemoryHeap> realHeap = BpMemoryHeap::get_heap(binder);
void* const base = realHeap->base();
if (base == MAP_FAILED)
return 0;
return static_cast<char*>(base) + offset;
}
// 从具体实现类中获取被管理的 Ashmem 基地址:MemoryHeap的基地址 + 偏移量
void* IMemory::pointer() const {
ssize_t offset;
sp<IMemoryHeap> heap = getMemory(&offset);
void* const base = heap!=0 ? heap->base() : MAP_FAILED;
if (base == MAP_FAILED)
return 0;
return static_cast<char*>(base) + offset;
}
// 被管理 Ashmem 的大小
size_t IMemory::size() const {
size_t size;
getMemory(NULL, &size);
return size;
}
// 被管理 Ashmem 的偏移量
ssize_t IMemory::offset() const {
ssize_t offset;
getMemory(&offset);
return offset;
}
MemoryBase 服务端
MemoryBase 继承 BpMemory,是 IMemory 的本地实现类,用于服务端。
1 | MemoryBase::MemoryBase(const sp<IMemoryHeap>& heap, |
MemoryBase 类非常简单,构造函数中传入了匿名共享内存的具体实现 MemoryHeapBase 对象,以及被管理部分的偏移量和大小。getMemory 简单的返回了整个匿名共享内存对象及对应的偏移量和大小,所以可以理解 MemoryBase 是 MemoryHeapBase 的简单封装。
BpMemory 客户端
1 | sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const |
通过 Binder 机制,从服务端读取偏移量,大小,并返回 BpMemory 代理实例(mHeap)。
Ashmem 总结
- 文件描述符
在形式上是一个非负整数,实际上它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。 - 进程间共享原理
Ashmem进程间共享原理:两个在不同进程中的文件描述符对应同一个指向设备文件/dev/ashmem的文件结构体。Binder机制在数据交互时大小不能超过 1M,可以通过传递匿名共享内存(Ashmem)的文件描述符或者IBinder对象(IMemoryHeap/IMemory),来实现大数据的共享。 - 优缺点
匿名共享内存不会占用Heap,不会导致OOM,但是如果肆意使用,会导致系统资源不足性能下降。 - 应用场景
在camera.preview预览,SurfaceFlinger绘制等,可以看到匿名共享内存的应用。