IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> android系统核心机制 基础(10)Ashmem匿名共享内存机制 -> 正文阅读

[移动开发]android系统核心机制 基础(10)Ashmem匿名共享内存机制

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计算在内。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-04 15:44:08  更:2022-03-04 15:46:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 17:52:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码