Android匿名共享内存(Ashmem)原理,全网最新

接着利用ashmem_ioctl设置共享内存的大小,

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;

}

可以看到,其实并未真正的分配内存,这也符合Linux的风格,只有等到真正的使用的时候,才会通过缺页中断分配内存,接着mmap函数,它会分配内存吗?

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;

// 这里创建的临时文件其实是备份用的临时文件,之类的临时文件有文章说只对内核态可见,用户态不可见,我们也没有办法通过命令查询到 ,可以看做是个隐藏文件,用户空间看不到!!

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;

}

其实这里就复用了Linux的共享内存机制,虽然说是匿名共享内存,但底层其实还是给共享内存设置了名称(前缀ASHMEM_NAME_PREFIX+名字),如果名字未设置,那就默认使用ASHMEM_NAME_PREFIX作为名称。不过,在这里没直接看到内存分配的函数。但是,有两个函数shmem_file_setup与shmem_set_file很重要,也是共享内存比较不好理解的地方,shmem_file_setup是原生linux的共享内存机制,不过Android也修改Linux共享内存的驱动代码,匿名共享内存其实就是在Linux共享内存的基础上做了改进,

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,分配成功就好比建立了文件,也许并未存在真实文件映射

d_instantiate(dentry, inode);//绑定

inode->i_size = size;

inode->i_nlink = 0; /* It is unlinked */

// 文件操作符,这里似乎真的是不在内存里面创建什么东西???

init_file(file, shm_mnt, dentry, FMODE_WRITE | FMODE_READ,

&shmem_file_operations);//绑定,并指定该文件操作指针为shmem_file_operations

}

通过shmem_file_setup在tmpfs临时文件系统中创建一个临时文件(也许只是内核中的一个inode节点),该文件与Ashmem驱动程序创建的匿名共享内存对应,不过用户态并不能看到该临时文件,之后就能够使用该临时文件了,注意共享内存机制真正使用map的对象其实是这个临时文件,而不是ashmem设备文件,这里之所以是一次mmap,主要是通过vma->vm_file = asma->file完成map对象的替换,当映射的内存引起缺页中断的时候,就会调用shmem_file_setup创建的对象的函数,而不是ashmem的,看下临时文件的对应的hook函数,

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,看一下写操作:

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);

}

进入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;

}

在内核中,一块内存对应的数据结构是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 */

};

当使用Ashmem分配了一块内存,部分不被使用时,就可以将这块内存unpin掉,内核可以将unpin对应的物理页面回收,回收后的内存还可以再次被获得(通过缺页handler),因为unpin操作并不会改变已经mmap的地址空间,不过,MemoryFile只会操作整个共享内存,而不会分块访问,所以pin与unpin对于它没多大意义,可以看做整个区域都是pin或者unpin的,首次通过env->GetByteArrayRegion访问会引发缺页中断,进而调用tmpfs 文件的相应操作,分配物理页,在Android现在的内核中,缺页中断对应的vm_operations_struct中的函数是fault,在共享内存实现中,对应的是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

};

当mmap的tmpfs文件引发缺页中断时, 就会调用shmem_fault函数,

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;

}

到这里,就可以看到会调用shmem_getpage函数分配真实的物理页,具体的分配策略比较复杂,不在分析。

Android匿名共享内存的pin与unpin

=======================

pin本身的意思是压住,定住,ashmem_pin_region和ashmem_unpin_region这两个函数从字面上来说,就是用来对匿名共享内存锁定和解锁,标识哪些内存正在使用需要锁定,哪些内存是不使用的,这样,ashmem驱动程序可以一定程度上辅助内存管理,提供一定的内存优化能力。匿名共享内存创建之初时,所有的内存都是pinned状态,只有用户主动申请,才会unpin一块内存,只有对于unpinned状态的内存块,用户才可以重新pin。现在仔细梳理一下驱动,看下pin与unpin的实现

static int __init ashmem_init(void)

