簡介
Ashmem即Android Shared Memory, 是Android提供的一種內(nèi)存共享的機(jī)制泽台。
使用
- Java層借助
MemoryFile
或者SharedMemory
矾缓。 - Native層借助
MemoryHeapBase
或者MemoryBase
嗜闻。 - Native層直接調(diào)用libc的
ashmem_create_region
和mmap
系統(tǒng)調(diào)用桅锄。
MemoryFile
基于SharedMemory
。MemoryBase
基于MemoryHeapBase
友瘤。SharedMemory
、MemoryHeapBase
都是基于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
只能通過調(diào)用SharedMemory.create
靜態(tài)方法或者通過Parcel反序列化的方式進(jìn)行創(chuàng)建邮旷。 SharedMemory
的創(chuàng)建進(jìn)程通過SharedMemory.create
創(chuàng)建蝇摸,使用進(jìn)程通過Parcel反序列化創(chuàng)建。
因為SharedMemory
類實現(xiàn)了Parcelable
律歼,所以可以通過binder跨進(jìn)程傳輸蜂嗽。
MemoryBase和MemoryHeapBase
MemoryBase
是對MemoryHeapBase
的包裝。MemoryHeapBase
對應(yīng)一塊共享內(nèi)存辱揭,使用ashmem_create_region/mmap
創(chuàng)建病附,MemoryHeapBase
內(nèi)部保存了共享內(nèi)存的地址和大小。通過MemoryBase
可以獲取其包裝的MemoryHeapBase
域庇。
MemoryBase
和MemoryHeapBase
都是Binder本地對象(BBinder),可以直接傳到其他進(jìn)程听皿。其他進(jìn)程分別使用IMemory
和IMemoryHeap
進(jìn)行跨進(jìn)程調(diào)用。
MemoryHeapBase
跨進(jìn)程傳輸本質(zhì)上傳輸?shù)氖枪蚕韮?nèi)存的fd尉姨,fd在經(jīng)過binder驅(qū)動時會被轉(zhuǎn)換成目標(biāo)進(jìn)程的fd,MemoryHeapBase
的客戶端代理對象BpMemoryHeap
在創(chuàng)建時候會將fd映射到自己的內(nèi)存空間九府,這樣客戶端進(jìn)程在使用IMemoryHeap
接口獲取到的內(nèi)存地址就是自己進(jìn)程空間的地址覆致。
ashmem_create_region 和 mmap
ashmem_create_region
/mmap
是SharedMemory和MemoryHeapBase的實現(xiàn)基礎(chǔ)。
int ashmem_create_region(const char *name, size_t size)
用于創(chuàng)建共享內(nèi)存儡羔,函數(shù)內(nèi)部首先通過open
函數(shù)打開/dev/ashmem
設(shè)備声旺,得到文件描述符后,通過調(diào)用ioctl
設(shè)置fd的名稱和大小鉴扫。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
通過binder將fd傳遞到其他進(jìn)程后澈缺,其他進(jìn)程可以通過mmap
系統(tǒng)調(diào)用,將共享內(nèi)存映射到當(dāng)前進(jìn)程的地址空間莱预,之后就可以通過返回的內(nèi)存首地址進(jìn)行內(nèi)存讀寫项滑。
這樣,兩個進(jìn)程之間就實現(xiàn)了直接的內(nèi)存共享枪狂,獲得了極高的進(jìn)程間通信效率。
Ashmem的Pin和Unpin
ashmem驅(qū)動提供了兩個用于內(nèi)存管理的ioctl
操作命令:pin/unpin
辜限,直接通過ashmem_create_region
創(chuàng)建的共享內(nèi)存默認(rèn)是pined
的狀態(tài)严蓖,也就是說氧急,應(yīng)用程序不主動關(guān)閉共享內(nèi)存fd的情況下吩坝,這篇內(nèi)存會始終保留,直到進(jìn)程死亡钾恢。
如果調(diào)用unpin
將共享內(nèi)存中的某段內(nèi)存解除鎖定鸳址,之后如果系統(tǒng)內(nèi)存不足稿黍,會自動釋放這部分內(nèi)存崩哩,再次使用同一段內(nèi)存前應(yīng)該先執(zhí)行pin操作,如果pin操作返回ASHMEM_WAS_PURGED
酣栈,也就是說內(nèi)存已經(jīng)被回收汹押,已經(jīng)回收的內(nèi)存再次訪問會觸發(fā)缺頁中斷重新進(jìn)行物理內(nèi)存的分配,因此這段內(nèi)存里的數(shù)據(jù)已經(jīng)不是起初的那個數(shù)據(jù)了棚贾,如果仍舊當(dāng)做原始數(shù)據(jù)進(jìn)行訪問必然引發(fā)錯誤。
通過pin/unpin
命令铸史,配合ashmem
驅(qū)動怯伊,可以進(jìn)行簡單的內(nèi)存管理。
原理
Ashmem的核心原理主要是兩部分:驅(qū)動和fd傳遞崭篡。
驅(qū)動
Ashmem是Linux內(nèi)核中的一個misc設(shè)備猩系,對應(yīng)的設(shè)備文件是/dev/ashmem
,此設(shè)備是一個虛擬設(shè)備寇甸,不存在實際文件疗涉,只在內(nèi)核驅(qū)動中對應(yīng)一個inode節(jié)點咱扣。Ashmem在驅(qū)動層是基于linux系統(tǒng)的共享內(nèi)存功能實現(xiàn)的涵防,Ashmem可以理解為只是對原生的共享內(nèi)存進(jìn)行了一層包裝,使其更方便在Android系統(tǒng)上使用壮池。
ashmem
設(shè)備文件支持如下操作:
// /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創(chuàng)建:(從Java層到驅(qū)動層的調(diào)用鏈)
[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
中只是創(chuàng)建了一個標(biāo)識ashmem
的結(jié)構(gòu)體椰憋,然后返回fd,并沒有進(jìn)行實際的內(nèi)存分配(無論是虛擬內(nèi)存還是物理內(nèi)存)证舟。 得到文件描述符后窗骑,就可以使用ashmem_mmap
將內(nèi)核中的共享內(nèi)存區(qū)域映射到進(jìn)程的虛擬地址空間。
ashmem_mmap
ashmem_mmap
通過調(diào)用內(nèi)核中shmem
相關(guān)函數(shù)在tempfs創(chuàng)建了一個大小等于創(chuàng)建ashmem
時傳入大小的臨時文件(由于是內(nèi)存文件抵知,所以磁盤上不存在實際的文件),然后將文件對應(yīng)的內(nèi)存映射到調(diào)用mmap的進(jìn)程。(注意map的是臨時文件而不是ashmem
文件)
其中涉及到的shmem
函數(shù)包括shmem_file_setup
和shmem_set_file
辛藻,他們?yōu)樵撆R時文件創(chuàng)建inode節(jié)點互订,將文件關(guān)聯(lián)到為該文件配的虛擬內(nèi)存,同時為該文件設(shè)置自己的文件操作函數(shù)(Linux共享內(nèi)存shmem的文件操作)氮墨,并為虛擬內(nèi)存設(shè)置缺頁處理函數(shù)吐葵。這樣后續(xù)對共享內(nèi)存的操作就變?yōu)榱藢empfs文件節(jié)點的操作。當(dāng)首次訪問共享內(nèi)存時觸發(fā)缺頁中斷處理函數(shù)并為該虛擬內(nèi)存分配實際的物理內(nèi)存猛铅。
tempfs
是Unix-like系統(tǒng)中一種基于內(nèi)存的文件系統(tǒng)凤藏,具有極高的訪問效率堕伪。
shmem
是Linux自帶的進(jìn)程間通信機(jī)制:共享內(nèi)存Shared Memory
栗菜。
共享內(nèi)存的虛擬文件記錄在/proc/<pid>/maps
文件中,pid表示打開這個共享內(nèi)存文件的進(jìn)程ID富俄。
ashmem_pin/ashmem_unpin
pin
和unpin
是ashmem
的ioctl
支持的兩個操作而咆,用于共享內(nèi)存的分塊使用和分塊回收,用于節(jié)省實際的物理內(nèi)存暴备。 新創(chuàng)建的共享內(nèi)存默認(rèn)都是pined的,當(dāng)調(diào)用unpin
時,驅(qū)動將unpined的內(nèi)存區(qū)域所在的頁掛在一個unpinned_list
鏈表上玛痊,后續(xù)內(nèi)存回收就是基于unpinned_list
鏈表進(jìn)行。
在ashmem
驅(qū)動初始化函數(shù)ashmem_init
里調(diào)用了內(nèi)核函數(shù)register_shrinker
混弥,注冊了一個內(nèi)存回收回調(diào)函數(shù)ashmem_shrink
对省,當(dāng)系統(tǒng)內(nèi)存緊張時,就會回調(diào)ashmem_shrink
蒿涎,由驅(qū)動自身進(jìn)行適當(dāng)?shù)膬?nèi)存回收。驅(qū)動就是在ashmem_shrink
中遍歷unpinned_list
進(jìn)行內(nèi)存回收仓手,以釋放物理內(nèi)存玻淑。
ashmem fd的傳遞:
fd通過Binder傳遞。
Binder機(jī)制不僅支持binder對象的傳遞添坊,還支持文件描述符的傳遞。fd經(jīng)過binder驅(qū)動時贬蛙,binder驅(qū)動會將源進(jìn)程的fd轉(zhuǎn)換成目標(biāo)進(jìn)程的fd,轉(zhuǎn)換過程為:取出發(fā)送方binder數(shù)據(jù)里的fd速客,通過fd找到文件對象,然后為目標(biāo)進(jìn)程創(chuàng)建fd岔擂,將目標(biāo)進(jìn)程fd和文件對象進(jìn)行關(guān)聯(lián)浪耘,將發(fā)送方binder數(shù)據(jù)里的fd改為目標(biāo)進(jìn)程的fd,然后將數(shù)據(jù)發(fā)送給目標(biāo)進(jìn)程七冲。這個過程相當(dāng)于文件在目標(biāo)進(jìn)程又打開了一次,目標(biāo)進(jìn)程使用的是自己的fd蝉稳,但和源進(jìn)程都指向的是同一個文件掘鄙。這樣源進(jìn)程和目標(biāo)進(jìn)程就都可以map到同一片內(nèi)存了。
使用場景
- 進(jìn)程間共享體積較大的數(shù)據(jù)操漠,比如bitmap。
- 提升進(jìn)程間傳輸數(shù)據(jù)的效率撞秋,比如ContentProvider基于共享內(nèi)存進(jìn)行數(shù)據(jù)傳送嚣鄙。
- 借助Bitmap解碼的inPurgeable屬性,在android4.x及以下系統(tǒng)版本中實現(xiàn)內(nèi)存在ashmem中分配哑子,以節(jié)省Java堆內(nèi)存。比如fresco圖片加載庫針對Android4.x及以下的機(jī)型對inPurgeable屬性的使用剧蹂。
總結(jié)
Ashmem
通過對Linux共享內(nèi)存的擴(kuò)展烦却,一方面使其使用更簡單,另一方面使其只能通過binder傳遞冒冬,增加了安全性。Ashmem
在Android系統(tǒng)中起著非常重要的作用简烤,比如整個顯示系統(tǒng)App-WMS-SurfaceFlinger之間就是通過Ashmem
傳遞的幀數(shù)據(jù)。