mm/mremap.c

  本模块提供系统调用,mremap。请先参考对模块mm/mmap.c的分析。先看系统调
用的功能。
  sys_mremap(unsigned long addr,unsigned long old_len, unsigned long new_len,
	unsigned long flags, unsigned long new_addr)
	
	mremap扩展或者收缩现存的memory mapping。可能会将映射在虚拟地址内进行移动。
  addr是要进行remap的现存映射首地址。old_size是现存映射的大小.new_len是
这次remap的请求长度。
  利用mremap可以实现高效的无拷贝realloc。
  flags:
  MREMAP_MAYMOVE:容许配给一个新的虚拟地址.
  MREMAP_FIXED: 配合new_addr,仅当此标记置位,new_addr才有意义.
  
  对这系统调用的分析,我们采用代码的强注释来完成:
  
/* 分为三种情况阅读代码比较容易或得整体印象:
 *  start --end(start+len) 位于vma的起始,结束,末尾
 *  加上每种又分为扩展和收缩两种情况。
 */
/*注意如果是fixmap,新旧地址就不能相等,并且
 *新旧地址交叠也不容许。(除非新旧长度都是0)
 */
unsigned long do_mremap(unsigned long addr,unsigned long old_len, 
unsigned long new_len,unsigned long flags, unsigned long new_addr)
{
	struct vm_area_struct *vma;
	unsigned long ret = -EINVAL;

	if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE))//仅支持这两个标记
		goto out;

	if (addr & ~PAGE_MASK)//首地址需要page align
		goto out;

	old_len = PAGE_ALIGN(old_len);
	new_len = PAGE_ALIGN(new_len);

	/* new_addr is only valid if MREMAP_FIXED is specified */
	//如果要求fix remap,首先清除指定(新)地址上的映射
	if (flags & MREMAP_FIXED) {//要求映射到指定虚拟地址
		if (new_addr & ~PAGE_MASK)
			goto out;
		if (!(flags & MREMAP_MAYMOVE))//fixmap要求和move配合使用
			goto out;

		if (new_len > TASK_SIZE || new_addr > TASK_SIZE - new_len)
			goto out;

		/* Check if the location we're moving into overlaps the
		 * old location at all, and fail if it does.
		 */
		 //fix map要求新旧地址空间不能有任何重叠
		if ((new_addr <= addr) && (new_addr+new_len) > addr)
			goto out;

		if ((addr <= new_addr) && (addr+old_len) > new_addr)
			goto out;

		do_munmap(current->mm, new_addr, new_len);/*参考对mmap.c的分析*/
	}

	/*
	 * Always allow a shrinking remap: that just unmaps
	 * the unnecessary pages..
	 */
	//如果remap后映射缩减,需要unmap缩减部分
	ret = addr;
	if (old_len >= new_len) {
		do_munmap(current->mm, addr+new_len, old_len - new_len);
		if (!(flags & MREMAP_FIXED) || (new_addr == addr))
			goto out;
	}

	/*
	 * Ok, we need to grow..  or relocate.
	 */
	//寻找涉案vma
	ret = -EFAULT;
	vma = find_vma(current->mm, addr); //addr<vma_end
	if (!vma || vma->vm_start > addr)
		goto out;//no such vma contain "addr"

	//检查指定vma 是否容许此次remap
	/* We can't remap across vm area boundaries */
	if (old_len > vma->vm_end - addr)//注意:remap 不能跨越vma
		goto out; //注意用户指定的addr 可以在一个vma内,而不是vma_stat
	if (vma->vm_flags & VM_DONTEXPAND) {//不能扩展的vma
		if (new_len > old_len)
			goto out;
	}
	if (vma->vm_flags & VM_LOCKED) {//内存锁定 总量不能超越限制
		unsigned long locked = current->mm->locked_vm << PAGE_SHIFT;
		locked += new_len - old_len;
		ret = -EAGAIN;
		if (locked > current->rlim[RLIMIT_MEMLOCK].rlim_cur)
			goto out;
	}
	ret = -ENOMEM;
	if ((current->mm->total_vm << PAGE_SHIFT) + (new_len - old_len)
	    > current->rlim[RLIMIT_AS].rlim_cur)//总vma不能超限
		goto out;
	/* Private writable mapping? Check memory availability.. */
	            /*内核为此种映射保留一定的内存,除非用户无需此种机制*/
	if ((vma->vm_flags & (VM_SHARED | VM_WRITE)) == VM_WRITE &&
	    !(flags & MAP_NORESERVE)				 &&
	    !vm_enough_memory((new_len - old_len) >> PAGE_SHIFT))
		goto out;



	/* old_len exactly to the end of the area..
	 * And we're not relocating the area.
	 */
	if (old_len == vma->vm_end - addr &&
	    !((flags & MREMAP_FIXED) && (addr != new_addr)) &&
	    (old_len != new_len || !(flags & MREMAP_MAYMOVE))) {
	    //位于末尾,长度不等或者不容许移动
	    //并且不是非等首地址的fixmap
	  unsigned long max_addr = TASK_SIZE;
		if (vma->vm_next)
			max_addr = vma->vm_next->vm_start;
		/* can we just expand the current mapping? */
		if (max_addr - addr >= new_len) {
			int pages = (new_len - old_len) >> PAGE_SHIFT;
			spin_lock(&vma->vm_mm->page_table_lock);
			vma->vm_end = addr + new_len;
			spin_unlock(&vma->vm_mm->page_table_lock);
			current->mm->total_vm += pages;
			if (vma->vm_flags & VM_LOCKED) {
				current->mm->locked_vm += pages;
				make_pages_present(addr + old_len,
						   addr + new_len);
			}
			ret = addr;
			goto out;
		}
	}

	/*
	 * We weren't able to just expand or shrink the area,
	 * we need to create a new one and move it..
	 */
	 /*必须移动映射了*/
	ret = -ENOMEM;
	if (flags & MREMAP_MAYMOVE) {//必须容许移动
		if (!(flags & MREMAP_FIXED)) {/*非fixmap时可以寻找新的虚拟空间*/
                                        
			new_addr = get_unmapped_area(0, new_len);
			if (!new_addr)
				goto out;
		}
		/*fixmap时new_addr指定的一段空间不会和old space交叠在一起所以move 
	   vma没有问题*/
		ret = move_vma(vma, addr, old_len, new_len, new_addr);
	}
