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.cframeworks/base/core/jni/android_os_MemoryFile.cppframeworks/base/core/jni/android_os_MemoryFile.hframework java层代码位置:

frameworks/base/core/java/android/os/MemoryFile.java2 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计算在内。

相关文章

彩票365苹果版怎么下载不了 第八节 破伤风

第八节 破伤风

🗓️ 08-21 👁️ 581
365结束投注 快速提升网站权重的技巧(15个实用方法帮助你提升网站权重)
彩票365苹果版怎么下载不了 纹关公的忌讳和讲究 细数关公开眼的严重后果
365结束投注 轻松设置QQ代理服务器,解决网络连接问题的详细指南
365体育投注网址亚洲下载 新生儿为何睡着时咧嘴笑?宝宝越早笑越聪明,8个逗笑方法现学现用
365体育投注网址亚洲下载 安卓手机怎么锁软件(如何在安卓手机上锁定应用程序)