mmap_region是mmap实现的另一个关键点,它实现
对设备文件或普通文件的file->f_op->mmap(file, vma)函数的调用
unsigned long mmap_region(struct file *file, unsigned long addr,
unsigned long len, unsigned long flags,
vm_flags_t vm_flags, unsigned long pgoff)
{
/* 分配一个struct vm_area_struct结构 */
vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
...
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;
INIT_LIST_HEAD(&vma->anon_vma_chain);
error = -EINVAL; /* when rejecting VM_GROWSDOWN|VM_GROWSUP */
if (file) {
if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
goto free_vma;
if (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 !=NULL 的情况,这里会调用到file的file_operations结构里面的mmap 方法
因而对于一个设备,这里就会调用到设备驱动程序里面的mmap方法
对于一个普通文件,调用对应的文件系统里面注册的file_operations结构里面的mmap
1. 比如ext4fs, fs/ext4/file.c里面,
const struct file_operations ext4_file_operations = {
...
.mmap = ext4_file_mmap,
...
};
ext4_file_mmap也只是为vma->vm_ops 赋值 &ext4_file_vm_ops;并没有执行真正的map动作。
static int ext4_file_mmap(struct file *file, struct vm_area_struct *vma)
{
struct address_space *mapping = file->f_mapping;
if (!mapping->a_ops->readpage)
return -ENOEXEC;
file_accessed(file);
vma->vm_ops = &ext4_file_vm_ops;
vma->vm_flags |= VM_CAN_NONLINEAR;
return 0;
}
2. 再比如ext3fs, fs/ext3/file.c里面
const struct file_operations ext3_file_operations = {
...
.mmap = generic_file_mmap,
...
};
generic_file_mmap也只是为vma->vm_ops 赋值 &generic_file_vm_ops;并没有执行真正的map动作。
int generic_file_mmap(struct file * file, struct vm_area_struct * vma)
{
struct address_space *mapping = file->f_mapping;
if (!mapping->a_ops->readpage)
return -ENODEV;
file_accessed(file);
vma->vm_ops = &generic_file_vm_ops;
vma->vm_flags |= VM_CAN_NONLINEAR;
return 0;
}
当对应的虚拟内存被访问时,将触发访存异常。内核捕捉到异常,再完成内存分配和读文件的事情
do_page_fault就是内核用于捕捉访存异常的函数。
其中内核会先确认引起异常的内存地址是合法的,并且找出它所对应的vma(如果找不到就是不合法)。
然后分配内存、建立页表。
对于本文中描述的mmap映射了某个文件的这种情况,内核还需要把文件对应位置上的数据读到新分配的内存上,
这个工作主要是由vma->vm_ops->fault来完成的。前面我们看到vma->vm_ops是如何被赋值的了,而且这个vma->vm_ops->fault就等于filemap_fault。
*/
error = file->f_op->mmap(file, vma);
if (error)
goto unmap_and_free_vma;
#ifdef CONFIG_PAX_SEGMEXEC
if (vma_m && (vm_flags & VM_EXECUTABLE))
added_exe_file_vma(mm);
#endif
#if defined(CONFIG_PAX_PAGEEXEC) && defined(CONFIG_X86_32)
if ((mm->pax_flags & MF_PAX_PAGEEXEC) && !(vma->vm_flags & VM_SPECIAL)) {
vma->vm_flags |= VM_PAGEEXEC;
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
}
#endif
if (vm_flags & VM_EXECUTABLE)
added_exe_file_vma(mm);
/* Can addr have changed??
*
* Answer: Yes, several device drivers can do it in their
* f_op->mmap method. -DaveM
*/
addr = vma->vm_start;
pgoff = vma->vm_pgoff;
vm_flags = vma->vm_flags;
} else if (vm_flags & VM_SHARED) {
if (unlikely(vm_flags & (VM_GROWSDOWN|VM_GROWSUP)))
goto free_vma;
error = shmem_zero_setup(vma);
if (error)
goto free_vma;
}
......
}
阅读(2122) | 评论(0) | 转发(0) |