mmap 内核实现--do_mmap_pgoff
2011-06-10 23:36:30
分类:
一 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完成。
具体参见前面博文
进程虚拟地址空间之arch_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 源码,很多地方不能触类旁通,左右逢源,还请高手指点。