out:
	return ret;
}  

剩下的需要分析的函数就是move_vma,其他函数不再分析:  
static inline unsigned long move_vma(struct vm_area_struct * vma,
	unsigned long addr, unsigned long old_len, unsigned long new_len,
	unsigned long new_addr)
{
	struct vm_area_struct * new_vma;

	new_vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
	if (new_vma) {
		//移动页表包括分配新的页表,页目录,拷贝pte
		if (!move_page_tables(current->mm, new_addr, addr, old_len)) {
			//页表操作完成,建立新的vma
		 	*new_vma = *vma;
			new_vma->vm_start = new_addr;
			new_vma->vm_end = new_addr+new_len;
			new_vma->vm_pgoff += (addr - vma->vm_start) >> PAGE_SHIFT;
			new_vma->vm_raend = 0;
			if (new_vma->vm_file)
				get_file(new_vma->vm_file);
			if (new_vma->vm_ops && new_vma->vm_ops->open)
				new_vma->vm_ops->open(new_vma);
			insert_vm_struct(current->mm, new_vma);
			do_munmap(current->mm, addr, old_len);   //unmap老的地址空间
			current->mm->total_vm += new_len >> PAGE_SHIFT;
			if (new_vma->vm_flags & VM_LOCKED) {//lock就立即分配内存
				current->mm->locked_vm += new_len >> PAGE_SHIFT;
				make_pages_present(new_vma->vm_start,
						   new_vma->vm_end);
			}
			return new_addr;
		}
		kmem_cache_free(vm_area_cachep, new_vma);
	}
	return -ENOMEM;
}