您的位置:首页 > 运维架构 > Linux

linux内核之情景分析mmap操作

2016-10-27 10:53 113 查看
进程可以通过mmap把一个已打开文件映射到用户空间.
mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset)

[/code]start表示用户空间映射的起始地址,offset文件的起始length长度.

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);

}

[/code]其主体是do_mmap2,注意其标志MAP_ANONYMOUS表示匿名映射

/* common code for old and new mmaps */

static inline long do_mmap2(

unsigned long addr, unsigned long len,

unsigned long prot, unsigned long flags,

unsigned long fd, unsigned long pgoff)

{

int error = -EBADF;

struct file * file = NULL;


flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);

if (!(flags & MAP_ANONYMOUS)) {//map_anonymous表示没有文件,只是在指定位置分配内存

file = fget(fd);//上一条表示,没有文件,就跳过if以下,有文件则打开文件

if (!file)//如果文件不存在,直接返回错误

goto out;

}


down(¤t->mm->mmap_sem);//信号量down操作

error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff);//mmap主体操作还是这个

up(¤t->mm->mmap_sem);//信号量up操作


if (file)

fput(file);

out:

return error;

}

[/code]其主体为do_mmap_pgoff
do_mmap_pgoff(file, addr, len, prot, flags, pgoff);

[/code]第一个参数为打开文件,第二个地址,第三长度,第四个参数为访问权限,第五个参数为其他控制目的,第6个为偏移量

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;

//file非0表示是文件,其对应一定有相关操作函数.

if (file && (!file->f_op || !file->f_op->mmap))

return -ENODEV;

//长度对齐,如果为0,直接返回

if ((len = PAGE_ALIGN(len)) == 0)

return addr;

//长度大于3g或者addr+len映射区域超过用户空间,返回错误

if (len > TASK_SIZE || addr > TASK_SIZE-len)

return -EINVAL;

// 偏移量是否超过了长度

/* offset overflow? */

if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)

return -EINVAL;

//映射次数是否超过了限定

/* Too many mappings? */

if (mm->map_count > MAX_MAP_COUNT)

return -ENOMEM;

//是否加锁?这里不知道了

/* mlock MCL_FUTURE? */

if (mm->def_flags & VM_LOCKED) {

unsigned long locked = mm->locked_vm << PAGE_SHIFT;

locked += len;

if (locked > current->rlim[RLIMIT_MEMLOCK].rlim_cur)

return -EAGAIN;

}


/* Do simple checking here so the lower-level routines won't have

* to. we assume access permissions have been handled by the open

* of the memory object, so we don't do any here.

*/

if (file != NULL) { //如果文件存在

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(file->f_dentry->d_inode) && (file->f_mode & FMODE_WRITE))

return -EACCES;

//确保我们的文件没有锁

/* make sure there are no mandatory locks on the file. */

if (locks_verify_locked(file->f_dentry->d_inode))

return -EAGAIN;


/* fall through */

case MAP_PRIVATE://私有映射

if (!(file->f_mode & FMODE_READ))

return -EACCES;

break;


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) {//如果参数flag的标志位map_fixed为0表示,指定映射位置只是一个参考值

if (addr & ~PAGE_MASK)

return -EINVAL;

} else {//不满足由内核从空洞执行分配一个区域

addr = get_unmapped_area(addr, len);

if (!addr)

return -ENOMEM;

}


/* Determine the object being mapped and call the appropriate

* specific mapper. the address has already been validated, but

* not unmapped, but the maps are removed from the list.

*/

vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//从slab获取一个vma结构

if (!vma)

return -ENOMEM;


vma->vm_mm = mm;//指向内存描述符

vma->vm_start = addr;//vma的起始地址指向映射的起始地址

vma->vm_end = addr + len;//同上

vma->vm_flags = vm_flags(prot,flags) | mm->def_flags;//设置vma属性


if (file) {//如果file为0,表示匿名映射,仅仅是为了创建虚拟区间,或者仅在于建立从物理空间到虚存空间映射,而非文件映射

VM_ClearReadHint(vma);//以下代码设置一堆属性

vma->vm_raend = 0;


if (file->f_mode & FMODE_READ)

vma->vm_flags |= VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;

if (flags & MAP_SHARED) {

vma->vm_flags |= VM_SHARED | VM_MAYSHARE;


/* This looks strange, but when we don't have the file open

* for writing, we can demote the shared mapping to a simpler

* private mapping. That also takes care of a security hole

* with ptrace() writing to a shared mapping without write

* permissions.

*

* We leave the VM_MAYSHARE bit on, just to get correct output

* from /proc/xxx/maps..

*/

if (!(file->f_mode & FMODE_WRITE))

vma->vm_flags &= ~(VM_MAYWRITE | VM_SHARED);

}

} else {

vma->vm_flags |= VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;

if (flags & MAP_SHARED)

vma->vm_flags |= VM_SHARED | VM_MAYSHARE;

}

