Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1099900
  • 博文数量: 185
  • 博客积分: 495
  • 博客等级: 下士
  • 技术积分: 1418
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-02 15:12
个人简介

治肾虚不含糖,专注内核性能优化二十年。 https://github.com/KnightKu

文章分类

全部博文(185)

文章存档

2019年(1)

2018年(12)

2017年(5)

2016年(23)

2015年(1)

2014年(22)

2013年(82)

2012年(39)

分类:

2013-01-28 14:20:34

原文地址:mmap 内核实现--do_mmap_pgoff 作者:Bean_lee

一 do_mmap_pgoff函数
1 入参检测
首先把len 按照页面大小对齐。 这体现了mmap系统调用的都是以页面为单位分配的内存映射。
如果 len = 0 或 len >TASK_SIZE 返回-ENOMEM;
TASK_SIZE  是3G,也就说将整个用户地址空间的3G都用来映射都不能满足。
    len = PAGE_ALIGN(len);
if (!len || len > TASK_SIZE)
return -ENOMEM;

这个不用说了,需占用的地址空间太多了。
if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)
               return -EOVERFLOW;

每个进程有最多可以用来映射的内存个数 sysctl_max_map_count。如果已经存在了足够多的内存映射,
则返回失败。
/* Too many mappings? */
if (mm->map_count > sysctl_max_map_count)
return -ENOMEM;
2 在用户地址空间中寻找个合适的地址 通过 get_unmapped_area完成。
   如果返回的地址不是按照page对齐的,可以直接返回了。
if (addr & ~PAGE_MASK)
return addr;

3  根据prot 和 flags 计算 vm_flags。
    PROT_READ   PROT_WRITE  PROT_EXEC  是prot 标志位
    VM_GROWSDOWN   VM_DENYWRITE VM_EXECUTABLE  VM_LOCKED 是flags标志位。
   
4 锁定内存相关的判断
如果flags 标志位中MAP_LOCKED 置1 ,则需要判断能否锁定内存
其次 如果允许锁定内存,查看 分配之后,是否超过了进程允许锁定的最大内存大小。
如果超过直接返回失败,错误码-EAGAIN

内存锁定的含义是,分配的内存始终位于真实内存之中,从不被置换(swap)出去。
应用层是通过调用系统调用mlock 来实现。mlock() 锁定开始于地址 addr 并延续长度
为 len 个地址范围的内存。调用成功返回后所有包含该地址范围的分页都保证在 RAM
内;这些分页保证一直在 RAM 内直到后来被解锁。
int mlock(const void *addr, size_t len);
if (flags & MAP_LOCKED) {
if (!can_do_mlock())
return -EPERM;
vm_flags |= VM_LOCKED;
}

if (vm_flags & VM_LOCKED) { unsigned long locked, lock_limit; locked = len >> PAGE_SHIFT; locked += mm->locked_vm; lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur;//资源限制 lock_limit >>= PAGE_SHIFT; if (locked > lock_limit && !capable(CAP_IPC_LOCK)) return -EAGAIN; }
    
5 根据是从具体文件还是匿名映射来判断相关条件
 如果是从真正的文件mmap,inode 是文件对应的d_inode,否则为空
inode = file ? file->f_path.dentry->d_inode : NULL;
1) 是从真实文件 向内存区域映射
如果 mmap系统调用的flags MAP_SHARED置位,表示内存中的任何改动,必须同步到磁盘上的文件中去。
如果(prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE) 表示内存区域允许WRITE,但是 磁盘文件不允许改,这就冲突了,必须返回错误。

后面的没看懂。
if (file) {
switch (flags & MAP_TYPE) {
case MAP_SHARED:
if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))
return -EACCES;

/*
* Make sure we don't allow writing to an append-only
* file..
*/
if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE))
return -EACCES;

/*
* Make sure there are no mandatory locks on the file.
*/
if (locks_verify_locked(inode))
return -EAGAIN;

vm_flags |= VM_SHARED | VM_MAYSHARE;
if (!(file->f_mode & FMODE_WRITE))
vm_flags &= ~(VM_MAYWRITE | VM_SHARED);

/* fall through */
case MAP_PRIVATE:
if (!(file->f_mode & FMODE_READ))
return -EACCES;
if (file->f_path.mnt->mnt_flags & MNT_NOEXEC) {
if (vm_flags & VM_EXEC)
return -EPERM;
vm_flags &= ~VM_MAYEXEC;
}
if (is_file_hugepages(file))
accountable = 0;

