匿名共享内存Ashmem

原理图

sequenceDiagram
sharedMemory->>+ashmem_dev: 1: ashmem_create_region
ashmem_dev->>ashmem_dev : fd = __ashmem_open() 创建ashmem_area放入file->private_data
ashmem_dev->>ashmem_dev: ioctl(fd, ASHMEM_SET_NAME, buf)
ashmem_dev->>ashmem_dev: ioctl(fd, ASHMEM_SET_SIZE, size)
ashmem_dev->>-sharedMemory: fd
sharedMemory->>+Os : 2: mmap
Os->>-ashmem : ashmem_mmap
ashmem->>+shmem: vmfile = shmem_file_setup
shmem->>shmem: shmem_get_inode
shmem->>-shmem: alloc_file
ashmem->>shmem: shmem_set_file
sharedMemory->>ashmem_dev: 3: native_write
ashmem_dev->>ashmem: unpinned && ashmem_pin_region
ashmem_dev->>shmem: env->GetByteArrayRegion
shmem->>+shmem: shmem_fault
Note right of shmem: 触发缺页中断
shmem->>-shmem: shmem_getpage分配真实物理页
ashmem_dev->>ashmem: ashmem_unpin_region

MemoryFile

    public MemoryFile(String name, int length) throws IOException {
        try {
            mSharedMemory = SharedMemory.create(name, length);
            mMapping = mSharedMemory.mapReadWrite();
        } catch (ErrnoException ex) {
            ex.rethrowAsIOException();
        }
    }

SharedMemory

create

public static @NonNull SharedMemory create(@Nullable String name, int size)
        throws ErrnoException {
    return new SharedMemory(nCreate(name, size));
}

nCreate

SharedMemory::cons

private SharedMemory(FileDescriptor fd) {
    mFileDescriptor = fd;
    mSize = nGetSize(mFileDescriptor);

    mMemoryRegistration = new MemoryRegistration(mSize);
    mCleaner = Cleaner.create(mFileDescriptor,
            new Closer(mFileDescriptor, mMemoryRegistration));
}

mapReadWrite

    public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {
        return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);
    }

map

public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
    checkOpen();
    validateProt(prot);
    //mmap
    long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);
    boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;
    Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());
    return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
}

Os

    public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { 
      // BlockGuardOs extends ForwardingOs中被代理的Os的mmap,也就是Linux的mmap
      return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset);                                                                                                                                      }

Libcore

public final class Libcore {
    private Libcore() { }

    /**
     * Direct access to syscalls. Code should strongly prefer using {@link #os}
     * unless it has a strong reason to bypass the helpful checks/guards that it
     * provides.
     */
    public static Os rawOs = new Linux();

    /**
     * Access to syscalls with helpful checks/guards.
     */
    public static Os os = new BlockGuardOs(rawOs);
}

Linux

mmap

public native long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException;

frameworks/base/core/jni/android_os_SharedMemory.cpp

android_os_SharedMemory

SharedMemory_create

static jobject SharedMemory_create(JNIEnv* env, jobject, jstring jname, jint size) {

    // Name is optional so we can't use ScopedUtfChars for this as it throws NPE on null
    const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr;

    int fd = ashmem_create_region(name, size);

    // Capture the error, if there is one, before calling ReleaseStringUTFChars
    int err = fd < 0 ? errno : 0;

    if (name) {
        env->ReleaseStringUTFChars(jname, name);
    }

    return jniCreateFileDescriptor(env, fd);
}

libnativehelper/JNIHelp.cpp

JNIHelp

jniCreateFileDescriptor

jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd) {
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
    if (fileDescriptorInitMethod == nullptr) {
        InitFieldsAndMethods(e);
    }
    jobject fileDescriptor = (*env)->NewObject(e, JniConstants::fileDescriptorClass,
            fileDescriptorInitMethod);
    // NOTE: NewObject ensures that an OutOfMemoryError will be seen by the Java
    // caller if the alloc fails, so we just return NULL when that happens.
    if (fileDescriptor != NULL)  {
        jniSetFileDescriptorOfFD(env, fileDescriptor, fd);
    }
    return fileDescriptor;
}

