android系统核心机制 基础(10)Ashmem匿名共享内存机制

您所在的位置:网站首页 安卓的内存机制有哪些 android系统核心机制 基础(10)Ashmem匿名共享内存机制

android系统核心机制 基础(10)Ashmem匿名共享内存机制

2024-07-17 01:36| 来源: 网络整理| 查看: 265

1 Ashmem匿名共享内存机制 简介

Ashmem是一种匿名共享内存机制,主要用于进程间大量传递数据。

1.1 为什么要有Ashmem匿名共享内存机制?

Android系统已经添加了Binder这个高效的跨进程通信的机制,那为什么还要搞一个Ashmem 匿名共享内存机制呢?

因为binder机制主要用于进程间的通信,适合进程间的方法调用(A进程的X方法调用B进程的Y方法),但如果进程间需要传输大量数据则并不可行,关于binder传递数据的限制我们可以看Binder初始化时的宏定义($AOSP/frameworks/native/libs/binder/ProcessState.cpp),关键定义如下:

//binder大小限制为1M-2Page(1Page=4k),即限制微1016k #define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))

而这就是Ashmem匿名共享内存机制的设计初衷。

1.2 Ashmem的分析解读

本文 主要从驱动层、native层到上层java层逐层进行简单分析,以MemoryFile为例加深理解。

驱动层代码位置:

/drivers/staging/android/ashmem.c/drivers/staging/android/uapi/ashmem.h

​framework native层代码位置:

system/core/libcutils/Ashmem-dev.cframeworks/base/core/jni/android_os_MemoryFile.cppframeworks/base/core/jni/android_os_MemoryFile.h

framework java层代码位置:

frameworks/base/core/java/android/os/MemoryFile.java 2 Ashmem核心机制解读

Ashmem的核心机制主要是驱动层(上层主要是封装和使用)和binder传递fd机制。

2.1 驱动层关键点解读 2.1.1 ashmem驱动初始化init方法

驱动代码首先关注init的代码实现,如下所示:

//ashmem的fops 操作方法集 static const struct file_operations ashmem_fops = { .owner = THIS_MODULE, .open = ashmem_open, .release = ashmem_release, .read = ashmem_read, .llseek = ashmem_llseek, .mmap = ashmem_mmap, .unlocked_ioctl = ashmem_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = compat_ashmem_ioctl, #endif }; //misc设备 static struct miscdevice ashmem_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "ashmem", .fops = &ashmem_fops, }; 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; } //这里创建misc设备,在/dev目录下生成一个ashmem设备文件。 ret = misc_register(&ashmem_misc); if (unlikely(ret)) { pr_err("failed to register misc device!\n"); return ret; } //当系统内存紧张时会回调ashmem_shrink,由驱动进行适当的内存回收。 register_shrinker(&ashmem_shrinker); pr_info("initialized\n"); return 0; }

这里我们留意下:ashmem设备文件提供 open、mmap、release和ioctl四种操作,但没有read和write操作。这是为什么呢?因为读写共享内存的方法是通过内存映射地址来进行的,即通过mmap系统调用把这个设备文件映射到进程地址空间中,之后就可以直接对内存进行读写了,不需要通过read 和write文件操作。

2.1.2 ashmem驱动中的ioctl关键命令集锦和解读

驱动中open、release、mmap、munmap这几个常见的命令比较好理解,这里重点解读ashmem中的ioctl中命令,定义如下所示:

//设置匿名共享内存名 #define ASHMEM_SET_NAME _IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN]) //获取匿名共享内存名 #define ASHMEM_GET_NAME _IOR(__ASHMEMIOC, 2, char[ASHMEM_NAME_LEN]) //设置匿名共享内存大小 #define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t) //获取匿名共享内存大小 #define ASHMEM_GET_SIZE _IO(__ASHMEMIOC, 4) //设置mask,可读/可写/可执行 #define ASHMEM_SET_PROT_MASK _IOW(__ASHMEMIOC, 5, unsigned long) //获取mask #define ASHMEM_GET_PROT_MASK _IO(__ASHMEMIOC, 6) //匿名共享内存pin操作,锁定空间 #define ASHMEM_PIN _IOW(__ASHMEMIOC, 7, struct ashmem_pin) //匿名共享内存unpin操作,解锁空间 #define ASHMEM_UNPIN _IOW(__ASHMEMIOC, 8, struct ashmem_pin) //获取匿名共享内存 pin状态 #define ASHMEM_GET_PIN_STATUS _IO(__ASHMEMIOC, 9) //回收所有匿名共享内存 #define ASHMEM_PURGE_ALL_CACHES _IO(__ASHMEMIOC, 10)

