Ashmem(Android共享内存)使用方法和原理

简介

Ashmem即Android Shared Memory, 是Android提供的一种内存共享的机制。

使用

  • Java层借助MemoryFile或者SharedMemory
  • Native层借助MemoryHeapBase或者MemoryBase
  • Native层直接调用libc的ashmem_create_regionmmap系统调用。

MemoryFile基于SharedMemoryMemoryBase基于MemoryHeapBaseSharedMemoryMemoryHeapBase都是基于ashmem_create_region/mmap

MemoryFile

MemoryFile是对SharedMemory的包装,官方推荐直接使用SharedMemory。

Applications should generally prefer to use {@link SharedMemory} which offers more flexible access & control over the shared memory region than MemoryFile does.

SharedMemory

SharedMemory只能通过调用SharedMemory.create静态方法或者通过Parcel反序列化的方式进行创建。 SharedMemory的创建进程通过SharedMemory.create创建,使用进程通过Parcel反序列化创建。

因为SharedMemory类实现了Parcelable,所以可以通过binder跨进程传输。

MemoryBase和MemoryHeapBase

MemoryBase是对MemoryHeapBase的包装。MemoryHeapBase对应一块共享内存,使用ashmem_create_region/mmap创建,MemoryHeapBase内部保存了共享内存的地址和大小。通过MemoryBase可以获取其包装的MemoryHeapBase

MemoryBaseMemoryHeapBase都是Binder本地对象(BBinder),可以直接传到其他进程。其他进程分别使用IMemoryIMemoryHeap进行跨进程调用。

MemoryHeapBase跨进程传输本质上传输的是共享内存的fd,fd在经过binder驱动时会被转换成目标进程的fd,MemoryHeapBase的客户端代理对象BpMemoryHeap在创建时候会将fd映射到自己的内存空间,这样客户端进程在使用IMemoryHeap接口获取到的内存地址就是自己进程空间的地址。

ashmem_create_region 和 mmap

ashmem_create_region/mmap是SharedMemory和MemoryHeapBase的实现基础。

int ashmem_create_region(const char *name, size_t size)

用于创建共享内存,函数内部首先通过open函数打开/dev/ashmem设备,得到文件描述符后,通过调用ioctl设置fd的名称和大小。

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

通过binder将fd传递到其他进程后,其他进程可以通过mmap系统调用,将共享内存映射到当前进程的地址空间,之后就可以通过返回的内存首地址进行内存读写。

这样,两个进程之间就实现了直接的内存共享,获得了极高的进程间通信效率。

Ashmem的Pin和Unpin

ashmem驱动提供了两个用于内存管理的ioctl操作命令:pin/unpin,直接通过ashmem_create_region创建的共享内存默认是pined的状态,也就是说,应用程序不主动关闭共享内存fd的情况下,这篇内存会始终保留,直到进程死亡。

如果调用unpin将共享内存中的某段内存解除锁定,之后如果系统内存不足,会自动释放这部分内存,再次使用同一段内存前应该先执行pin操作,如果pin操作返回ASHMEM_WAS_PURGED,也就是说内存已经被回收,已经回收的内存再次访问会触发缺页中断重新进行物理内存的分配,因此这段内存里的数据已经不是起初的那个数据了,如果仍旧当做原始数据进行访问必然引发错误。

通过pin/unpin命令,配合ashmem驱动,可以进行简单的内存管理。

原理

Ashmem的核心原理主要是两部分:驱动和fd传递。

驱动

Ashmem是Linux内核中的一个misc设备,对应的设备文件是/dev/ashmem,此设备是一个虚拟设备,不存在实际文件,只在内核驱动中对应一个inode节点。Ashmem在驱动层是基于linux系统的共享内存功能实现的,Ashmem可以理解为只是对原生的共享内存进行了一层包装,使其更方便在Android系统上使用。

ashmem设备文件支持如下操作:

// /drivers/staging/android/ashmem.c
809static const struct file_operations ashmem_fops = {
810 .owner = THIS_MODULE,
811 .open = ashmem_open,
812 .release = ashmem_release,
813 .read = ashmem_read,
814 .llseek = ashmem_llseek,
815 .mmap = ashmem_mmap,
816 .unlocked_ioctl = ashmem_ioctl,
817#ifdef CONFIG_COMPAT
818 .compat_ioctl = compat_ashmem_ioctl,
819#endif
820};

ashmem创建:(从Java层到驱动层的调用链)

[java] android.os.SharedMemory#create
[jni] /frameworks/base/core/jni/android_os_SharedMemory.cpp#SharedMemory_create
[libc] /system/core/libcutils/ashmem-dev.c#ashmem_create_region
[driver] /drivers/staging/android/ashmem.c#ashmem_open
ashmem_open

ashmem_open中只是创建了一个标识ashmem的结构体,然后返回fd,并没有进行实际的内存分配(无论是虚拟内存还是物理内存)。 得到文件描述符后,就可以使用ashmem_mmap将内核中的共享内存区域映射到进程的虚拟地址空间。

ashmem_mmap

ashmem_mmap通过调用内核中shmem相关函数在tempfs创建了一个大小等于创建ashmem时传入大小的临时文件(由于是内存文件,所以磁盘上不存在实际的文件),然后将文件对应的内存映射到调用mmap的进程。(注意map的是临时文件而不是ashmem文件)

其中涉及到的shmem函数包括shmem_file_setupshmem_set_file,他们为该临时文件创建inode节点,将文件关联到为该文件配的虚拟内存,同时为该文件设置自己的文件操作函数(Linux共享内存shmem的文件操作),并为虚拟内存设置缺页处理函数。这样后续对共享内存的操作就变为了对tempfs文件节点的操作。当首次访问共享内存时触发缺页中断处理函数并为该虚拟内存分配实际的物理内存。

tempfs是Unix-like系统中一种基于内存的文件系统,具有极高的访问效率。
shmem是Linux自带的进程间通信机制:共享内存Shared Memory
共享内存的虚拟文件记录在/proc/<pid>/maps文件中,pid表示打开这个共享内存文件的进程ID。

ashmem_pin/ashmem_unpin

pinunpinashmemioctl支持的两个操作,用于共享内存的分块使用和分块回收,用于节省实际的物理内存。 新创建的共享内存默认都是pined的,当调用unpin时,驱动将unpined的内存区域所在的页挂在一个unpinned_list链表上,后续内存回收就是基于unpinned_list链表进行。

ashmem驱动初始化函数ashmem_init里调用了内核函数register_shrinker,注册了一个内存回收回调函数ashmem_shrink,当系统内存紧张时,就会回调ashmem_shrink,由驱动自身进行适当的内存回收。驱动就是在ashmem_shrink中遍历unpinned_list进行内存回收,以释放物理内存。

ashmem fd的传递:

fd通过Binder传递。

Binder机制不仅支持binder对象的传递,还支持文件描述符的传递。fd经过binder驱动时,binder驱动会将源进程的fd转换成目标进程的fd,转换过程为:取出发送方binder数据里的fd,通过fd找到文件对象,然后为目标进程创建fd,将目标进程fd和文件对象进行关联,将发送方binder数据里的fd改为目标进程的fd,然后将数据发送给目标进程。这个过程相当于文件在目标进程又打开了一次,目标进程使用的是自己的fd,但和源进程都指向的是同一个文件。这样源进程和目标进程就都可以map到同一片内存了。

使用场景

  • 进程间共享体积较大的数据,比如bitmap。
  • 提升进程间传输数据的效率,比如ContentProvider基于共享内存进行数据传送。
  • 借助Bitmap解码的inPurgeable属性,在android4.x及以下系统版本中实现内存在ashmem中分配,以节省Java堆内存。比如fresco图片加载库针对Android4.x及以下的机型对inPurgeable属性的使用。

总结

Ashmem通过对Linux共享内存的扩展,一方面使其使用更简单,另一方面使其只能通过binder传递,增加了安全性。Ashmem在Android系统中起着非常重要的作用,比如整个显示系统App-WMS-SurfaceFlinger之间就是通过Ashmem传递的帧数据。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269