system/core/libcutils/ashmem-dev.cpp

ashmem-dev

#define ASHMEM_DEVICE "/dev/ashmem"

ashmem_create_region

/*
 * ashmem_create_region - creates a new ashmem region and returns the file
 * descriptor, or <0 on error
 *
 * `name' is an optional label to give the region (visible in /proc/pid/maps)
 * `size' is the size of the region, in page-aligned bytes
 */
int ashmem_create_region(const char *name, size_t size)
{
    int ret, save_errno;

    int fd = __ashmem_open();
    if (fd < 0) {
        return fd;
    }

    if (name) {
        char buf[ASHMEM_NAME_LEN] = {0};

        strlcpy(buf, name, sizeof(buf));
        ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf));
    }

    ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));

    return fd;
}

__ashmem_open

static int __ashmem_open()
{
    int fd;

    pthread_mutex_lock(&__ashmem_lock);
    fd = __ashmem_open_locked();
    pthread_mutex_unlock(&__ashmem_lock);

    return fd;
}

__ashmem_open_locked

/* logistics of getting file descriptor for ashmem */
static int __ashmem_open_locked()
{
    int ret;
    struct stat st;

    int fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR | O_CLOEXEC));
    if (fd < 0) {
        return fd;
    }

    ret = TEMP_FAILURE_RETRY(fstat(fd, &st));

    __ashmem_rdev = st.st_rdev;
    return fd;
}

drivers/staging/android/ashmem.c

ashmem.c

#define ASHMEM_NAME_DEF		"dev/ashmem"

ashmem_open

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

ashmem_mmap

static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct ashmem_area *asma = file->private_data;
	int ret = 0;
  
  	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 */
    //shmem_file_setup是原生linux的共享内存机制,匿名共享内存其实就是在Linux共享内存的基础上做了改进
		vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
		if (IS_ERR(vmfile)) {
			ret = PTR_ERR(vmfile);
			goto out;
		}
		vmfile->f_mode |= FMODE_LSEEK;
		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;
	}
  return ret;
}

mm/shmem.c

shmem.c

shmem_file_setup

/**
 * shmem_file_setup - get an unlinked file living in tmpfs
 * @name: name for dentry (to be seen in /proc/<pid>/maps
 * @size: size to be set for the file
 * @flags: VM_NORESERVE suppresses pre-accounting of the entire object size
 */
struct file *shmem_file_setup(const char *name, loff_t size, unsigned long flags)
{
	return __shmem_file_setup(name, size, flags, 0);
}

__shmem_file_setup

static struct file *__shmem_file_setup(const char *name, loff_t size,
				       unsigned long flags, unsigned int i_flags)
{
	inode = shmem_get_inode(sb, NULL, S_IFREG | S_IRWXUGO, 0, flags);//分配inode,分配成功就好比建立了文件,也许并未存在真实文件映射
  
  res = alloc_file(&path, FMODE_WRITE | FMODE_READ,
		  &shmem_file_operations);
  
  return res;
}

shmem_set_file

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

//TODO shmem_vm_ops结构体的定义是下面两者中的哪一种,通过debug确定

static const struct vm_operations_struct shmem_vm_ops = {
	.fault		= shmem_fault,
	.map_pages	= filemap_map_pages,
#ifdef CONFIG_NUMA
	.set_policy     = shmem_set_policy,
	.get_policy     = shmem_get_policy,
#endif
};
#define shmem_vm_ops				generic_file_vm_ops

const struct vm_operations_struct generic_file_vm_ops = {
	.fault		= filemap_fault,
	.map_pages	= filemap_map_pages,
	.page_mkwrite	= filemap_page_mkwrite,
};

参考

Android匿名共享内存(Ashmem)原理

Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析(1)