匿名内存共享
匿名内存映射简单介绍
Anonymous Shared Memory是Android特有的内存共享机制,它是基于linxus共享内存而改进的,它可以将指定的物理内存分别映射到各个进程自己的虚拟的地址空间中,从而便捷地实现进程间的内存共享。
在内存映射中,主要涉及到的函数是为mmap()函数,其函数定义如下:
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
这mmap函数里有多个参数,其中addr代表文件被映射的开始地址(这个地址相对于进程空间来说),这个通常为空,由系统去决定开始地址,len内存映射长度,prot代表读写权限,flags代表共享权限,fd描述符,offset文件映射偏移量,整个映射结构如图:
引用内存映射对象,会三种情况:
- 在文件映射长度之内的引用
- 在文件映射长度之外但却在文件内存映射区之内的引用
- 在文件没有映射的内存区之外的引用
这三个区域如图所示:
第一种情况:在文件映射长度之内,对其映射内存的数据改写,都会在操作结束后(产生一个SIGSEGV信号通知系统)同步到文件中。
第二种情况:在对其文件映射长度之内的映射内存的数据改写会同步到文件中,而在其外的数据改写就不会同步到文件中。
第三种情况:对其文件没有映射的内存区之外的引用会因其系统产生一个SIGBUS信号而中断程序。
假设:文件大小的为5000B,内存映射块也设置5000B,虚拟内存的每一页大小设置为8192B,并让文件全部映射到内存映射快中,其映射过程和引用结果就如图所示:
又假设:文件大小依然为5000B,内存映射快设置为15000B,虚拟内存的每一页大小设置为8192B,并让文件全部映射到内存映射快中,其映射过程和引用结果就如图所示:
关于linxus的进程间内存共享通讯的简单介绍及实例,请参考下面这两篇博客:
http://blog.csdn.net/ljianhui/article/details/10253345、
http://www.cnblogs.com/wwwjieo0/p/3758023.html
匿名共享内存创建过程
android在应用层通过frameworks\base\core\java\android\os下的MemoryFile文件来创建匿名内存映射的。
/**
* Allocates a new ashmem region. The region is initially not purgable.
*
* @param name optional name for the file (can be null).
* @param length of the memory file in bytes.
* @throws IOException if the memory file could not be created.
*/
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;
}
}
从这个构造方法我们可以知道,Android其实调用了本地的方法创建匿名共享内存,其本地方法在frameworks\base\core\jni目录下的android_os_MemoryFile.cpp,其源码如下:
static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length)
{
//将Java字符串转换C语言char*类型
const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL);
//创建文件内存映射块
int result = ashmem_create_region(namestr, length);
//释放Java字符对象的内存
if (name)
env->ReleaseStringUTFChars(name, namestr);
if (result < 0) {
jniThrowException(env, "java/io/IOException", "ashmem_create_region failed");
return NULL;
}
//返回文件描述符(相当于操作文件的指针)
return jniCreateFileDescriptor(env, result);
}
static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,
jint length, jint prot)
{
//获取到文件描述符
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
//映射到进程空间中
jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0);
if (!result)
jniThrowException(env, "java/io/IOException", "mmap failed");
return result;
}
从这个文件来看,它调用了系统内核底层的方法创建了内存,其文件在Linux/drivers/staging/android/ashmem.c。
static int __init ashmem_init(void)
{
int ret;
//创建缓存内存
ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
sizeof(struct ashmem_area),
0, 0, NULL);
if (unlikely(!ashmem_area_cachep)) {
pr_err("failed to create slab cache\n");
return -ENOMEM;
}
//创建缓存内存
ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
sizeof(struct ashmem_range),
0, 0, NULL);
if (unlikely(!ashmem_range_cachep)) {
pr_err("failed to create slab cache\n");
return -ENOMEM;
}
//注册初始化信息
ret = misc_register(&ashmem_misc);
if (unlikely(ret)) {
pr_err("failed to register misc device!\n");
return ret;
}
//注册回收算法
register_shrinker(&ashmem_shrinker);
pr_info("initialized\n");
return 0;
}
分配文件内存的源码:
/**
* ashmem_open() - Opens an Anonymous Shared Memory structure
* @inode: The backing file's index node(?)
* @file: The backing file
*
* Please note that the ashmem_area is not returned by this function - It is
* instead written to "file->private_data".
*
* Return: 0 if successful, or another code if unsuccessful.
*/
static int ashmem_open(struct inode *inode, struct file *file)
{
struct ashmem_area *asma;
int ret;
ret = generic_file_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;
}
映射内存到进程空间的源码:
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);
/* user needs to SET_SIZE before mapping */
if (unlikely(!asma->size)) {
ret = -EINVAL;
goto out;
}
/* requested protection bits must match our allowed protection mask */
if (unlikely((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask)) &
calc_vm_prot_bits(PROT_MASK))) {
ret = -EPERM;
goto out;
}
vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
if (!asma->file) {
char *name = ASHMEM_NAME_DEF;
struct file *vmfile;
if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')
name = asma->name;
/* ... and allocate the backing shmem file */
//创建临时支持文件
vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
if (unlikely(IS_ERR(vmfile))) {
ret = PTR_ERR(vmfile);
goto out;
}
asma->file = vmfile;
}
get_file(asma->file);
/*
* XXX - Reworked to use shmem_zero_setup() instead of
* shmem_set_file while we're in staging. -jstultz
*/
if (vma->vm_flags & VM_SHARED) {
//内存映射并初始化内存的数据为0
ret = shmem_zero_setup(vma);
if (ret) {
fput(asma->file);
goto out;
}
}
if (vma->vm_file)
fput(vma->vm_file);
vma->vm_file = asma->file;
out:
//释放锁
mutex_unlock(&ashmem_mutex);
//返回文件内存映射进程空间的开始地址
return ret;
}
参考资料:
http://notjustburritos.tumblr.com/post/21442138796/an-introduction-to-android-shared-memory
https://vec.io/posts/andriod-ipc-shared-memory-with-ashmem-memoryfile-and-binder