vma->vm_page_prot = protection_map[vma->vm_flags & 0x0f];

vma->vm_ops = NULL;

vma->vm_pgoff = pgoff;//表示所映射内容在文件的起点,此值用于发生缺页异常根据虚存地址计算出相应页面的文件位置

vma->vm_file = NULL;

vma->vm_private_data = NULL;


/* Clear old maps */

error = -ENOMEM;

if (do_munmap(mm, addr, len))//检查目的地址的vma空间是否已经使用(如果map_fixed设置为1的话)

goto free_vma;//已经使用则释放free_vma

//检查是否超过了限制

/* Check against address space limit. */

if ((mm->total_vm << PAGE_SHIFT) + len

> current->rlim[RLIMIT_AS].rlim_cur)

goto free_vma;

//检查当前进程专用的可写区间而物理页面不足的情况

/* Private writable mapping? Check memory availability.. */

if ((vma->vm_flags & (VM_SHARED | VM_WRITE)) == VM_WRITE &&

!(flags & MAP_NORESERVE) &&

!vm_enough_memory(len >> PAGE_SHIFT))

goto free_vma;


if (file) {//vm_deanwrite职位表示从文件到vma映射,表示不允许同过常规方式读写文件

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);//递增file结构的共享计数

error = file->f_op->mmap(file, vma);//一个文件操作必须存在mmap,否则释放vma

if (error)

goto unmap_and_free_vma;

} else if (flags & MAP_SHARED) {//共享映射

error = shmem_zero_setup(vma);

if (error)

goto free_vma;

}


/* Can addr have changed??

*为了防止flags与addr有变化,再重新设置一遍,

* Answer: Yes, several device drivers can do it in their

*         f_op->mmap method. -DaveM

*/

flags = vma->vm_flags;

addr = vma->vm_start;


insert_vm_struct(mm, vma);//插入当前进程的内存描述符

if (correct_wcount)

atomic_inc(&file->f_dentry->d_inode->i_writecount);


mm->total_vm += len >> PAGE_SHIFT;//映射区域+len>>page_shit

if (flags & VM_LOCKED) {//需要加锁

mm->locked_vm += len >> PAGE_SHIFT;

make_pages_present(addr, addr + len);//建立初始映射

}

return addr;


unmap_and_free_vma:

if (correct_wcount)

atomic_inc(&file->f_dentry->d_inode->i_writecount);

vma->vm_file = NULL;

fput(file);

/* Undo any partial mapping done by a device driver. */

flush_cache_range(mm, vma->vm_start, vma->vm_end);

zap_page_range(mm, vma->vm_start, vma->vm_end - vma->vm_start);

flush_tlb_range(mm, vma->vm_start, vma->vm_end);

free_vma:

kmem_cache_free(vm_area_cachep, vma);

return error;

}

[/code]以上是文件与虚拟区间之间建立的映射,但具体的映射(从虚拟地址映射到物理地址)还没开始,而是把具体页面的映射推迟到真正需要的时候才进行,具体映射的简历,物理页面的换入和换出分别准备了一些函数,filemap_nopage(),ext2_readpage(),ext2_writepage()什么时候调用呢(1)该区间中的一个页面首次收到访问时,会由于页面没映射发生缺页异常,相应的异常处理程序do_no_page(),对于ext2系统,do_no_page()会通过ext2_readpage()分配一个空闲内存页面并从文件读入相应页面,并建立映射.(2)建立映射后,往页面写使得页面变脏,但页面的内容并不会立即写回文件.而是由内核线程bdflush()周期性的运行时通过page_launder()间接调用ext2_writepage(),将页面的内容写入文件.如果页面很长时间没有收到访问,那就会被try_to_swap_out()解除映射而转入不活跃状态,如果页面是脏的那就也调用ext2_writepage()写入然后再解除映射(3)解除了映射的页面再次收到访问时又会发生缺页异常,因为页面无映射进入do_no_page()mmap映射,如果文件映射的一个页面长期得不到访问,将直接把页表项设置为0,如果访问到将重新alloc_page分配一个新页面,然后把文件读取到新页面,再建立映射,对于普通的换入/换出则是发生缺页异常从swap分区查找到换出的页面,然后建立映射

来自为知笔记(Wiz)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: