閱讀之前,不妨先思考一個(gè)問(wèn)題,在Android系統(tǒng)中,APP端View視圖的數(shù)據(jù)是如何傳遞SurfaceFlinger服務(wù)的呢?View繪制的數(shù)據(jù)最終是按照一幀一幀顯示到屏幕的,而每一幀都會(huì)占用一定的存儲(chǔ)空間,在APP端執(zhí)行draw的時(shí)候,數(shù)據(jù)很明顯是要繪制到APP的進(jìn)程空間,但是視圖窗口要經(jīng)過(guò)SurfaceFlinger圖層混排才會(huì)生成最終的幀,而SurfaceFlinger又運(yùn)行在另一個(gè)獨(dú)立的服務(wù)進(jìn)程,那么View視圖的數(shù)據(jù)是如何在兩個(gè)進(jìn)程間傳遞的呢,普通的Binder通信肯定不行,因?yàn)锽inder不太適合這種數(shù)據(jù)量較大的通信,那么View數(shù)據(jù)的通信采用的是什么IPC手段呢?答案就是共享內(nèi)存,更精確的說(shuō)是匿名共享內(nèi)存。共享內(nèi)存是Linux自帶的一種IPC機(jī)制,Android直接使用了該模型,不過(guò)做出了自己的改進(jìn),進(jìn)而形成了Android的匿名共享內(nèi)存(Anonymous Shared Memory-Ashmem)。通過(guò)Ashmem,APP進(jìn)程同SurfaceFlinger共用一塊內(nèi)存,如此,就不需要進(jìn)行數(shù)據(jù)拷貝,APP端繪制完畢,通知SurfaceFlinger端合成,再輸出到硬件進(jìn)行顯示即可,當(dāng)然,個(gè)中細(xì)節(jié)會(huì)更復(fù)雜,本文主要分析下匿名共享內(nèi)存的原理及在Android中的特性,下面就來(lái)看下個(gè)中細(xì)節(jié),不過(guò)首先看一下Linux的共享內(nèi)存的用法,簡(jiǎn)單了解下:

Linux共享內(nèi)存
首先看一下兩個(gè)關(guān)鍵函數(shù),
- int shmget(key_t key, size_t size, int shmflg); 該函數(shù)用來(lái)創(chuàng)建共享內(nèi)存
- void *shmat(int shm_id, const void *shm_addr, int shmflg); 要想訪問(wèn)共享內(nèi)存,必須將其映射到當(dāng)前進(jìn)程的地址空間
參考網(wǎng)上的一個(gè)demo,簡(jiǎn)單的看下,其中key_t是共享內(nèi)存的唯一標(biāo)識(shí),可以說(shuō),Linux的共享內(nèi)存其實(shí)是有名共享內(nèi)存,而名字就是key,具體用法如下
讀取進(jìn)程
int main()
{
void *shm = NULL;//分配的共享內(nèi)存的原始首地址
struct shared_use_st *shared;//指向shm
int shmid;//共享內(nèi)存標(biāo)識(shí)符
//創(chuàng)建共享內(nèi)存
shmid = shmget((key_t)12345, sizeof(struct shared_use_st), 0666|IPC_CREAT);
//將共享內(nèi)存映射到當(dāng)前進(jìn)程的地址空間
shm = shmat(shmid, 0, 0);
//設(shè)置共享內(nèi)存
shared = (struct shared_use_st*)shm;
shared->written = 0;
//訪問(wèn)共享內(nèi)存
while(1){
if(shared->written != 0) {
printf("You wrote: %s", shared->text);
if(strncmp(shared->text, "end", 3) == 0)
break;
}}
//把共享內(nèi)存從當(dāng)前進(jìn)程中分離
if(shmdt(shm) == -1) { }
//刪除共享內(nèi)存
if(shmctl(shmid, IPC_RMID, 0) == -1) { }
exit(EXIT_SUCCESS);
}
寫(xiě)進(jìn)程
int main()
{
void *shm = NULL;
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ + 1];//用于保存輸入的文本
int shmid;
//創(chuàng)建共享內(nèi)存
shmid = shmget((key_t) 12345, sizeof(struct shared_use_st), 0666|IPC_CREAT);
//將共享內(nèi)存連接到當(dāng)前進(jìn)程的地址空間
shm = shmat(shmid, (void*)0, 0);
printf("Memory attached at %X\n", (int)shm);
//設(shè)置共享內(nèi)存
shared = (struct shared_use_st*)shm;
while(1)//向共享內(nèi)存中寫(xiě)數(shù)據(jù)
{
//數(shù)據(jù)還沒(méi)有被讀取,則等待數(shù)據(jù)被讀取,不能向共享內(nèi)存中寫(xiě)入文本
while(shared->written == 1)
{
sleep(1);
}
//向共享內(nèi)存中寫(xiě)入數(shù)據(jù)
fgets(buffer, BUFSIZ, stdin);
strncpy(shared->text, buffer, TEXT_SZ);
shared->written = 1;
if(strncmp(buffer, "end", 3) == 0)
running = 0;
}
//把共享內(nèi)存從當(dāng)前進(jìn)程中分離
if(shmdt(shm) == -1) { }
sleep(2);
exit(EXIT_SUCCESS);
}
可以看到,Linux共享內(nèi)存通信效率非常高,進(jìn)程間不需要傳遞數(shù)據(jù),便可以直接訪問(wèn),缺點(diǎn)也很明顯,Linux共享內(nèi)存沒(méi)有提供同步的機(jī)制,在使用時(shí),要借助其他的手段來(lái)處理進(jìn)程間同步。Anroid本身在核心態(tài)是支持System V的功能,但是bionic庫(kù)刪除了glibc的shmget等函數(shù),使得android無(wú)法采用shmget的方式實(shí)現(xiàn)有名共享內(nèi)存,當(dāng)然,它也沒(méi)想著用那個(gè),Android在此基礎(chǔ)上,創(chuàng)建了自己的匿名共享內(nèi)存方式。
Android的匿名共享內(nèi)存
Android可以使用Linux的一切IPC通信方式,包括共享內(nèi)存,不過(guò)Android主要使用的方式是匿名共享內(nèi)存Ashmem(Anonymous Shared Memory),跟原生的不太一樣,比如它在自己的驅(qū)動(dòng)中添加了互斥鎖,另外通過(guò)fd的傳遞來(lái)實(shí)現(xiàn)共享內(nèi)存的傳遞。MemoryFile是Android為匿名共享內(nèi)存而封裝的一個(gè)對(duì)象,這里通過(guò)使用MemoryFile來(lái)分析,Android中如何利用共享內(nèi)存來(lái)實(shí)現(xiàn)大數(shù)據(jù)傳遞,同時(shí)MemoryFile也是進(jìn)程間大數(shù)據(jù)傳遞的一個(gè)手段,開(kāi)發(fā)的時(shí)候可以使用:
IMemoryAidlInterface.aidl
package com.snail.labaffinity;
import android.os.ParcelFileDescriptor;
interface IMemoryAidlInterface {
ParcelFileDescriptor getParcelFileDescriptor();
}
MemoryFetchService
public class MemoryFetchService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MemoryFetchStub();
}
static class MemoryFetchStub extends IMemoryAidlInterface.Stub {
@Override
public ParcelFileDescriptor getParcelFileDescriptor() throws RemoteException {
MemoryFile memoryFile = null;
try {
memoryFile = new MemoryFile("test_memory", 1024);
memoryFile.getOutputStream().write(new byte[]{1, 2, 3, 4, 5});
Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);
return ParcelFileDescriptor.dup(des);
} catch (Exception e) {}
return null;
}}}
TestActivity.java
Intent intent = new Intent(MainActivity.this, MemoryFetchService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
byte[] content = new byte[10];
IMemoryAidlInterface iMemoryAidlInterface
= IMemoryAidlInterface.Stub.asInterface(service);
try {
ParcelFileDescriptor parcelFileDescriptor = iMemoryAidlInterface.getParcelFileDescriptor();
FileDescriptor descriptor = parcelFileDescriptor.getFileDescriptor();
FileInputStream fileInputStream = new FileInputStream(descriptor);
fileInputStream.read(content);
} catch (Exception e) {
}}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Service.BIND_AUTO_CREATE);
以上是應(yīng)用層使用匿名共享內(nèi)存的方法,關(guān)鍵點(diǎn)就是文件描述符(FileDescriptor)的傳遞,文件描述符是Linux系統(tǒng)中訪問(wèn)與更新文件的主要方式。從MemoryFile字面上看出,共享內(nèi)存被抽象成了文件,不過(guò)本質(zhì)也是如此,就是在tmpfs臨時(shí)文件系統(tǒng)中創(chuàng)建一個(gè)臨時(shí)文件,(只是創(chuàng)建了節(jié)點(diǎn),而沒(méi)有看到實(shí)際的文件) 該文件與Ashmem驅(qū)動(dòng)程序創(chuàng)建的匿名共享內(nèi)存對(duì)應(yīng),可以直接去proc/pid下查看:

下面就基于MemoryFile主要分析兩點(diǎn),共享內(nèi)存的分配與傳遞,先看下MemoryFile的構(gòu)造函數(shù)
public MemoryFile(String name, int length) throws IOException {
mLength = length;
mFD = native_open(name, length);
if (length > 0) {
mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
} else {
mAddress = 0;
}
}
可以看到 Java層只是簡(jiǎn)單的封裝,具體實(shí)現(xiàn)在native層 ,首先是通過(guò)native_open調(diào)用ashmem_create_region創(chuàng)建共享內(nèi)存,
static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length)
{
const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL);
int result = ashmem_create_region(namestr, length);
if (name)
env->ReleaseStringUTFChars(name, namestr);
if (result < 0) {
jniThrowException(env, "java/io/IOException", "ashmem_create_region failed");
return NULL;
}
return jniCreateFileDescriptor(env, result);
}
接著通過(guò)native_mmap調(diào)用mmap將共享內(nèi)存映射到當(dāng)前進(jìn)程空間,之后Java層就能利用FileDescriptor,像訪問(wèn)文件一樣訪問(wèn)共享內(nèi)存。
static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,
jint length, jint prot)
{
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
<!--系統(tǒng)調(diào)用mmap,分配內(nèi)存-->
jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0);
if (!result)
jniThrowException(env, "java/io/IOException", "mmap failed");
return result;
}
ashmem_create_region這個(gè)函數(shù)是如何向Linux申請(qǐng)一塊共享內(nèi)存的呢?
int ashmem_create_region(const char *name, size_t size)
{
int fd, ret;
fd = open(ASHMEM_DEVICE, O_RDWR);
if (fd < 0)
return fd;
if (name) {
char buf[ASHMEM_NAME_LEN];
strlcpy(buf, name, sizeof(buf));
ret = ioctl(fd, ASHMEM_SET_NAME, buf);
if (ret < 0)
goto error;
}
ret = ioctl(fd, ASHMEM_SET_SIZE, size);
if (ret < 0)
goto error;
return fd;
error:
close(fd);
return ret;
}
ASHMEM_DEVICE其實(shí)就是抽象的共享內(nèi)存設(shè)備,它是一個(gè)雜項(xiàng)設(shè)備(字符設(shè)備的一種),在驅(qū)動(dòng)加載之后,就會(huì)在/dev下穿件ashem文件,之后用戶(hù)就能夠訪問(wèn)該設(shè)備文件,同一般的設(shè)備文件不同,它僅僅是通過(guò)內(nèi)存抽象的,同普通的磁盤(pán)設(shè)備文件、串行端口字段設(shè)備文件不一樣:
#define ASHMEM_DEVICE "/dev/ashmem"
static struct miscdevice ashmem_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ashmem",
.fops = &ashmem_fops,
};
接著進(jìn)入驅(qū)動(dòng)看一下,如何申請(qǐng)共享內(nèi)存,open函數(shù)很普通,主要是創(chuàng)建一個(gè)ashmem_area對(duì)象
static int ashmem_open(struct inode *inode, struct file *file)
{
struct ashmem_area *asma;
int ret;
ret = nonseekable_open(inode, file);
if (unlikely(ret))
return ret;
asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
if (unlikely(!asma))
return -ENOMEM;
INIT_LIST_HEAD(&asma->unpinned_list);
memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);
asma->prot_mask = PROT_MASK;
file->private_data = asma;
return 0;
}
接著利用ashmem_ioctl設(shè)置共享內(nèi)存的大小,
static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct ashmem_area *asma = file->private_data;
long ret = -ENOTTY;
switch (cmd) {
...
case ASHMEM_SET_SIZE:
ret = -EINVAL;
if (!asma->file) {
ret = 0;
asma->size = (size_t) arg;
}
break;
...
}
return ret;
}
可以看到,其實(shí)并未真正的分配內(nèi)存,這也符合Linux的風(fēng)格,只有等到真正的使用的時(shí)候,才會(huì)通過(guò)缺頁(yè)中斷分配內(nèi)存,接著mmap函數(shù),它會(huì)分配內(nèi)存嗎?
static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
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;
// 這里創(chuàng)建的臨時(shí)文件其實(shí)是備份用的臨時(shí)文件,之類(lèi)的臨時(shí)文件有文章說(shuō)只對(duì)內(nèi)核態(tài)可見(jiàn),用戶(hù)態(tài)不可見(jiàn),我們也沒(méi)有辦法通過(guò)命令查詢(xún)到 ,可以看做是個(gè)隱藏文件,用戶(hù)空間看不到??!
<!--校準(zhǔn)真正操作的文件-->
vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
asma->file = vmfile;
}
get_file(asma->file);
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;
}
vma->vm_flags |= VM_CAN_NONLINEAR;
out:
mutex_unlock(&ashmem_mutex);
return ret;
}
其實(shí)這里就復(fù)用了Linux的共享內(nèi)存機(jī)制,雖然說(shuō)是匿名共享內(nèi)存,但底層其實(shí)還是給共享內(nèi)存設(shè)置了名稱(chēng)(前綴ASHMEM_NAME_PREFIX+名字),如果名字未設(shè)置,那就默認(rèn)使用ASHMEM_NAME_PREFIX作為名稱(chēng)。不過(guò),在這里沒(méi)直接看到內(nèi)存分配的函數(shù)。但是,有兩個(gè)函數(shù)shmem_file_setup與shmem_set_file很重要,也是共享內(nèi)存比較不好理解的地方,shmem_file_setup是原生linux的共享內(nèi)存機(jī)制,不過(guò)Android也修改Linux共享內(nèi)存的驅(qū)動(dòng)代碼,匿名共享內(nèi)存其實(shí)就是在Linux共享內(nèi)存的基礎(chǔ)上做了改進(jìn),
struct file *shmem_file_setup(char *name, loff_t size, unsigned long flags)
{
int error;
struct file *file;
struct inode *inode;
struct dentry *dentry, *root;
struct qstr this;
error = -ENOMEM;
this.name = name;
this.len = strlen(name);
this.hash = 0; /* will go */
root = shm_mnt->mnt_root;
dentry = d_alloc(root, &this);//分配dentry cat/proc/pid/maps可以查到
error = -ENFILE;
file = get_empty_filp(); //分配file
error = -ENOSPC;
inode = shmem_get_inode(root->d_sb, S_IFREG | S_IRWXUGO, 0, flags);//分配inode,分配成功就好比建立了文件,也許并未存在真實(shí)文件映射
d_instantiate(dentry, inode);//綁定
inode->i_size = size;
inode->i_nlink = 0; /* It is unlinked */
// 文件操作符,這里似乎真的是不在內(nèi)存里面創(chuàng)建什么東西???
init_file(file, shm_mnt, dentry, FMODE_WRITE | FMODE_READ,
&shmem_file_operations);//綁定,并指定該文件操作指針為shmem_file_operations
...
}
通過(guò)shmem_file_setup在tmpfs臨時(shí)文件系統(tǒng)中創(chuàng)建一個(gè)臨時(shí)文件(也許只是內(nèi)核中的一個(gè)inode節(jié)點(diǎn)),該文件與Ashmem驅(qū)動(dòng)程序創(chuàng)建的匿名共享內(nèi)存對(duì)應(yīng),不過(guò)用戶(hù)態(tài)并不能看到該臨時(shí)文件,之后就能夠使用該臨時(shí)文件了,注意共享內(nèi)存機(jī)制真正使用map的對(duì)象其實(shí)是這個(gè)臨時(shí)文件,而不是ashmem設(shè)備文件,這里之所以是一次mmap,主要是通過(guò)vma->vm_file = asma->file完成map對(duì)象的替換,當(dāng)映射的內(nèi)存引起缺頁(yè)中斷的時(shí)候,就會(huì)調(diào)用shmem_file_setup創(chuàng)建的對(duì)象的函數(shù),而不是ashmem的,看下臨時(shí)文件的對(duì)應(yīng)的hook函數(shù),
void shmem_set_file(struct vm_area_struct *vma, struct file *file)
{
if (vma->vm_file)
fput(vma->vm_file);
vma->vm_file = file;
vma->vm_ops = &shmem_vm_ops;
}
到這里回到之前的MemoryFile,看一下寫(xiě)操作:
public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
throws IOException {
if (isDeactivated()) {
throw new IOException("Can't write to deactivated memory file.");
}
if (srcOffset < 0 || srcOffset > buffer.length || count < 0
|| count > buffer.length - srcOffset
|| destOffset < 0 || destOffset > mLength
|| count > mLength - destOffset) {
throw new IndexOutOfBoundsException();
}
native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
}
進(jìn)入native代碼
static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz,
jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
jint count, jboolean unpinned)
{
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
ashmem_unpin_region(fd, 0, 0);
return -1;
}
env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset);
if (unpinned) {
ashmem_unpin_region(fd, 0, 0);
}
return count;
}
在內(nèi)核中,一塊內(nèi)存對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)是ashmem_area:
struct ashmem_area {
char name[ASHMEM_FULL_NAME_LEN];/* optional name for /proc/pid/maps */
struct list_head unpinned_list; /* list of all ashmem areas */
struct file *file; /* the shmem-based backing file */
size_t size; /* size of the mapping, in bytes */
unsigned long prot_mask; /* allowed prot bits, as vm_flags */
};
當(dāng)使用Ashmem分配了一塊內(nèi)存,部分不被使用時(shí),就可以將這塊內(nèi)存unpin掉,內(nèi)核可以將unpin對(duì)應(yīng)的物理頁(yè)面回收,回收后的內(nèi)存還可以再次被獲得(通過(guò)缺頁(yè)handler),因?yàn)閡npin操作并不會(huì)改變已經(jīng)mmap的地址空間,不過(guò),MemoryFile只會(huì)操作整個(gè)共享內(nèi)存,而不會(huì)分塊訪問(wèn),所以pin與unpin對(duì)于它沒(méi)多大意義,可以看做整個(gè)區(qū)域都是pin或者unpin的,首次通過(guò)env->GetByteArrayRegion訪問(wèn)會(huì)引發(fā)缺頁(yè)中斷,進(jìn)而調(diào)用tmpfs 文件的相應(yīng)操作,分配物理頁(yè),在Android現(xiàn)在的內(nèi)核中,缺頁(yè)中斷對(duì)應(yīng)的vm_operations_struct中的函數(shù)是fault,在共享內(nèi)存實(shí)現(xiàn)中,對(duì)應(yīng)的是shmem_fault如下,
static struct vm_operations_struct shmem_vm_ops = {
.fault = shmem_fault,
#ifdef CONFIG_NUMA
.set_policy = shmem_set_policy,
.get_policy = shmem_get_policy,
#endif
};
當(dāng)mmap的tmpfs文件引發(fā)缺頁(yè)中斷時(shí), 就會(huì)調(diào)用shmem_fault函數(shù),
static int shmem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
struct inode *inode = vma->vm_file->f_path.dentry->d_inode;
int error;
int ret;
if (((loff_t)vmf->pgoff << PAGE_CACHE_SHIFT) >= i_size_read(inode))
return VM_FAULT_SIGBUS;
error = shmem_getpage(inode, vmf->pgoff, &vmf->page, SGP_CACHE, &ret);
if (error)
return ((error == -ENOMEM) ? VM_FAULT_OOM : VM_FAULT_SIGBUS);
return ret | VM_FAULT_LOCKED;
}
到這里,就可以看到會(huì)調(diào)用shmem_getpage函數(shù)分配真實(shí)的物理頁(yè),具體的分配策略比較復(fù)雜,不在分析。
Android匿名共享內(nèi)存的pin與unpin
pin本身的意思是壓住,定住,ashmem_pin_region和ashmem_unpin_region這兩個(gè)函數(shù)從字面上來(lái)說(shuō),就是用來(lái)對(duì)匿名共享內(nèi)存鎖定和解鎖,標(biāo)識(shí)哪些內(nèi)存正在使用需要鎖定,哪些內(nèi)存是不使用的,這樣,ashmem驅(qū)動(dòng)程序可以一定程度上輔助內(nèi)存管理,提供一定的內(nèi)存優(yōu)化能力。匿名共享內(nèi)存創(chuàng)建之初時(shí),所有的內(nèi)存都是pinned狀態(tài),只有用戶(hù)主動(dòng)申請(qǐng),才會(huì)unpin一塊內(nèi)存,只有對(duì)于unpinned狀態(tài)的內(nèi)存塊,用戶(hù)才可以重新pin。現(xiàn)在仔細(xì)梳理一下驅(qū)動(dòng),看下pin與unpin的實(shí)現(xiàn)
static int __init ashmem_init(void)
{
int ret;
<!--創(chuàng)建 ahemem_area 高速緩存-->
ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
sizeof(struct ashmem_area),
0, 0, NULL);
...
<!--創(chuàng)建 ahemem_range高速緩存-->
ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
sizeof(struct ashmem_range),
0, 0, NULL);
...
<!--注冊(cè)雜項(xiàng)設(shè)備去送-->
ret = misc_register(&ashmem_misc);
...
register_shrinker(&ashmem_shrinker);
return 0;
}
打開(kāi)ashem的時(shí)候 ,會(huì)利用ashmem_area_cachep告訴緩存新建ashmem_area對(duì)象,并初始化unpinned_list,開(kāi)始肯定為null
static int ashmem_open(struct inode *inode, struct file *file)
{
struct ashmem_area *asma;
int ret;
ret = nonseekable_open(inode, file);
asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
<!--關(guān)鍵是初始化unpinned_list列表-->
INIT_LIST_HEAD(&asma->unpinned_list);
memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);
asma->prot_mask = PROT_MASK;
file->private_data = asma;
return 0;
}
一開(kāi)始都是pin的,看一下pin與unpin的調(diào)用范例:
int ashmem_pin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
return ioctl(fd, ASHMEM_PIN, &pin);
}
int ashmem_unpin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
return ioctl(fd, ASHMEM_UNPIN, &pin);
}
接著看ashmem_unpin
static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend)
{
struct ashmem_range *range, *next;
unsigned int purged = ASHMEM_NOT_PURGED;
restart:
list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) {
if (range_before_page(range, pgstart))
break;
if (page_range_subsumed_by_range(range, pgstart, pgend))
return 0;
if (page_range_in_range(range, pgstart, pgend)) {
pgstart = min_t(size_t, range->pgstart, pgstart),
pgend = max_t(size_t, range->pgend, pgend);
purged |= range->purged;
range_del(range);
goto restart;
}
}
return range_alloc(asma, range, purged, pgstart, pgend);
}
這個(gè)函數(shù)主要作用是創(chuàng)建一個(gè)ashmem_range ,并插入ashmem_area的unpinned_list,在插入的時(shí)候可能會(huì)有合并為,這個(gè)時(shí)候要首先刪除原來(lái)的unpin ashmem_range,之后新建一個(gè)合并后的ashmem_range插入unpinned_list。

