本篇内容主要讲解“Android匿名内存怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android匿名内存怎么实现”吧!
Android 匿名内存解析
有了binder机制为什么还需要匿名内存来实现IPC呢?我觉得很大的原因就是binder传输是有大小限制的,不说应用层的限制。在驱动中binder的传输大小被限制在了4M,分享一张图片可能就超过了这个限制。匿名内存的主要解决思路就是通过binder传输文件描述符,使得两个进程都能访问同一个地址来实现共享。
MemoryFile使用
在平常开发中android提供了MemoryFile来实现匿名内存。看下最简单的实现。
Service端
const val GET_ASH_MEMORY = 1000 class MyService : Service() { val ashData = "AshDemo".toByteArray() override fun onBind(intent: Intent): IBinder { return object : Binder() { override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { when(code){ GET_ASH_MEMORY->{//收到客户端请求的时候会烦 val descriptor = createMemoryFile() reply?.writeParcelable(descriptor, 0) reply?.writeInt(ashData.size) return true } else->{ return super.onTransact(code, data, reply, flags) } } } } } private fun createMemoryFile(): ParcelFileDescriptor? { val file = MemoryFile("AshFile", 1024)//创建MemoryFile val descriptorMethod = file.javaClass.getDeclaredMethod("getFileDescriptor") val fd=descriptorMethod.invoke(file)//反射拿到fd file.writeBytes(ashData, 0, 0,ashData.size)//写入字符串 return ParcelFileDescriptor.dup(fd as FileDescriptor?)//返回一个封装的fd } }
Server的功能很简单收到GET_ASH_MEMORY请求的时候创建一个MemoryFile,往里写入一个字符串的byte数组,然后将fd和字符长度写入reply中返回给客户端。
Client端
class MainActivity : AppCompatActivity() { val connect = object :ServiceConnection{ override fun onServiceConnected(name: ComponentName?, service: IBinder?) { val reply = Parcel.obtain() val sendData = Parcel.obtain() service?.transact(GET_ASH_MEMORY, sendData, reply, 0)//传输信号GET_ASH_MEMORY val pfd = reply.readParcelable<ParcelFileDescriptor>(javaClass.classLoader) val descriptor = pfd?.fileDescriptor//拿到fd val size = reply.readInt()//拿到长度 val input = FileInputStream(descriptor) val bytes = input.readBytes() val message = String(bytes, 0, size, Charsets.UTF_8)//生成string Toast.makeText(this@MainActivity,message,Toast.LENGTH_SHORT).show() } override fun onServiceDisconnected(name: ComponentName?) { } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<TextView>(R.id.intent).setOnClickListener { //启动服务 bindService(Intent(this,MyService::class.java),connect, Context.BIND_AUTO_CREATE) } } }
客户端也很简单,启动服务,发送一个获取MemoryFile的请求,然后通过reply拿到fd和长度,用FileInputStream读取fd中的内容,最后通过toast可以验证这个message已经拿到了。
AshMemory 创建原理
public MemoryFile(String name, int length) throws IOException { try { mSharedMemory = SharedMemory.create(name, length); mMapping = mSharedMemory.mapReadWrite(); } catch (ErrnoException ex) { ex.rethrowAsIOException(); } }
MemoryFile就是对SharedMemory的一层封装,具体的工能都是SharedMemory实现的。看SharedMemory的实现。
public static @NonNull SharedMemory create(@Nullable String name, int size) throws ErrnoException { if (size <= 0) { throw new IllegalArgumentException("Size must be greater than zero"); } return new SharedMemory(nCreate(name, size)); } private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;
通过一个JNI获得fd,从这里可以推断出java层也只是一个封装,拿到的已经是创建好的fd。
//frameworks/base/core/jni/android_os_SharedMemory.cpp jobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) { const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr; int fd = ashmem_create_region(name, size);//创建匿名内存块 int err = fd < 0 ? errno : 0; if (name) { env->ReleaseStringUTFChars(jname, name); } if (fd < 0) { jniThrowErrnoException(env, "SharedMemory_create", err); return nullptr; } jobject jifd = jniCreateFileDescriptor(env, fd);//创建java fd返回 if (jifd == nullptr) { close(fd); } return jifd; }
通过cutils中的ashmem_create_region函数实现的创建
//system/core/libcutils/ashmem-dev.cpp int ashmem_create_region(const char *name, size_t size) { int ret, save_errno; if (has_memfd_support()) {//老版本兼容用 return memfd_create_region(name ? name : "none", size); } int fd = __ashmem_open();//打开Ashmem驱动 if (fd < 0) { return fd; } if (name) { char buf[ASHMEM_NAME_LEN] = {0}; strlcpy(buf, name, sizeof(buf)); ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf));//通过ioctl设置名字 if (ret < 0) { goto error; } } ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));//通过ioctl设置大小 if (ret < 0) { goto error; } return fd; error: save_errno = errno; close(fd); errno = save_errno; return ret; }
标准的驱动交互操作
1.open打开驱动
2.通过ioctl与驱动进行交互
下面看下open的流程
static int __ashmem_open() { int fd; pthread_mutex_lock(&__ashmem_lock); fd = __ashmem_open_locked(); pthread_mutex_unlock(&__ashmem_lock); return fd; } /* logistics of getting file descriptor for ashmem */ static int __ashmem_open_locked() { static const std::string ashmem_device_path = get_ashmem_device_path();//拿到Ashmem驱动路径 if (ashmem_device_path.empty()) { return -1; } int fd = TEMP_FAILURE_RETRY(open(ashmem_device_path.c_str(), O_RDWR | O_CLOEXEC)); return fd; }
回到MemoryFile的构造函数中,拿到了驱动的fd之后调用了mapReadWrite
public @NonNull ByteBuffer mapReadWrite() throws ErrnoException { return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize); } public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException { checkOpen(); validateProt(prot); if (offset < 0) { throw new IllegalArgumentException("Offset must be >= 0"); } if (length <= 0) { throw new IllegalArgumentException("Length must be > 0"); } if (offset + length > mSize) { throw new IllegalArgumentException("offset + length must not exceed getSize()"); } long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);//调用了系统的mmap boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0; Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire()); return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly); }
到这里就有一个疑问,Linux就有共享内存,android为什么要自己搞一套,只能看下Ashmemory驱动的实现了。
驱动第一步看init和file_operations
static int __init ashmem_init(void) { int ret = -ENOMEM; ashmem_area_cachep = kmem_cache_create("ashmem_area_cache", sizeof(struct ashmem_area), 0, 0, NULL);//创建 if (!ashmem_area_cachep) { pr_err("failed to create slab cache "); goto out; } ashmem_range_cachep = kmem_cache_create("ashmem_range_cache", sizeof(struct ashmem_range), 0, SLAB_RECLAIM_ACCOUNT, NULL);//创建 if (!ashmem_range_cachep) { pr_err("failed to create slab cache "); goto out_free1; } ret = misc_register(&ashmem_misc);//注册为了一个misc设备 ........ return ret; }
创建了两个内存分配器ashmem_area_cachep和ashmem_range_cachep用于分配ashmem_area和ashmem_range
//common/drivers/staging/android/ashmem.c static const struct file_operations ashmem_fops = { .owner = THIS_MODULE, .open = ashmem_open, .release = ashmem_release, .read_iter = ashmem_read_iter, .llseek = ashmem_llseek, .mmap = ashmem_mmap, .unlocked_ioctl = ashmem_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = compat_ashmem_ioctl, #endif #ifdef CONFIG_PROC_FS .show_fdinfo = ashmem_show_fdinfo, #endif };
open调用的就是ashmem_open
static int ashmem_open(struct inode *inode, struct file *file) { struct ashmem_area *asma; int ret; ret = generic_file_open(inode, file); if (ret) return ret; asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);//分配一个ashmem_area if (!asma) return -ENOMEM; INIT_LIST_HEAD(&asma->unpinned_list);//初始化unpinned_list memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);//初始化一个名字 asma->prot_mask = PROT_MASK; file->private_data = asma; return 0; }
ioctl设置名字和长度调用的就是ashmem_ioctl
static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ashmem_area *asma = file->private_data; long ret = -ENOTTY; switch (cmd) { case ASHMEM_SET_NAME: ret = set_name(asma, (void __user *)arg); break; case ASHMEM_SET_SIZE: ret = -EINVAL; mutex_lock(&ashmem_mutex); if (!asma->file) { ret = 0; asma->size = (size_t)arg; } mutex_unlock(&ashmem_mutex); break; } ........ }
实现也都很简单就是改变了一下asma里的值。接下来就是重点mmap了,具体是怎么分配内存的。
static int ashmem_mmap(struct file *file, struct vm_area_struct *vma) { static struct file_operations vmfile_fops; struct ashmem_area *asma = file->private_data; int ret = 0; mutex_lock(&ashmem_mutex); /* user needs to SET_SIZE before mapping */ if (!asma->size) {//判断设置了size ret = -EINVAL; goto out; } /* requested mapping size larger than object size */ if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {//判断大小是否超过了虚拟内存 ret = -EINVAL; goto out; } /* requested protection bits must match our allowed protection mask */ if ((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) & calc_vm_prot_bits(PROT_MASK, 0)) {//权限判断 ret = -EPERM; goto out; } vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask); if (!asma->file) {//是否创建过临时文件,没创建过进入 char *name = ASHMEM_NAME_DEF; struct file *vmfile; struct inode *inode; if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '