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 ,表示内存已经被回收,需要重新进行物理内存的分配。
关于回收内存的原理解读:
- 当调用
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<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计算在内。
|