下面來(lái)看一下pin函數(shù)的實(shí)現(xiàn),先理解了unpin,pin就很好理解了,其實(shí)就是將一塊共享內(nèi)存投入使用,如果它位于unpinedlist,就將它摘下來(lái):
static int ashmem_pin(struct ashmem_area *asma, size_t pgstart, size_t pgend)
{
struct ashmem_range *range, *next;
int ret = ASHMEM_NOT_PURGED;
list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) {
/* moved past last applicable page; we can short circuit */
if (range_before_page(range, pgstart))
break;
if (page_range_in_range(range, pgstart, pgend)) {
ret |= range->purged;
if (page_range_subsumes_range(range, pgstart, pgend)) {
range_del(range);
continue;
}
if (range->pgstart >= pgstart) {
range_shrink(range, pgend + 1, range->pgend);
continue;
}
if (range->pgend <= pgend) {
range_shrink(range, range->pgstart, pgstart-1);
continue;
}
range_alloc(asma, range, range->purged,
pgend + 1, range->pgend);
range_shrink(range, range->pgstart, pgstart - 1);
break;
}
}
return ret;
}

Android進(jìn)程共享內(nèi)存的傳遞-fd文件描述符的傳遞
原生Linux共享內(nèi)存是通過(guò)傳遞已知的key來(lái)處理的,但是Android中不存在這種機(jī)制,Android是怎么處理的呢?那就是通過(guò)Binder傳遞文件描述符來(lái)處理,Android的Binder對(duì)于fd的傳遞也做了適配,原理其實(shí)就是在內(nèi)核層為要傳遞的目標(biāo)進(jìn)程轉(zhuǎn)換fd,因?yàn)樵趌inux中fd只是對(duì)本進(jìn)程是有效、且唯一,進(jìn)程A打開(kāi)一個(gè)文件得到一個(gè)fd,不能直接為進(jìn)程B使用,因?yàn)锽中那個(gè)fd可能壓根無(wú)效、或者對(duì)應(yīng)其他文件,不過(guò),雖然同一個(gè)文件可以有多個(gè)文件描述符,但是文件只有一個(gè),在內(nèi)核層也只會(huì)對(duì)應(yīng)一個(gè)inode節(jié)點(diǎn)與file對(duì)象,這也是內(nèi)核層可以傳遞fd的基礎(chǔ),Binder驅(qū)動(dòng)通過(guò)當(dāng)前進(jìn)程的fd找到對(duì)應(yīng)的文件,然后為目標(biāo)進(jìn)程新建fd,并傳遞給目標(biāo)進(jìn)程,核心就是把進(jìn)程A中的fd轉(zhuǎn)化成進(jìn)程B中的fd,看一下Android中binder的實(shí)現(xiàn):
void binder_transaction(){
...
case BINDER_TYPE_FD: {
int target_fd;
struct file *file;
<!--關(guān)鍵點(diǎn)1 可以根據(jù)fd在當(dāng)前進(jìn)程獲取到file ,多個(gè)進(jìn)程打開(kāi)同一文件,在內(nèi)核中對(duì)應(yīng)的file是一樣-->
file = fget(fp->handle);
<!--關(guān)鍵點(diǎn)2,為目標(biāo)進(jìn)程獲取空閑fd-->
target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
<!--關(guān)鍵點(diǎn)3將目標(biāo)進(jìn)程的空閑fd與file綁定-->
task_fd_install(target_proc, target_fd, file);
fp->handle = target_fd;
} break;
...
}
<!--從當(dāng)前進(jìn)程打開(kāi)的files中找到file在內(nèi)核中的實(shí)例-->
struct file *fget(unsigned int fd)
{
struct file *file;
struct files_struct *files = current->files;
rcu_read_lock();
file = fcheck_files(files, fd);
rcu_read_unlock();
return file;
}
static void task_fd_install(
struct binder_proc *proc, unsigned int fd, struct file *file)
{
struct files_struct *files = proc->files;
struct fdtable *fdt;
if (files == NULL)
return;
spin_lock(&files->file_lock);
fdt = files_fdtable(files);
rcu_assign_pointer(fdt->fd[fd], file);
spin_unlock(&files->file_lock);
}

