mm/mmap.c

(I) 接口介绍
  虽然本模块提供了其他接口,但是重点还是do_mmap_pgoff,sys_brk.
思路还是先看看man:

 1)mmap
/* arch/i386/kernel/sys_i386.c */
asmlinkage long sys_mmap2(unsigned long addr, unsigned long len,
	unsigned long prot, unsigned long flags,
	unsigned long fd, unsigned long pgoff)
{
	return do_mmap2(addr, len, prot, flags, fd, pgoff);
}

  mmap将fd(file or other)偏移offset的长度为len的一段,映射到内存。addr
是用户向系统建议的起始地址. 真正的映射地址由系统调用返回,never 0.
  参数prot给出映射内的内存段的性质:
     PROT_EXEC, PROT_READ,PROT_WRITE,PROT_NONE(pages not be accessed).
  flags给出映射属性:
     MAP_FIXED:必须映射到用户指定的起始内存地址addr.(不鼓励使用)
     MAP_SHARED:这个映射和其他映射了相同fd的进程共享.写内存等于写文件,但
是在调用msync或者munmap之前文件不会被更新.
     MAP_PRIVATE:创建私有的COW映射,存储内存对原文件无影响.此选项没有定义
在映射后如果文件内容发生变化将会如何.
     MAP_EXECUTABLE,MAP_DENYWRITE: 不再使用了.ignore.
     MAP_NORESERVE:和MAP_PRIVATE共同使用.不为此映射保留swap紧急页,会导致
在内存缺乏的时候写内存操作引起SIGSEGV.
     MAP_LOCKED: ver〉2.5.37,同时进行mlock。
     MAP_GROWSDOWN:用于stack。
     MAP_ANONYMOUS:(same as MAP_ANON)无文件作为后备缓存。从linux2.4开始
实现了此标志和MAP_SHARED的联合使用。
     MAP_FILE: 兼容目的的flag。ignore。
     MAP_32BIT: X86-64,映射入处理器地址的头2G。
     
     如果映射出的内存页面有不足一页的情况,剩余的页面部分被清0,但是写相关
内存不会被写出到文件.如果已经影射的文件大小被改变了,效果未定.
     
  
   2)brk
  asmlinkage unsigned long sys_brk(unsigned long brk)
    设置进程的数据段的结束地址(高端)为指定值.
    
II) do_mmap_pgoff

   思路简析: 就是建立起一个vma,设置vma->vm_ops, 使得缺页中断能够通过
vma->vm_ops->readpage从指定文件读取相关内容。
   由于有内存分配操作,需要do_munmap 先!
   顺便处理一下mlock,还做了大量的安检。

