Android MemoryFile内存文件 + Ashmem匿名共享内存


最近看EVS的代码,其中预览YUV数据时用到了MemoryFile(内存文件)。
在Binder的onTransact时,操作FileDescriptor来实现:

@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
    
    
	//... ...
    Parcel _data = Parcel.obtain();
    Parcel _reply = Parcel.obtain();
    IBinder b = ServiceManager.getService(DESCRIPTOR);
    try {
    
    
        FileDescriptor fd = null;
        _data.writeInterfaceToken(DESCRIPTOR); 
        try {
    
    
            fd = mMemoryFile.getFileDescriptor();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        _data.writeFileDescriptor(fd);
        b.transact(XXX, _data, _reply, 0);
        _reply.readException();
        _reply.readInt();
    } catch (RemoteException e) {
    
    
        e.printStackTrace();
    } finally {
    
    
        _reply.recycle();
        _data.recycle();
    }
	//... ...
}		

MemoryFile

源码路径:./frameworks/base/core/java/android/os/MemoryFile.java

主要功能

  • readBytes()
  • writeBytes()

代码实现

    /**
     * Reads bytes from the memory file.
     * Will throw an IOException if the file has been purged.
     *
     * @param buffer byte array to read bytes into.
     * @param srcOffset offset into the memory file to read from.
     * @param destOffset offset into the byte array buffer to read into.
     * @param count number of bytes to read.
     * @return number of bytes read.
     * @throws IOException if the memory file has been purged or deactivated.
     */
    public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
            throws IOException {
    
    
        beginAccess();
        try {
    
    
            mMapping.position(srcOffset);
            mMapping.get(buffer, destOffset, count);
        } finally {
    
    
            endAccess();
        }
        return count;
    }

    /**
     * Write bytes to the memory file.
     * Will throw an IOException if the file has been purged.
     *
     * @param buffer byte array to write bytes from.
     * @param srcOffset offset into the byte array buffer to write from.
     * @param destOffset offset  into the memory file to write to.
     * @param count number of bytes to write.
     * @throws IOException if the memory file has been purged or deactivated.
     */
    public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
            throws IOException {
    
    
        beginAccess();
        try {
    
    
            mMapping.position(destOffset);
            mMapping.put(buffer, srcOffset, count);
        } finally {
    
    
            endAccess();
        }
    }

JNI:./frameworks/base/core/jni/android_os_MemoryFile.cpp


namespace android {
    
    

static jboolean android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor,
        jboolean pin) {
    
    
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    int result = (pin ? ashmem_pin_region(fd, 0, 0) : ashmem_unpin_region(fd, 0, 0));
    if (result < 0) {
    
    
        jniThrowException(env, "java/io/IOException", NULL);
    }
    return result == ASHMEM_WAS_PURGED;
}

static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz,
        jobject fileDescriptor) {
    
    
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region.
    // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel
    // should return ENOTTY for all other valid file descriptors
    int result = ashmem_get_size_region(fd);
    if (result < 0) {
    
    
        if (errno == ENOTTY) {
    
    
            // ENOTTY means that the ioctl does not apply to this object,
            // i.e., it is not an ashmem region.
            return (jint) -1;
        }
        // Some other error, throw exception
        jniThrowIOException(env, errno);
        return (jint) -1;
    }
    return (jint) result;
}

static const JNINativeMethod methods[] = {
    
    
    {
    
    "native_pin",   "(Ljava/io/FileDescriptor;Z)Z", (void*)android_os_MemoryFile_pin},
    {
    
    "native_get_size", "(Ljava/io/FileDescriptor;)I",
            (void*)android_os_MemoryFile_get_size}
};

int register_android_os_MemoryFile(JNIEnv* env) {
    
    
    return RegisterMethodsOrDie(env, "android/os/MemoryFile", methods, NELEM(methods));
}

}

可以看到使用了ashmem, ashmem又是什么?

Ashmem

Ashmem (Anonymous Shared Memroy,匿名共享内存)是一种共享内存的机制,它利用了Linux的mmap系统调用,将不同进程中的同一段物理内存映射到进程各自的虚拟地址空间,从而实现高效的进程间共享。

