mm/mmap.c
(I) 接口介绍
虽然本模块提供了其他接口,但是重点还是do_mmap_pgoff,sys_brk.
思路还是先看看man:
1)mmap
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:
.......
if (locks_verify_locked(file->f_dentry->d_inode))
return -EAGAIN;
case ....: default:
return -EINVAL;
}
}
if (flags & MAP_FIXED) {
if (addr & ~PAGE_MASK)
return -EINVAL;
} else {
addr = get_unmapped_area(addr, len);
if (!addr)
return -ENOMEM;
}
vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
.... 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;
} ....
error = -ENOMEM;
if (do_munmap(mm, addr, len))
goto free_vma; 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);
..... 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;
...... mpnt = find_vma_prev(mm, addr, &prev);
if (!mpnt) return 0;
if (mpnt->vm_start >= addr+len) return 0;
.....
extra = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
if (!extra)
return -ENOMEM;
npp = (prev ? &prev->vm_next : &mm->mmap);
free = NULL;
spin_lock(&mm->page_table_lock); for ( ; mpnt && mpnt->vm_start < addr+len; mpnt = *npp) {
*npp = mpnt->vm_next; mpnt->vm_next = free; free = mpnt;
if (mm->mmap_avl) avl_remove(mpnt, &mm->mmap_avl);
}
mm->mmap_cache = NULL;
spin_unlock(&mm->page_table_lock);
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;
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); mm->map_count--;
flush_cache_range(mm, st, end); zap_page_range(mm, st, size); flush_tlb_range(mm, st, end);
extra = unmap_fixup(mm, mpnt, st, size, extra);
if (file) atomic_inc(&file->f_dentry->d_inode->i_writecount);
}
if (extra)
kmem_cache_free(vm_area_cachep, extra);
free_pgtables(mm, prev, addr, addr+len);
return 0;
}
注意上面的这一行:
...
remove_shared_vm_struct(mpnt); ...
迄今为止,只找到mapping的一个来源,inode->i_mapping,而此指针在inode初始化的
时候指向同一inode内的inode->idata.见fs/inode.c 函数clean_inode。
do_munmap应用很广,是此模块的一个重要的对外接口。
IV)sys_brk(unsigned long brk)
支持brk扩展/收缩。
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;
if (brk <= mm->brk) {
if (!do_munmap(mm, newbrk, oldbrk-newbrk))
goto set_brk;
goto out;
}
...
if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))
goto out;
...
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).