unsigned long do_mmap_pgoff(struct file * file, unsigned long addr, unsigned long len,
	unsigned long prot, unsigned long flags, unsigned long pgoff)
{
	struct mm_struct * mm = current->mm;
	struct vm_area_struct * vma;
	int correct_wcount = 0;
	int error;

  //例行的参数,限制量检查
      .....//代码省略
	/*
	 * 一些关于文件操作权限的检查
	 */
	if (file != NULL) {
		switch (flags & MAP_TYPE) {
		case MAP_SHARED:
			.......//省略若干
			/* make sure there are no mandatory locks on the file. */
			/*mandatory lock 就是内核实现的文件互斥锁*/
			if (locks_verify_locked(file->f_dentry->d_inode))
				return -EAGAIN;
    case ....://省略若干
		default:
			return -EINVAL;
		}
	}

	/* Obtain the address to map to. we verify (or select) it and ensure
	 * that it represents a valid section of the address space.
	 */
	if (flags & MAP_FIXED) {
		if (addr & ~PAGE_MASK)
			return -EINVAL;
	} else {
		addr = get_unmapped_area(addr, len); /*先圈地,寻找未映射区域*/
		if (!addr)
			return -ENOMEM;
	}

	/* 分配vma并简单设置,由于内存分配可能睡眠,需要unmap先.
	 */
	vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
  ....
  //根据file的情况和系统调用的参数设置vma的属性
  //注意 VM_SHARED的设置依据
	if (file) {
		......
		if (flags & MAP_SHARED) {
			vma->vm_flags |= VM_SHARED | VM_MAYSHARE;

			....
		}
	} else {
		vma->vm_flags |= VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
		if (flags & MAP_SHARED)
			vma->vm_flags |= VM_SHARED | VM_MAYSHARE;
	}
	//vma配置
	....
	
	/*清除已有映射,我们可能被suspend过*/
	error = -ENOMEM;
	if (do_munmap(mm, addr, len))
		goto free_vma;
 
	//.... 省略若干代码

  //mmap能够工作的关键代码段.
	if (file) {
		if (vma->vm_flags & VM_DENYWRITE) {
			error = deny_write_access(file);/*禁止作为普通文件的写操作*/
			if (error)
				goto free_vma;
			correct_wcount = 1;
		}
		vma->vm_file = file;
		get_file(file);
		error = file->f_op->mmap(file, vma);/*ext2就是generic_file_mmap
		                                     *就是设置vma->vm_ops从而使
		                                     *vma->vm_ops->readpage就是
		                                     *filemap_nopage
		                                     */
	

  .....

  //新的vma生效
	insert_vm_struct(mm, vma); 
	
	...
}

  做完这些操作,mmap剩下的工作就是缺页中断的任务了.见相关文件的分析.
  
  
III)asmlinkage long sys_munmap(unsigned long addr, size_t len)
  
   核心函数是do_munmap,注释简单说了一下思路,先把受影响的vma找出来,然
后挨个处理。
int do_munmap(struct mm_struct *mm, unsigned long addr, size_t len)
{
	struct vm_area_struct *mpnt, *prev, **npp, *free, *extra;
  ......
	//参数检查
	
	//寻找vma->end >大于addr的vma
	mpnt = find_vma_prev(mm, addr, &prev);
	if (!mpnt) //addr超出所有vma的范围,没有映射存在,直接返回
		return 0;
	/* we have  addr < mpnt->vm_end  */

	if (mpnt->vm_start >= addr+len)//落入空洞,直接返回
		return 0;

  .....

	/*
	 * We may need one additional vma to fix up the mappings ... 
	 * and this is the last chance for an easy error exit.
	 */
	extra = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
	if (!extra)
		return -ENOMEM;

 /* 
  * 这部分找到需要处理的mpnt, 挂入free 链表
  */
	npp = (prev ? &prev->vm_next : &mm->mmap);
	free = NULL;
	spin_lock(&mm->page_table_lock);
	//只要end>vm_start,vma就有肯定(addr处已经于一个vma中了)受影响
	for ( ; mpnt && mpnt->vm_start < addr+len; mpnt = *npp) {
		*npp = mpnt->vm_next; //从链表摘除
		mpnt->vm_next = free; //挂入等待处理的vma表
		free = mpnt;
		if (mm->mmap_avl) //从vma avl查找表摘除
			avl_remove(mpnt, &mm->mmap_avl);
	}
	mm->mmap_cache = NULL;	/* Kill the cache. */
	spin_unlock(&mm->page_table_lock);

  /*逐个处理受影响的vma*/
	while ((mpnt = free) != NULL) {
		unsigned long st, end, size;
		struct file *file = NULL;

		free = free->vm_next;

		st = addr < mpnt->vm_start ? mpnt->vm_start : addr;
		end = addr+len;
		end = end > mpnt->vm_end ? mpnt->vm_end : end;
		size = end - st;
    /*如果将一个vma分成两部分,而又是VM_DENYWRITE的,
      要额外对d_inode->i_writecount加一次锁,因为我们保留了一个
      VM_DENYWRITE属性的vma,注意file仅在VM_DENYWRITE的情况下
      非空*/
		if (mpnt->vm_flags & VM_DENYWRITE &&
		    (st != mpnt->vm_start || end != mpnt->vm_end) &&
		    (file = mpnt->vm_file) != NULL) {
			atomic_dec(&file->f_dentry->d_inode->i_writecount);
		}
		remove_shared_vm_struct(mpnt);//从inode 的i_mapping->i_mmap_shared链表摘除
		mm->map_count--;

		flush_cache_range(mm, st, end); //i386 为空
		zap_page_range(mm, st, size); //释放user used data page,参考memory.c的分析
		flush_tlb_range(mm, st, end); //i386为空

		/*
		 * Fix the mapping, and free the old area if it wasn't reused.
		 */
		extra = unmap_fixup(mm, mpnt, st, size, extra);
		if (file)//注意上面的条件if (mpnt->vm_flags & VM_DENYWRITE &&...
			atomic_inc(&file->f_dentry->d_inode->i_writecount);
	}

	/* Release the extra vma struct if it wasn't used */
	if (extra)
		kmem_cache_free(vm_area_cachep, extra);
  /*在给定的区间[start,end],寻找一个没有映射的子集
     释放这个子集范围内的page table,pmd
  */
	free_pgtables(mm, prev, addr, addr+len);

	return 0;
}

  注意上面的这一行:
  ...
  remove_shared_vm_struct(mpnt);//从inode 的i_mapping->i_mmap_shared链表摘除
  ...
  
  迄今为止,只找到mapping的一个来源,inode->i_mapping,而此指针在inode初始化的
时候指向同一inode内的inode->idata.见fs/inode.c 函数clean_inode。
  do_munmap应用很广,是此模块的一个重要的对外接口。
  
IV)sys_brk(unsigned long brk)

   支持brk扩展/收缩。
/*
 *  sys_brk() for the most part doesn't need the global kernel
 *  lock, except when an application is doing something nasty
 *  like trying to un-brk an area that has already been mapped
 *  to a regular file.  in this case, the unmapping will need
 *  to invoke file system routines that need the global lock.
 */
asmlinkage unsigned long sys_brk(unsigned long brk)
{
	unsigned long rlim, retval;
	unsigned long newbrk, oldbrk;
	struct mm_struct *mm = current->mm;

	down(&mm->mmap_sem);

	if (brk < mm->end_code)
		goto out;
	newbrk = PAGE_ALIGN(brk);
	oldbrk = PAGE_ALIGN(mm->brk);
	if (oldbrk == newbrk)
		goto set_brk;

	/* Always allow shrinking brk. */
	if (brk <= mm->brk) {
		if (!do_munmap(mm, newbrk, oldbrk-newbrk)) /*brk收缩就是一个对
				                 匿名map的 munmap操作*/
			goto set_brk;
		goto out;
	}

	...//limit 检查

	/* Check against existing mmap mappings. */
	/* brk部分必须位于一个vma内*/
	if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))
		goto out;

	...//空闲内存检测
	
	/* Ok, looks good - let it rip. */
	if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)
		goto out;
set_brk:
	mm->brk = brk;
out:
	retval = mm->brk;
	up(&mm->mmap_sem);
	return retval;
}
  核心函数do_brk也是一个本模块主要的对外接口。实际上brk是mmap的一个特例。
就是一个匿名的mmap操作,所以和do_mmap_pgoff逻辑相同。不再罗列。

V)其他重要接口函数
  lock_vma_mappings/unlock_vma_mapping: 对vma设计到的spin inode->i_mapping->
i_shared_lock 加解锁,应用很多。
   
  find_vma/find_vma_prev/find_extend_vma: 在进程的vma链表或者avl树中查找
vma。注意查找到的vma的特性,并不保证所查询的地址在此vma中。

  __insert_vm_struct/insert_vm_struct:建立vma的两个关联,a)放入进程的vma
列表或者avl查找树。 b)如果关联一个file,链入inode->i_mapping->i_mmap(_shared).