以上命令中 获取/设置 名字、大小、mask也都是常规操作,ASHMEM_PURGE_ALL_CACHES命令表示回收操作,这里着重解读pin和unpin操作,如下:

pin:锁定空间,默认直接创建的共享内存默认是pin的状态,即只要不主动关闭共享内存fd,这块内存就会始终保留,直到进程死亡。unpin:解锁空间,如果调用unpin方法,则后面若系统内存不足,会自动释放这部分内存,如果再次使用同一段内存则应该先执行pin操作,如果pin返回ASHMEM_WAS_PURGED,表示内存已经被回收,需要重新进行物理内存的分配。

关于回收内存的原理解读:

当调用unpin时,驱动将该部分内存区域所在的Page挂在一个unpinned_list链表上。ashmem_init中注册了内存回收回调函数ashmem_shrink。当系统内存紧张时,就会回调ashmem_shrink,由驱动进行适当的内存回收。在ashmem_shrink中遍历unpinned_list进行内存回收,以释放物理内存。 2.2 binder传递fd

Binder机制本身支持文件描述符的传递。这里以进程A的fd1 转换为 进程B的fd2的过程为例:

取出进程A发送方binder数据里的fd1,通过fd1找到文件对象X。为目标进程B创建fd2,将目标进程B的fd2和文件对象X进行关联。将进程A发送方binder数据里的fd1转换为目标进程的fd2(dup操作),将数据发送给目标进程B。

这相当于文件在目标进程又打开了一次,目标进程B使用的是自己的fd2,但进程A的fd1和进程B的fd2都指向同一内存区域(可以将这块内存理解为临时文件)。此时源进程A和目标进程B都可以map到同一片内存。如下所示:

总结:使用fd传递 + 内存映射 这样的设计 满足了Buffer传递 同时又避免了进程间内存拷贝 所消耗的资源,提升系统效率。

3 framework native层解读

这里以MemoryFile的native层实现为例,native层android_os_MemoryFile.cpp通过调用Ashmem-dev.c中封装的方法进而调用到驱动层的fops方法,因此这里先解读Ashmem-dev.c中的内容,如下所示:

#define ASHMEM_DEVICE "/dev/ashmem" int ashmem_create_region(const char *name, size_t size) { int fd, ret; fd = open(ASHMEM_DEVICE, O_RDWR); if (fd < 0) return fd; if (name) { char buf[ASHMEM_NAME_LEN] = {0}; strlcpy(buf, name, sizeof(buf)); ret = ioctl(fd, ASHMEM_SET_NAME, buf); if (ret < 0) goto error; } ret = ioctl(fd, ASHMEM_SET_SIZE, size); if (ret < 0) goto error; return fd; error: close(fd); return ret; } int ashmem_set_prot_region(int fd, int prot) { return ioctl(fd, ASHMEM_SET_PROT_MASK, prot); } 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); } int ashmem_get_size_region(int fd) { return ioctl(fd, ASHMEM_GET_SIZE, NULL); }

内容较为简单,就是驱动层的封装。接下来看android_os_MemoryFile.cpp的实现,代码如下:

namespace android { //打开设备节点并返回fd操作 static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length) { const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL); int result = ashmem_create_region(namestr, length); if (name) env->ReleaseStringUTFChars(name, namestr); if (result < 0) { jniThrowException(env, "java/io/IOException", "ashmem_create_region failed"); return NULL; } return jniCreateFileDescriptor(env, result); } //内存映射mmap static jlong android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, jint length, jint prot) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); void* result = mmap(NULL, length, prot, MAP_SHARED, fd, 0); if (result == MAP_FAILED) { jniThrowException(env, "java/io/IOException", "mmap failed"); } return reinterpret_cast(result); } //内存映射解除munmap static void android_os_MemoryFile_munmap(JNIEnv* env, jobject clazz, jlong addr, jint length) { int result = munmap(reinterpret_cast(addr), length); if (result < 0) jniThrowException(env, "java/io/IOException", "munmap failed"); } //关闭fd static void android_os_MemoryFile_close(JNIEnv* env, jobject clazz, jobject fileDescriptor) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); if (fd >= 0) { jniSetFileDescriptorOfFD(env, fileDescriptor, -1); close(fd); } } //读数据操作 static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz, jobject fileDescriptor, jlong 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); jniThrowException(env, "java/io/IOException", "ashmem region was purged"); return -1; } //数组传递,将(const jbyte *)address + srcOffset中的内容拷贝到buffer中 env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset); if (unpinned) { ashmem_unpin_region(fd, 0, 0); } return count; } //写数据操作 static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz, jobject fileDescriptor, jlong 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); jniThrowException(env, "java/io/IOException", "ashmem region was purged"); return -1; } //数组传递,将buffer中内容拷贝到 (jbyte *)address + destOffset中 env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset); if (unpinned) { ashmem_unpin_region(fd, 0, 0); } return count; } //根据需要执行pin、unpin操作 static void 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); } } //获取匿名共享内存大小 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; } //native方法 对应关系表 static const JNINativeMethod methods[] = { {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, {"native_mmap", "(Ljava/io/FileDescriptor;II)J", (void*)android_os_MemoryFile_mmap}, {"native_munmap", "(JI)V", (void*)android_os_MemoryFile_munmap}, {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, {"native_read", "(Ljava/io/FileDescriptor;J[BIIIZ)I", (void*)android_os_MemoryFile_read}, {"native_write", "(Ljava/io/FileDescriptor;J[BIIIZ)V", (void*)android_os_MemoryFile_write}, {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, {"native_get_size", "(Ljava/io/FileDescriptor;)I", (void*)android_os_MemoryFile_get_size} }; //注册native方法 int register_android_os_MemoryFile(JNIEnv* env) { return AndroidRuntime::registerNativeMethods(env, "android/os/MemoryFile",methods, NELEM(methods)); } }

可以看到 android_os_MemoryFile.cpp主要是通过Ashmem-dev.c调用驱动中fops中的方法。逐层封装,同时也是MemoryFile在native层的封装和实现。

4 framework java层解读

MemoryFile关键代码简要解读如下:

public class MemoryFile { private static String TAG = "MemoryFile"; //native方法调用 private static native FileDescriptor native_open(String name, int length) throws IOException; // returns memory address for ashmem region private static native long native_mmap(FileDescriptor fd, int length, int mode) throws IOException; private static native void native_munmap(long addr, int length) throws IOException; private static native void native_close(FileDescriptor fd); private static native int native_read(FileDescriptor fd, long address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; private static native void native_write(FileDescriptor fd, long address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; private static native int native_get_size(FileDescriptor fd) throws IOException; private FileDescriptor mFD; //ashmem 匿名内存文件描述符 private long mAddress; //ashmem memory首地址 private int mLength; //ashmem region的长度 private boolean mAllowPurging = false; // unpin:true;pin:flase //构造器,封装open & mmap操作 public MemoryFile(String name, int length) throws IOException { mLength = length; if (length >= 0) { mFD = native_open(name, length); } else { throw new IOException("Invalid length: " + length); } if (length > 0) { mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE); } else { mAddress = 0; } } //封装munmap操作 void deactivate() { if (!isDeactivated()) { try { native_munmap(mAddress, mLength); mAddress = 0; } catch (IOException ex) { Log.e(TAG, ex.toString()); } } } private boolean isDeactivated() { return mAddress == 0; } //pin操作 synchronized public boolean allowPurging(boolean allowPurging) throws IOException { boolean oldValue = mAllowPurging; if (oldValue != allowPurging) { native_pin(mFD, !allowPurging); mAllowPurging = allowPurging; } return oldValue; } //封装read操作 public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count) throws IOException { //... return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); } //封装write操作 public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count) throws IOException { //... native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); } //... private class MemoryInputStream extends InputStream { //... } private class MemoryOutputStream extends OutputStream { //... } }

MemoryFile使用注意事项:

@1 如果想使用MemoryFile 来进行进程间的大数据通信,那么关键在于通过binder将 fd在进程之间进行传递。

@2 MemoryFile中的getFileDescriptor方法在系统中是@hide的,需要通过反射的方式才能拿到,如下所示:

Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor"); FileDescriptor des = (FileDescriptor) method.invoke(memoryFile); 6 总结

Ashmem机制总结:

Ashmem机制相当于Linux共享内存的扩展,扩展后使用更加便捷。通过binder传递fd这种方式增加了安全性,同时避免了buffer拷贝,效率提升。

Ashmem应用范围:

binder 跨进程的大数据传递,MemoryFile类的应用 安卓显示系统中gralloc库中使用的就是ashmem机制来传递数据(安卓系统上层创建surface时由SurfaceFlinger 使用gralloc模块分配内存,gralloc HAL层中就是使用Ashmem来传递帧buffer数据)。

Ashmem使用注意:

匿名共享内存不会占用Dalvik Heap与Native Heap,不会导致OOM。共享存占用空间的计算,是计算到第一个创建它的进程中,其他进程不会将ashmem计算在内。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3