if (!file->f_op || !file->f_op->mmap)
return -ENODEV;
break;

default:
return -EINVAL;
}
}

2) 匿名映射的相关判断

else {
switch (flags & MAP_TYPE) {
case MAP_SHARED:
vm_flags |= VM_SHARED | VM_MAYSHARE;
break;
case MAP_PRIVATE:
/*
* Set pgoff according to addr for anon_vma.
*/
pgoff = addr >> PAGE_SHIFT;
break;
default:
return -EINVAL;
}
}

6 剩下的工作交给mmap_region 去做。
------------ ---------------------------------------------------------------------
二 mmap_region 函数
1 find_vma_prepare 来确定新线性区的位置 这个函数和find_vma的功能是一样的。即查找结束地址位于addr之后的第一个区域vma。find_vma_prepare 还带回来一个副产品,即前一个内存区域的prev。

  vma->vm_start < addr + len,表示产生了交集,这种情况需要调用do_munmap先将vma  解映射,然后重新查找。
munmap_back:
vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent);
if (vma && vma->vm_start < addr + len) {
if (do_munmap(mm, addr, len))
return -ENOMEM;
goto munmap_back;
}

2 判断能否增加 len >> PAGE_SHIFT个页面
  may_expand_vm函数如下
当前的页面数cur + 新分配的页面数npages 不能超过进程对应的资源限制
int may_expand_vm(struct mm_struct *mm, unsigned long npages)
{
unsigned long cur = mm->total_vm; /* pages */
unsigned long lim;

lim = current->signal->rlim[RLIMIT_AS].rlim_cur >> PAGE_SHIFT;

if (cur + npages > lim)
return 0;
return 1;
}
           if (!may_expand_vm(mm, len >> PAGE_SHIFT))
return -ENOMEM;

3 第三部分没看懂,留待以后再研究,或等高人指点
if (accountable && (!(flags & MAP_NORESERVE) ||
   sysctl_overcommit_memory == OVERCOMMIT_NEVER)) {
if (vm_flags & VM_SHARED) {
/* Check memory availability in shmem_file_setup? */
vm_flags |= VM_ACCOUNT;
} else if (vm_flags & VM_WRITE) {
/*
* Private writable mapping: check memory availability
*/
charged = len >> PAGE_SHIFT;
if (security_vm_enough_memory(charged))
return -ENOMEM;
vm_flags |= VM_ACCOUNT;
}
}

4  如果是匿名映射,并且是空间是私有的(没有VM_SHARED),则尝试将新区域 和 前一个区域 或后面一个区域合并,
   可以想见,如果可以合并就不需要新分配一个vm_area_struct 来管理新分配的区域。如果合并成功,直接
   跳转到第 步
if (!file && !(vm_flags & VM_SHARED) &&
   vma_merge(mm, prev, addr, addr + len, vm_flags,
NULL, NULL, pgoff, NULL))
goto out;

5 不能合并,没办法,新多出一个区域,必须分配 vm_area_struct 来管理这个区域。
 调用的kmem_cache_alloc.

vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
if (!vma) {
error = -ENOMEM;
goto unacct_error;
}

vma->vm_mm = mm;
vma->vm_start = addr;
vma->vm_end = addr + len;
vma->vm_flags = vm_flags;
vma->vm_page_prot = vm_get_page_prot(vm_flags);
vma->vm_pgoff = pgoff;

 6 如果匿名映射 并且私有映射 则该区域是共享匿名区,调用shmem_zero_setup 进行初始化
             else if(vm_flags & VM_SHARED) {
error = shmem_zero_setup(vma);
if (error)
goto free_vma;
}

7 增加total_vm 的大小。可以看下前面的2。
   mm->total_vm += len >> PAGE_SHIFT;
8 如果设置了 VM_LOCKED 标志,需要调用make_pages_present 分配区域的所有页,并将它们锁在 RAM中

       if (vm_flags & VM_LOCKED) {
mm->locked_vm += len >> PAGE_SHIFT;
make_pages_present(addr, addr + len);
}
9返回地址  addr。

总结:这个mmap中有很多内容我也并不了解,内部有的函数也没有深入的去看。
      第一遍看kernel 源码,很多地方不能触类旁通,左右逢源,还请高手指点。


阅读(6333) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~