首页 > 代码库 > 内存映射
内存映射
1.概述
一个线性区可以和磁盘文件系统的普通文件的某一个部分或者块设备文件相关联。这就意味着内核把对线性区中页内某个字节的访问转换成对文件中相应字节的操作,这种技术称为内存映射。
有两种类型的内存映射:共享型:
在线性区页上的任何写操作都会修改磁盘上的文件;而且,如果进程对共享映射中的一个页进行写,那么这种修改对于其他映射了这同一文件的所有进程来说都是可见的。
私有型:
当进程创建的映射只是读文件,而不是写文件时才会使用此种映射。出于这种目的,私有映射的效率要比共享映射的效率更高。但是对私有映射页的任何写操作都会使内核停止映射该文件中的页。因此,写操作不会修改磁盘上的文件,对访问相同文件的其他进程也不可见。
进程可以发出一个mmap()系统调用来创建一个新的内存映射。程序员必须指定一个MAP_SHARED标志或者MAP_PRIVATE标志,而后一种情况下是私有映射。一旦创建这种映射,进程就可以从这个新线性区的内存单元读取数据,也就等价于读取了文件中存放的数据。如果这个内存映射是共享的,那么进程可以通过对相同的内存单元进行写而达到修改相应文件的目的。
2.内存映射的数据结构
内存映射可以用下列数据结构的组合来表示:
- 与所映射的文件相关的索引节点对象
- 不同进程对一个文件进行不同映射所使用的文件对象
- 对文件进行每一不同映射所使用的vm_area_struct描述符
- 对文件进行映射的线性区所分配的每个页框所对应的页描述符
每个线性区都有一个vm_file字段,与所映射文件的文件对象连接(如果该字段为NULL,则线性区没有用于内存映射)。第一个映射单元的位置存放在线性区描述符的vm_pgoff字段,它表示以页大小为单元的偏移量。
3.创建内存映射
要创建一个新的内存映射,进程就要发出一个mmap()系统调用,并向该函数传递以下参数:
- 文件描述符,标识要映射的文件
- 文件内的偏移量
- 要映射的文件部分的长度
- 一组标志。
- 一组权限
- 一个可选的线性地址
mmap()系统调用返回新线性区中的第一个单元位置的线性地址。下面我们就详细介绍当对文件进行映射的线性区时执行的步骤。我们所讨论的是do_mmap_pgoff()的file参数(文件对象指针)非空的情形。为清楚起见,我们要列举描述do_mmap_pgoff的步骤,并指出新条件的其他步骤:
1.检查是否为要映射的文件定义了mmap文件操作。如果没有则返回错误码
2.函数get_unmapped_area()调用文件对象的get_unmapped_area方法,如果已定义,就为文件的内存映射分配一个合适的线性区间。
3.除了进行正常的一致性检查之外,还要对所请求的内存映射的种类(mmap的flags参数)与打开文件时所指定的标志(存放在file->f_mode字段中)进行比较。根据这两个消息源,执行以下检查:
- 如果请求一个共享可写的内存映射,就检查文件是为写入而打开的,而不是以追加模式打开的。
- 如果请求一个共享内存映射,就检查文件没有强制上锁
- 对于任何种类的内存映射,都要检查文件是为读操作而打开的。
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; break; default: return -EINVAL; }
如果以上这些条件都不能满足,就返回一个错误码。
4.用文件对象的地址初始化线性区描述符的vm_file字段,并增加文件的引用计数。对映射的文件调用mmap方法,将文件对象地址和线性区描述符地址作为参数传给他。
if (file) { error = -EINVAL; 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); error = file->f_op->mmap(file, vma); if (error) goto unmap_and_free_vma;
对于大多数文件系统,该方法由generic_file_mmap()实现,它执行如下步骤:
a.将当前时间赋值给文件索引节点对象的i_atime字段,并将该索引节点标记为脏。
b.用generic_file_vm_ops表的地址初始化线性区描述符的vm_ops字段。在这个表中的方法,除了nopage和populate方法外,其他都是空。nopage方法由filemap_nopage()实现,而populate方法由filemap_populate()实现。
5.增加文件索引节点的i_writecount字段的值,该字段就是写进程的引用计数。
5.内存映射的请求调页
出于效率的原因,内存映射创建之后并没有立即把页框分配给它,而是尽可能向后延迟到不再延迟--也就说,当进程试图对其中的一页进行寻址时,就产生一个缺页异常(后面会专门写一篇缺页异常的文章)
内核会验证缺页所在的地址是否包含在某个进程的线性区中,如果是这样,那么内核就检查这个线性地址所对应的页表项,如果表项为空就调用do_no_page()函数。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。