為什么看不到匿名共享內(nèi)存對(duì)應(yīng)的文件呢
為什么Android用戶(hù)看不到共享內(nèi)存對(duì)應(yīng)的文件,Google到的說(shuō)法是:在內(nèi)核沒(méi)有定義defined(CONFIG_TMPFS) 情況下,tmpfs對(duì)用戶(hù)不可見(jiàn):
If CONFIG_TMPFS is not set, the user visible part of tmpfs is not build. But the internal mechanisms are always present.
而在Android的shmem.c驅(qū)動(dòng)中確實(shí)沒(méi)有defined(CONFIG_TMPFS) ,這里只是猜測(cè),也許還有其他解釋?zhuān)缬辛私?,望能指?dǎo)。
匿名共享內(nèi)存的優(yōu)點(diǎn)也是BUG
匿名共享內(nèi)存不會(huì)占用Dalvik Heap與Native Heap,不會(huì)導(dǎo)致OOM,這是優(yōu)點(diǎn),同時(shí)也是缺點(diǎn),因?yàn)槿绻烈馐褂茫瑫?huì)導(dǎo)致系統(tǒng)資源不足,性能下降,

另外共享存占用空間的計(jì)算,只會(huì)計(jì)算到第一個(gè)創(chuàng)建它的進(jìn)程中,其他進(jìn)程不將ashmem計(jì)算在內(nèi)。
總結(jié)
Android匿名共享內(nèi)存是基于Linux共享內(nèi)存的,都是在tmpfs文件系統(tǒng)上新建文件,并將其映射到不同的進(jìn)程空間,從而達(dá)到共享內(nèi)存的目的,只是,Android在Linux的基礎(chǔ)上進(jìn)行了改造,并借助Binder+fd文件描述符實(shí)現(xiàn)了共享內(nèi)存的傳遞。
作者:看書(shū)的小蝸牛
原文鏈接:Android匿名共享內(nèi)存(Ashmem)原理
僅供參考,歡迎指正