Android匿名共享内存笔记
2015-03-06 14:39
190 查看
Android匿名共享内存的实现主要在ashmem驱动中,把驱动搞懂就搞懂了这个机制。
1、ashmem_open()
name表示这块共享内存的名字,这个名字会显示/proc/<pid>/maps文件中,<pid>表示打开这个共享内存文件的进程ID;域unpinned_list是一个列表头,它把这块共享内存中所有被解锁的内存块连接在一起;域file表示这个共享内存在临时文件系统tmpfs中对应的文件,在内核决定要把这块共享内存对应的物理页面回收时,就会把它的内容交换到这个临时文件中去;域size表示这块共享内存的大小;域prot_mask表示这块共享内存的访问保护位。
2、ashmem_ioctl()
上层应用主要通过ioctl()函数来与驱动交互,在驱动中对应ashmem_ioctl()函数。
3、ashmem_mmap()
借用老罗的一段分析:“这里的vma是由Linux内核的文件系统层传进来的,它的类型为struct vm_area_struct,它表示的是当前进程空间中一块连续的虚拟地址空间,它的起始地址可以由用户来指定,也可以由内核自己来分配,这里我们从JNI方法native_mmap调用的mmap的第一个参数为NULL可以看出,这块连续的虚拟地址空间的起始地址是由内核来指定的。文件内存映射操作完成后,用户访问这个范围的地址空间就相当于是访问对应的文件的内容了。从这里我们也可以看出,Android系统的匿名共享内存是在虚拟地址空间连续的,但是在物理地址空间就不一定是连续的了。”。“从mmap函数的返回值中得到了这块虚拟空间的起始地址了,这个起始地址最终返回到应用程序框架层的MemoryFile类的构造函数中,并且保存在成员变量mAddress中,后面,共享内存的读写操作就是对这个地址空间进行操作了。”。
所以mmap()的作用相当于在内核创建一块指定大小和名字的内存块。
4、ashmem_pin_unpin()
这个pin/unpin我也没看懂,不知道是什么原理。
参考文章:
老罗文章:http://blog.csdn.net/luoshengyang/article/details/6664554
更详细文章:http://blog.csdn.net/yangwen123/article/details/9318319
1、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 (unlikely(ret)) return ret; asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL); if (unlikely(!asma)) return -ENOMEM; INIT_LIST_HEAD(&asma->unpinned_list); memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN); asma->prot_mask = PROT_MASK; file->private_data = asma; return 0; }上层应用每次需创建一块匿名共享内存时会打开/dev/ashmem文件节点,此时ashmem驱动便会调用ashmem_open()来创建一个ashmem_area结构体对象保存在file->private_data中,供后续调用ashmen_ioctl()时取用。
struct ashmem_area { char name[ASHMEM_FULL_NAME_LEN]; /* optional name in /proc/pid/maps */ struct list_head unpinned_list; /* list of all ashmem areas */ struct file *file; /* the shmem-based backing file */ size_t size; /* size of the mapping, in bytes */ unsigned long prot_mask; /* allowed prot bits, as vm_flags */ }
name表示这块共享内存的名字,这个名字会显示/proc/<pid>/maps文件中,<pid>表示打开这个共享内存文件的进程ID;域unpinned_list是一个列表头,它把这块共享内存中所有被解锁的内存块连接在一起;域file表示这个共享内存在临时文件系统tmpfs中对应的文件,在内核决定要把这块共享内存对应的物理页面回收时,就会把它的内容交换到这个临时文件中去;域size表示这块共享内存的大小;域prot_mask表示这块共享内存的访问保护位。
2、ashmem_ioctl()
static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ashmem_area *asma = file->private_data; //将第1步中创建的ashmem_area取出来; long ret = -ENOTTY; switch (cmd) { case ASHMEM_SET_NAME: //设置匿名共享内存块名字,保存在ashmem_area->name中; ret = set_name(asma, (void __user *) arg); break; case ASHMEM_GET_NAME: //回去名字; ret = get_name(asma, (void __user *) arg); break; case ASHMEM_SET_SIZE: //设置内存块大小,保存在ashmem_area->size中; ret = -EINVAL; if (!asma->file) { ret = 0; asma->size = (size_t) arg; } break; case ASHMEM_GET_SIZE: ret = asma->size; break; case ASHMEM_SET_PROT_MASK: //设置ashmem_area->prot_mask; ret = set_prot_mask(asma, arg); break; case ASHMEM_GET_PROT_MASK: ret = asma->prot_mask; break; case ASHMEM_PIN: case ASHMEM_UNPIN: case ASHMEM_GET_PIN_STATUS: // ret = ashmem_pin_unpin(asma, cmd, (void __user *) arg); break; case ASHMEM_PURGE_ALL_CACHES: ret = -EPERM; if (capable(CAP_SYS_ADMIN)) { struct shrink_control sc = { .gfp_mask = GFP_KERNEL, .nr_to_scan = 0, }; ret = ashmem_shrink(&ashmem_shrinker, &sc); sc.nr_to_scan = ret; ashmem_shrink(&ashmem_shrinker, &sc); } break; } return ret; }
上层应用主要通过ioctl()函数来与驱动交互,在驱动中对应ashmem_ioctl()函数。
3、ashmem_mmap()
static int ashmem_mmap(struct file *file, struct vm_area_struct *vma) { struct ashmem_area *asma = file->private_data; int ret = 0; mutex_lock(&ashmem_mutex); /* user needs to SET_SIZE before mapping */ if (unlikely(!asma->size)) { ret = -EINVAL; goto out; } /* requested protection bits must match our allowed protection mask */ if (unlikely((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask)) & calc_vm_prot_bits(PROT_MASK))) { 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; if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0') name = asma->name; /* ... and allocate the backing shmem file */ vmfile = shmem_file_setup(name, asma->size, vma->vm_flags, 0); //最重要的就是这个调用了,shmem_file_setup()从tmpfs文件系统中创建一个文件(实际上就是一段RAM)给ashmem_area用,该文件代表着这段被共享的内存。 if (unlikely(IS_ERR(vmfile))) { ret = PTR_ERR(vmfile); goto out; } asma->file = vmfile; //shmem_file_setup创建的vmfile保存在ashmem_area->file中; } get_file(asma->file); if (vma->vm_flags & VM_SHARED) shmem_set_file(vma, asma->file); else { if (vma->vm_file) fput(vma->vm_file); vma->vm_file = asma->file; } out: mutex_unlock(&ashmem_mutex); return ret; }
借用老罗的一段分析:“这里的vma是由Linux内核的文件系统层传进来的,它的类型为struct vm_area_struct,它表示的是当前进程空间中一块连续的虚拟地址空间,它的起始地址可以由用户来指定,也可以由内核自己来分配,这里我们从JNI方法native_mmap调用的mmap的第一个参数为NULL可以看出,这块连续的虚拟地址空间的起始地址是由内核来指定的。文件内存映射操作完成后,用户访问这个范围的地址空间就相当于是访问对应的文件的内容了。从这里我们也可以看出,Android系统的匿名共享内存是在虚拟地址空间连续的,但是在物理地址空间就不一定是连续的了。”。“从mmap函数的返回值中得到了这块虚拟空间的起始地址了,这个起始地址最终返回到应用程序框架层的MemoryFile类的构造函数中,并且保存在成员变量mAddress中,后面,共享内存的读写操作就是对这个地址空间进行操作了。”。
所以mmap()的作用相当于在内核创建一块指定大小和名字的内存块。
4、ashmem_pin_unpin()
static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd, void __user *p) { struct ashmem_pin pin; size_t pgstart, pgend; int ret = -EINVAL; if (unlikely(!asma->file)) return -EINVAL; if (unlikely(copy_from_user(&pin, p, sizeof(pin)))) return -EFAULT; /* per custom, you can pass zero for len to mean "everything onward" */ if (!pin.len) pin.len = PAGE_ALIGN(asma->size) - pin.offset; //匿名共享内存块大小 - 偏移量 = 将要解锁或锁定的内存块大小 if (unlikely((pin.offset | pin.len) & ~PAGE_MASK)) return -EINVAL; if (unlikely(((__u32) -1) - pin.offset < pin.len)) return -EINVAL; if (unlikely(PAGE_ALIGN(asma->size) < pin.offset + pin.len)) return -EINVAL; pgstart = pin.offset / PAGE_SIZE; //将 解锁/锁定 的偏移量转换成页起始,上层传下来的offset一般为0,故一般解锁/锁定整个匿名内存块; pgend = pgstart + (pin.len / PAGE_SIZE) - 1; mutex_lock(&ashmem_mutex); switch (cmd) { case ASHMEM_PIN: ret = ashmem_pin(asma, pgstart, pgend); //锁定内存块; break; case ASHMEM_UNPIN: ret = ashmem_unpin(asma, pgstart, pgend); //解锁内存块; break; case ASHMEM_GET_PIN_STATUS: ret = ashmem_get_pin_status(asma, pgstart, pgend); break; } mutex_unlock(&ashmem_mutex); return ret; }
static int ashmem_pin(struct ashmem_area *asma, size_t pgstart, size_t pgend) { struct ashmem_range *range, *next; int ret = ASHMEM_NOT_PURGED; list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) { /* moved past last applicable page; we can short circuit */ if (range_before_page(range, pgstart)) break; /* * The user can ask us to pin pages that span multiple ranges, * or to pin pages that aren't even unpinned, so this is messy. * * Four cases: * 1. The requested range subsumes an existing range, so we * just remove the entire matching range. * 2. The requested range overlaps the start of an existing * range, so we just update that range. * 3. The requested range overlaps the end of an existing * range, so we just update that range. * 4. The requested range punches a hole in an existing range, * so we have to update one side of the range and then * create a new range for the other side. */ if (page_range_in_range(range, pgstart, pgend)) { ret |= range->purged; /* Case #1: Easy. Just nuke the whole thing. */ if (page_range_subsumes_range(range, pgstart, pgend)) { range_del(range); continue; } /* Case #2: We overlap from the start, so adjust it */ if (range->pgstart >= pgstart) { range_shrink(range, pgend + 1, range->pgend); continue; } /* Case #3: We overlap from the rear, so adjust it */ if (range->pgend <= pgend) { range_shrink(range, range->pgstart, pgstart-1); continue; } /* * Case #4: We eat a chunk out of the middle. A bit * more complicated, we allocate a new range for the * second half and adjust the first chunk's endpoint. */ range_alloc(asma, range, range->purged, pgend + 1, range->pgend); range_shrink(range, range->pgstart, pgstart - 1); break; } } return ret; }
static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend) { struct ashmem_range *range, *next; unsigned int purged = ASHMEM_NOT_PURGED; restart: list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) { //unpinned_list链表中保存着已解锁内存片; /* short circuit: this is our insertion point */ if (range_before_page(range, pgstart)) //循环会从地址大到小遍历,如果找到内存片在解锁起始地址之前,那么就需要重新开辟内存片了;例如已经存在内存片0~100,现在要解锁200~300的内存片,那么退出该循环,会调用range_alloc()来开辟内存片; break; /* * The user can ask us to unpin pages that are already entirely * or partially pinned. We handle those two cases here. */ if (page_range_subsumed_by_range(range, pgstart, pgend)) //如果要解锁的内存块已经被包含在一块已经解锁的内存片中,那么就无需额外操作,直接返回; return 0; if (page_range_in_range(range, pgstart, pgend)) { //如果是部分相交, pgstart = min_t(size_t, range->pgstart, pgstart), pgend = max_t(size_t, range->pgend, pgend); purged |= range->purged; range_del(range); goto restart; } } return range_alloc(asma, range, purged, pgstart, pgend); }
这个pin/unpin我也没看懂,不知道是什么原理。
参考文章:
老罗文章:http://blog.csdn.net/luoshengyang/article/details/6664554
更详细文章:http://blog.csdn.net/yangwen123/article/details/9318319
相关文章推荐
- Android系统匿名共享内存Ashmem
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析(1)
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析(6)
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析(5)
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析(下)
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析(3)
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析