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

112 篇文章 81 订阅

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.c
  • frameworks/base/core/jni/android_os_MemoryFile.cpp
  • frameworks/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,表示内存已经被回收,需要重新进行物理内存的分配。

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

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

2.2 binder传递fd

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

  1. 取出进程A发送方binder数据里的fd1,通过fd1找到文件对象X。
  2. 为目标进程B创建fd2,将目标进程B的fd2和文件对象X进行关联。
  3. 将进程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<jlong>(result);
}

//内存映射解除munmap
static void android_os_MemoryFile_munmap(JNIEnv* env, jobject clazz, jlong addr, jint length)
{
    int result = munmap(reinterpret_cast<void *>(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计算在内。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在内容上,《Android系统源代码情景分析(含CD光盘1张)》结合使用情景,全面、深入、细致地分析了Android系统的源代码,涉及到Linux内核层、硬件抽象层(HAL)、运行时库层(Runtime)、应用程序框架层(Application Framework)以及应用程序层(Application)。   在组织上,《Android系统源代码情景分析(含CD光盘1张)》将上述内容划分为初识Android系统、Android专用驱动系统和Android应用程序框架三大篇。初识Android系统篇介绍了参考书籍、基础知识以及实验环境搭建;Android专用驱动系统篇介绍了Logger日志驱动程序、Binder进程间通信驱动程序以及Ashmem匿名共享内存驱动程序;Android应用程序框架篇从组件、进程、消息以及安装四个维度对Android应用程序的框架进行了深入的剖析。   通过上述内容及其组织,本书能使读者既能从整体上把握Android系统的层次结构,又能从细节上掌握每一个层次的要点。 第1篇 初识Android系统 第1章 准备知识 第2章 硬件抽象层 第3章 智能指针 第2篇 Android专用驱动系统 第4章 Logger日志系统 第5章 Binder进程间通信系统 第6章 Ashmem匿名共享内存系统 第7章 Activity组件的启动过程 第8章 Service组件的启动过程 第9章 Android系统广播机制10章 Content Provider组件的实现原理 第11章 Zygote和System进程的启动过程 第12章 Android应用程序进程的启动过程 第13章 Android应用程序的消息处理机制 第14章 Android应用程序的键盘消息处理机制 第15章 Android应用程序线程的消息循环模型 第16章 Android应用程序的安装和显示过程
### 回答1: Android C的共享内存是一种高效的IPC机制,它允许不同的进程共享内存区域,从而实现数据共享和数据传输。在Android系统中,使用共享内存有两种基本方法:POSIX共享内存Ashmem。 POSIX共享内存(shm_open系统调用)是基于文件的IPC机制,它可以在不同的进程间共享文件系统中的内存块。在使用该方法时,首先创建并打开一个共享内存对象以便其他进程能够在其中写入或读取数据。与普通文件不同的是,该对象可以被多个进程同时访问,从而实现内存共享和数据传输。 AshmemAndroid专有的共享内存机制,它通过匿名内存映射(mmap系统调用)来创建共享内存,使多个进程可以共享相同的内存区域。在使用Ashmem时,首先在一个进程中分配一块内存区域,并将其标记为共享内存。其他进程可以通过Binder接口来获取该内存区域所对应的Ashmem文件描述符,并进一步映射内存区域,以便共享数据。 正如所见,Android C的共享内存机制提供了一种高效的IPC方法,可以在不同的进程之间实现数据共享和数据传输。但是由于共享内存存在并发访问、内存泄露等问题,因此在应用中使用时需要格外小心。 ### 回答2: Android C共享内存是一种在Android系统中用于不同进程间共享数据的机制。在多进程应用程序中,进程之间共享数据允许各个进程共同访问数据,从而提高系统的整体性能。C共享内存实现了这种数据共享的方式,允许多个进程可以同步地访问相同的内存区域,从而实现数据共享。 C共享内存操作需要用到管道和信号量等Linux中的IPC技术。进程可以通过信号量来控制对共享内存区域的访问,从而实现数据同步。同时,通过管道机制,同步地向共享内存区域写入和读出数据。在Android开发中,通常会使用NDK库和底层C语言来实现共享内存操作,可以对共享内存区域进行读写操作和管理。 通常情况下,在Android的多进程应用程序中,可以使用C共享内存来实现不同进程之间的数据共享,从而提高应用程序的整体性能和响应速度。C共享内存也可以被用于进程间的通信,例如在游戏和音视频应用程序中,可以使用共享内存来实现不同进程的交互与协作。总的来说,Android C共享内存提供了一种能够优化应用程序性能和提高用户体验的底层机制

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值