{

int ret;

ashmem_area_cachep = kmem_cache_create(“ashmem_area_cache”,

sizeof(struct ashmem_area),

0, 0, NULL);

ashmem_range_cachep = kmem_cache_create(“ashmem_range_cache”,

sizeof(struct ashmem_range),

0, 0, NULL);

ret = misc_register(&ashmem_misc);

register_shrinker(&ashmem_shrinker);

return 0;

}

打开ashem的时候 ,会利用ashmem_area_cachep告诉缓存新建ashmem_area对象,并初始化unpinned_list,开始肯定为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);

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;

}

一开始都是pin的,看一下pin与unpin的调用范例:

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);

}

这个函数主要作用是创建一个ashmem_range ,并插入ashmem_area的unpinned_list,在插入的时候可能会有合并为,这个时候要首先删除原来的unpin ashmem_range,之后新建一个合并后的ashmem_range插入unpinned_list。

Android匿名共享内存(Ashmem)原理,全网最新

共享内存.jpg

下面来看一下pin函数的实现,先理解了unpin,pin就很好理解了,其实就是将一块共享内存投入使用,如果它位于unpinedlist,就将它摘下来:

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;

}

![](https://www.icode9.com/i/ll/?i=img_convert/cf708ba006256df58226c1d5091b6

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

浏览器打开:qq.cn.hn/FTe 免费领取

1b2.png)

pin共享内存.jpg

Android进程共享内存的传递-fd文件描述符的传递

===========================

原生Linux共享内存是通过传递已知的key来处理的,但是Android中不存在这种机制,Android是怎么处理的呢?那就是通过Binder传递文件描述符来处理,Android的Binder对于fd的传递也做了适配,原理其实就是在内核层为要传递的目标进程转换fd,因为在linux中fd只是对本进程是有效、且唯一,进程A打开一个文件得到一个fd,不能直接为进程B使用,因为B中那个fd可能压根无效、或者对应其他文件,不过,虽然同一个文件可以有多个文件描述符,但是文件只有一个,在内核层也只会对应一个inode节点与file对象,这也是内核层可以传递fd的基础,Binder驱动通过当前进程的fd找到对应的文件,然后为目标进程新建fd,并传递给目标进程,核心就是把进程A中的fd转化成进程B中的fd,看一下Android中binder的实现:

void binder_transaction(){

case BINDER_TYPE_FD: {

int target_fd;

struct file *file;

file = fget(fp->handle);

target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);

task_fd_install(target_proc, target_fd, file);

fp->handle = target_fd;

} break;

}

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);

}

Android匿名共享内存(Ashmem)原理,全网最新

fd传递.jpg

为什么看不到匿名共享内存对应的文件呢

==================

为什么Android用户看不到共享内存对应的文件,Google到的说法是:在内核没有定义defined(CONFIG_TMPFS) 情况下,tmpfs对用户不可见:

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驱动中确实没有defined(CONFIG_TMPFS) ,这里只是猜测,也许还有其他解释,如有了解,望能指导。

匿名共享内存的优点也是BUG

==============

匿名共享内存不会占用Dalvik Heap与Native Heap,不会导致OOM,这是优点,同时也是缺点,因为如果肆意使用,会导致系统资源不足,性能下降,

Android匿名共享内存(Ashmem)原理,全网最新

匿名共享内存不占用

另外共享存占用空间的计算,只会计算到第一个创建它的进程中,其他进程不将ashmem计算在内。

总结

==

Android匿名共享内存是基于Linux共享内存的,都是在tmpfs文件系统上新建文件,并将其映射到不同的进程空间,从而达到共享内存的目的,只是,Android在Linux的基础上进行了改造,并借助Binder+fd文件描述符实现了共享内存的传递。

作者:看书的小蜗牛
链接:https://www.jianshu.com/p/d9bc9c668ba6
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上一篇:Linux下快速拷贝单个大文件的秘诀


下一篇:POJ 2965&&1753