优势

  • 在dev目录下对应的设备是/dev/ashmem,相比于传统的内存分配机制,如malloc、anonymous/named mmap,其好处是提供了辅助内核内存回收算法的pin/unpin机制。
  • 我们知道Binder 每个进程 mmap接收数据的大小有限制,超过 1M 就会报错。所以我们可以用Ashmem在 IPC 中传递大型数据。

源码位置:./system/core/libcutils/ashmem-dev.cpp

主要方法

  • ashmem_create_region :向ashmem驱动程序请求为应用程序创建一块匿名共享内存,并且放回它的文件描述符。
  • ashmem_set_prot_region:用来设置匿名共享内存的访问保护位。
  • ashmem_pin_region:向fd描述的文件发送io控制命令ASHMEM_PIN,用来锁定一小块内存区域。offset表示在匿名内存中的偏移位置,len表示长度。
  • ashmem_unpin_region:和 ashmem_pin_region 基本一样,只是发送了ASHMEM_UNPIN命令用来解锁一块内存区域。
  • ashmem_get_size_region:用来获取匿名共享内存大小。

其中 MemoryFile用到了两个:pin_region和get_size_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));
        if (ret < 0) {
    
    
            goto error;
        }
    }

    ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));
    if (ret < 0) {
    
    
        goto error;
    }

    return fd;

error:
    save_errno = errno;
    close(fd);
    errno = save_errno;
    return ret;
}


int ashmem_set_prot_region(int fd, int prot)
{
    
    
    int ret = __ashmem_is_ashmem(fd, 1);
    if (ret < 0) {
    
    
        return ret;
    }

    return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_PROT_MASK, prot));
}

int ashmem_pin_region(int fd, size_t offset, size_t len)
{
    
    
    // TODO: should LP64 reject too-large offset/len?
    ashmem_pin pin = {
    
     static_cast<uint32_t>(offset), static_cast<uint32_t>(len) };

    int ret = __ashmem_is_ashmem(fd, 1);
    if (ret < 0) {
    
    
        return ret;
    }

    return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin));
}

int ashmem_unpin_region(int fd, size_t offset, size_t len)
{
    
    
    // TODO: should LP64 reject too-large offset/len?
    ashmem_pin pin = {
    
     static_cast<uint32_t>(offset), static_cast<uint32_t>(len) };

    int ret = __ashmem_is_ashmem(fd, 1);
    if (ret < 0) {
    
    
        return ret;
    }

    return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_UNPIN, &pin));
}

int ashmem_get_size_region(int fd)
{
    
    
    int ret = __ashmem_is_ashmem(fd, 1);
    if (ret < 0) {
    
    
        return ret;
    }

    return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL));
}

实现

ashmme的典型用法是先打开设备文件,然后做mmap映射。
第一步通过调用ashmem_create_region函数,这个函数完成这几件事:

  1. fd = open("/dev/ashmem", O_RDWR);
  2. ioctl(fd, ASHMEM_SET_NAME, region_name); // 这一步可选
  3. ioctl(fd, ASHMEM_SET_SIZE, region_size);

第二步,应用程序一般会调用mmap来把ashmem分配的空间映射到进程空间:
mapAddr = mmap(NULL, pHdr->mapLength, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);

应用程序还可以通过ioctl来pin和unpin某一段映射的空间,以提示内核的page cache算法可以把哪些页面回收,这是一般mmap做不到的。
可以说ashmem以较小的代价(用户需进行额外的ioctl调用来设置名字,大小,pin和unpin),获得了一些内存使用的智能性。

扫描二维码关注公众号,回复: 11717674 查看本文章

特点

ashmem本身实现也很小巧,只有不到700行。原因是借助了内核已经有的工具,例如:

  • shmem_file_setup(支撑文件)
  • cache_shrinker(slab分配算法的页面回收的回调函数)等。

如果不使用ashmem驱动,并且舍弃pin/unpin语义,那么模拟ashmem的语义还是很简单的。首先,ashmem_create_region可以为进程创建一个唯一的文件(如进程名+时戳),打开,然后返回这个文件的fd;接着应用程序可以进行一般的mmap操作了。如果不使用ashmem_create_region接口函数,那么使用anonymous的mmap就可以了,但这种方式属于正在被丢弃的方式,而且并不是所有的系统都支持,比如Macos就不支持。

猜你喜欢

转载自blog.csdn.net/zhoumushui/article/details/108583046