Android匿名共享内存笔记

Android匿名共享内存的实现主要在ashmem驱动中,把驱动搞懂就搞懂了这个机制。

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我也没看懂,不知道是什么原理。

参考文章:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值