Android匿名内存怎么实现

其他教程   发布日期:2023年08月31日   浏览次数:588

本篇内容主要讲解“Android匿名内存怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android匿名内存怎么实现”吧!

    Android 匿名内存解析

    有了binder机制为什么还需要匿名内存来实现IPC呢?我觉得很大的原因就是binder传输是有大小限制的,不说应用层的限制。在驱动中binder的传输大小被限制在了4M,分享一张图片可能就超过了这个限制。匿名内存的主要解决思路就是通过binder传输文件描述符,使得两个进程都能访问同一个地址来实现共享。

    MemoryFile使用

    在平常开发中android提供了MemoryFile来实现匿名内存。看下最简单的实现。

    Service端

    1. const val GET_ASH_MEMORY = 1000
    2. class MyService : Service() {
    3. val ashData = "AshDemo".toByteArray()
    4. override fun onBind(intent: Intent): IBinder {
    5. return object : Binder() {
    6. override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
    7. when(code){
    8. GET_ASH_MEMORY->{//收到客户端请求的时候会烦
    9. val descriptor = createMemoryFile()
    10. reply?.writeParcelable(descriptor, 0)
    11. reply?.writeInt(ashData.size)
    12. return true
    13. }
    14. else->{
    15. return super.onTransact(code, data, reply, flags)
    16. }
    17. }
    18. }
    19. }
    20. }
    21. private fun createMemoryFile(): ParcelFileDescriptor? {
    22. val file = MemoryFile("AshFile", 1024)//创建MemoryFile
    23. val descriptorMethod = file.javaClass.getDeclaredMethod("getFileDescriptor")
    24. val fd=descriptorMethod.invoke(file)//反射拿到fd
    25. file.writeBytes(ashData, 0, 0,ashData.size)//写入字符串
    26. return ParcelFileDescriptor.dup(fd as FileDescriptor?)//返回一个封装的fd
    27. }
    28. }

    Server的功能很简单收到GET_ASH_MEMORY请求的时候创建一个MemoryFile,往里写入一个字符串的byte数组,然后将fd和字符长度写入reply中返回给客户端。

    Client端

    1. class MainActivity : AppCompatActivity() {
    2. val connect = object :ServiceConnection{
    3. override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
    4. val reply = Parcel.obtain()
    5. val sendData = Parcel.obtain()
    6. service?.transact(GET_ASH_MEMORY, sendData, reply, 0)//传输信号GET_ASH_MEMORY
    7. val pfd = reply.readParcelable<ParcelFileDescriptor>(javaClass.classLoader)
    8. val descriptor = pfd?.fileDescriptor//拿到fd
    9. val size = reply.readInt()//拿到长度
    10. val input = FileInputStream(descriptor)
    11. val bytes = input.readBytes()
    12. val message = String(bytes, 0, size, Charsets.UTF_8)//生成string
    13. Toast.makeText(this@MainActivity,message,Toast.LENGTH_SHORT).show()
    14. }
    15. override fun onServiceDisconnected(name: ComponentName?) {
    16. }
    17. }
    18. override fun onCreate(savedInstanceState: Bundle?) {
    19. super.onCreate(savedInstanceState)
    20. setContentView(R.layout.activity_main)
    21. findViewById<TextView>(R.id.intent).setOnClickListener {
    22. //启动服务
    23. bindService(Intent(this,MyService::class.java),connect, Context.BIND_AUTO_CREATE)
    24. }
    25. }
    26. }

    客户端也很简单,启动服务,发送一个获取MemoryFile的请求,然后通过reply拿到fd和长度,用FileInputStream读取fd中的内容,最后通过toast可以验证这个message已经拿到了。

    AshMemory 创建原理

    1. public MemoryFile(String name, int length) throws IOException {
    2. try {
    3. mSharedMemory = SharedMemory.create(name, length);
    4. mMapping = mSharedMemory.mapReadWrite();
    5. } catch (ErrnoException ex) {
    6. ex.rethrowAsIOException();
    7. }
    8. }

    MemoryFile就是对SharedMemory的一层封装,具体的工能都是SharedMemory实现的。看SharedMemory的实现。

    1. public static @NonNull SharedMemory create(@Nullable String name, int size)
    2. throws ErrnoException {
    3. if (size <= 0) {
    4. throw new IllegalArgumentException("Size must be greater than zero");
    5. }
    6. return new SharedMemory(nCreate(name, size));
    7. }
    8. private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;

    通过一个JNI获得fd,从这里可以推断出java层也只是一个封装,拿到的已经是创建好的fd。

    1. //frameworks/base/core/jni/android_os_SharedMemory.cpp
    2. jobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) {
    3. const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr;
    4. int fd = ashmem_create_region(name, size);//创建匿名内存块
    5. int err = fd < 0 ? errno : 0;
    6. if (name) {
    7. env->ReleaseStringUTFChars(jname, name);
    8. }
    9. if (fd < 0) {
    10. jniThrowErrnoException(env, "SharedMemory_create", err);
    11. return nullptr;
    12. }
    13. jobject jifd = jniCreateFileDescriptor(env, fd);//创建java fd返回
    14. if (jifd == nullptr) {
    15. close(fd);
    16. }
    17. return jifd;
    18. }

    通过cutils中的ashmem_create_region函数实现的创建

    1. //system/core/libcutils/ashmem-dev.cpp
    2. int ashmem_create_region(const char *name, size_t size)
    3. {
    4. int ret, save_errno;
    5. if (has_memfd_support()) {//老版本兼容用
    6. return memfd_create_region(name ? name : "none", size);
    7. }
    8. int fd = __ashmem_open();//打开Ashmem驱动
    9. if (fd < 0) {
    10. return fd;
    11. }
    12. if (name) {
    13. char buf[ASHMEM_NAME_LEN] = {0};
    14. strlcpy(buf, name, sizeof(buf));
    15. ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf));//通过ioctl设置名字
    16. if (ret < 0) {
    17. goto error;
    18. }
    19. }
    20. ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));//通过ioctl设置大小
    21. if (ret < 0) {
    22. goto error;
    23. }
    24. return fd;
    25. error:
    26. save_errno = errno;
    27. close(fd);
    28. errno = save_errno;
    29. return ret;
    30. }

    标准的驱动交互操作

    1.open打开驱动

    2.通过ioctl与驱动进行交互

    下面看下open的流程

    1. static int __ashmem_open()
    2. {
    3. int fd;
    4. pthread_mutex_lock(&__ashmem_lock);
    5. fd = __ashmem_open_locked();
    6. pthread_mutex_unlock(&__ashmem_lock);
    7. return fd;
    8. }
    9. /* logistics of getting file descriptor for ashmem */
    10. static int __ashmem_open_locked()
    11. {
    12. static const std::string ashmem_device_path = get_ashmem_device_path();//拿到Ashmem驱动路径
    13. if (ashmem_device_path.empty()) {
    14. return -1;
    15. }
    16. int fd = TEMP_FAILURE_RETRY(open(ashmem_device_path.c_str(), O_RDWR | O_CLOEXEC));
    17. return fd;
    18. }

    回到MemoryFile的构造函数中,拿到了驱动的fd之后调用了mapReadWrite

    1. public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {
    2. return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);
    3. }
    4. public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
    5. checkOpen();
    6. validateProt(prot);
    7. if (offset < 0) {
    8. throw new IllegalArgumentException("Offset must be >= 0");
    9. }
    10. if (length <= 0) {
    11. throw new IllegalArgumentException("Length must be > 0");
    12. }
    13. if (offset + length > mSize) {
    14. throw new IllegalArgumentException("offset + length must not exceed getSize()");
    15. }
    16. long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);//调用了系统的mmap
    17. boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;
    18. Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());
    19. return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
    20. }

    到这里就有一个疑问,Linux就有共享内存,android为什么要自己搞一套,只能看下Ashmemory驱动的实现了。

    驱动第一步看init和file_operations

    1. static int __init ashmem_init(void)
    2. {
    3. int ret = -ENOMEM;
    4. ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
    5. sizeof(struct ashmem_area),
    6. 0, 0, NULL);//创建
    7. if (!ashmem_area_cachep) {
    8. pr_err("failed to create slab cache
    9. ");
    10. goto out;
    11. }
    12. ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
    13. sizeof(struct ashmem_range),
    14. 0, SLAB_RECLAIM_ACCOUNT, NULL);//创建
    15. if (!ashmem_range_cachep) {
    16. pr_err("failed to create slab cache
    17. ");
    18. goto out_free1;
    19. }
    20. ret = misc_register(&ashmem_misc);//注册为了一个misc设备
    21. ........
    22. return ret;
    23. }

    创建了两个内存分配器ashmem_area_cachep和ashmem_range_cachep用于分配ashmem_area和ashmem_range

    1. //common/drivers/staging/android/ashmem.c
    2. static const struct file_operations ashmem_fops = {
    3. .owner = THIS_MODULE,
    4. .open = ashmem_open,
    5. .release = ashmem_release,
    6. .read_iter = ashmem_read_iter,
    7. .llseek = ashmem_llseek,
    8. .mmap = ashmem_mmap,
    9. .unlocked_ioctl = ashmem_ioctl,
    10. #ifdef CONFIG_COMPAT
    11. .compat_ioctl = compat_ashmem_ioctl,
    12. #endif
    13. #ifdef CONFIG_PROC_FS
    14. .show_fdinfo = ashmem_show_fdinfo,
    15. #endif
    16. };

    open调用的就是ashmem_open

    1. static int ashmem_open(struct inode *inode, struct file *file)
    2. {
    3. struct ashmem_area *asma;
    4. int ret;
    5. ret = generic_file_open(inode, file);
    6. if (ret)
    7. return ret;
    8. asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);//分配一个ashmem_area
    9. if (!asma)
    10. return -ENOMEM;
    11. INIT_LIST_HEAD(&asma->unpinned_list);//初始化unpinned_list
    12. memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);//初始化一个名字
    13. asma->prot_mask = PROT_MASK;
    14. file->private_data = asma;
    15. return 0;
    16. }

    ioctl设置名字和长度调用的就是ashmem_ioctl

    1. static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    2. {
    3. struct ashmem_area *asma = file->private_data;
    4. long ret = -ENOTTY;
    5. switch (cmd) {
    6. case ASHMEM_SET_NAME:
    7. ret = set_name(asma, (void __user *)arg);
    8. break;
    9. case ASHMEM_SET_SIZE:
    10. ret = -EINVAL;
    11. mutex_lock(&ashmem_mutex);
    12. if (!asma->file) {
    13. ret = 0;
    14. asma->size = (size_t)arg;
    15. }
    16. mutex_unlock(&ashmem_mutex);
    17. break;
    18. }
    19. ........
    20. }

    实现也都很简单就是改变了一下asma里的值。接下来就是重点mmap了,具体是怎么分配内存的。

    1. static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
    2. {
    3. static struct file_operations vmfile_fops;
    4. struct ashmem_area *asma = file->private_data;
    5. int ret = 0;
    6. mutex_lock(&ashmem_mutex);
    7. /* user needs to SET_SIZE before mapping */
    8. if (!asma->size) {//判断设置了size
    9. ret = -EINVAL;
    10. goto out;
    11. }
    12. /* requested mapping size larger than object size */
    13. if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {//判断大小是否超过了虚拟内存
    14. ret = -EINVAL;
    15. goto out;
    16. }
    17. /* requested protection bits must match our allowed protection mask */
    18. if ((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
    19. calc_vm_prot_bits(PROT_MASK, 0)) {//权限判断
    20. ret = -EPERM;
    21. goto out;
    22. }
    23. vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
    24. if (!asma->file) {//是否创建过临时文件,没创建过进入
    25. char *name = ASHMEM_NAME_DEF;
    26. struct file *vmfile;
    27. struct inode *inode;
    28. if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '

    以上就是Android匿名内存怎么实现的详细内容,更多关于Android匿名内存怎么实现的资料请关注九品源码其它相关文章!