欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

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

程序员文章站 2022-06-21 19:51:30
目录MemoryFile主要功能代码实现Ashmem优势主要方法实现特点最近看EVS的代码,其中预览YUV数据时用到了MemoryFile(内存文件)。在Binder的onTransact时,操作FileDescriptor来实现:@Overrideprotected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {//... ... Parcel _data = Parcel.obtain();...


最近看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),获得了一些内存使用的智能性。

特点

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

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

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

本文地址:https://blog.csdn.net/zhoumushui/article/details/108583046