操作系统——内存映射

定义

虚拟内存的目标存储器是磁盘,所以虚拟内存区域是和磁盘中的文件对应的。初始化虚拟内存的内容时,会把虚拟内存区域和一个磁盘文件对象对应起来,这个过程叫内存映射。被映射的对象称为:共享对象。虚拟内存可以映射的磁盘文件对象包括两种:

  • 一个普通的磁盘文件,文件中的内容被分成页大小的块,因为按需进行页面调度,只有真正需要读取这些虚拟页面时,才会交换到主存。
  • 一个匿名文件,匿名文件时内核创建的,内容全是二进制0,它相当于一个占位符,不会产生实际的磁盘流量。映射到匿名文件中的页叫做请求二进制零的页。

作用

在多个进程的虚拟内存区域已和同一个共享对象建立映射关系的前提下,若其中一个进程对该虚拟区域进行写操作,那么对于也把该共享对象映射到其自身虚拟内存区域的进程也是可见的。

假设进程1,2的虚拟内存区域同时映射到同一个共享对象:
当进程1对其虚拟内存进行写操作时,也会被映射到进程2的虚拟内存区域中。
示意图如下:


image.png

image.png

实现过程

  • 内存映射的实现过程主要时通过Linux系统下的调用函数:mmap()
  • 该函数的作用 = 创建虚拟内存区域+与共享对象建立映射关系。
  • 函数原型:
/**
  * 函数原型
  */
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

  • 函数的使用:
/**
  * 具体使用(用户进程调用mmap())
  * 下述代码即常见了一片大小 = MAP_SIZE的接收缓存区 & 关联到共享对象中(即建立映射)
  */
  mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
  • 内部原理:
    步骤1:创建虚拟内存区域
    步骤2:实现地址映射关系,即:进程的虚拟地址空间映射到共享对象。
    注意:这个时候该虚拟地址并没有任何数据关联到文件中,仅仅只是建立映射关系,当其中一个进程对虚拟内存写入数据时,则真正实现了数据的可见。

特点

  • 提高数据的读,写和传输的性能。
      1.减少了数据拷贝次数
      2.用户空间和内核空间的高效交互。
      3.用内存读写代替I/O读写。
  • 提高内存利用率:通过虚拟内存和共享对象。

应用场景

1.实现内存共享,例如跨进程通信。
2.提高数据读/写效率,如文件读/写操作。

实例讲解

传统的Linux系统文件操作流程如下:
  • 1.用户进程发起读文件请求
  • 2.内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode。
  • 3.inode在address_space上查找要请求的文件页是否已经存在缓存页中。如果存在,则直接返回这片文件页的内容。如果不存在,则通过inode定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后再发起读页面过程,将页面缓存中的数据返回给用户进程。
    示意图:


    image.png

    特点:

  • 常规文件操作为了提高读写效率&保护磁盘,使用了页缓存机制。
  • 需要两次拷贝才能完成操作。
使用内存映射的文件读/写操作:

 工作流程如下:

  • 步骤1.创建虚拟映射区域
  • 1.在当前进程的虚拟内存空间中,寻找一段满足要求大小的虚拟地址。
  • 2.为此虚拟地址分配一个虚拟内存区域
  • 3.初始化该虚拟内存区域
  • 4.插入该虚拟内存区域到进程的虚拟内存地址的链表/树中。
  • 步骤2.实现地址映射关系
  • 1.依次通过待映射文件指针,文件描述符和文件结构体,最终调用内核空间中系统调用函数mmap()
  • 2.内核空间的mmap()通过虚拟文件系统inode模块定位到文件磁盘物理地址。
    3.通过remap_pfn_range()建立页表,即实现了文件地址和虚拟地址区域的映射关系。

这两个步骤创建虚拟空间和映射地址,但时并无将任何文件数据拷贝到主存;真正的数据拷贝时刻:当进程发起读/写操作时。

  • 步骤 3。进程访问该映射空间,实现文件内容到物理内存的数据拷贝。
  • 1.进程的读/写操作访问虚拟地址空间这一段映射地址。
  • 2.若进程通过写操作改变了其内容,一定时间后系统会自动回写脏页面到对应的磁盘地址,即完成了写到文件的过程。(修改的脏页面不会立即更新,而是又延迟,但是可通过msync()来强制同步,此时,所写的内容就能立即保存到文件。)

示意图:


image.png

特点:直接通过映射进行交互,数据拷贝的次数只有一次,文件读取的效率高,可实现高效大规模数据传输。

跨进程通信

使用传统的跨进程通信

工作流程:

  • 1.发送进程通过系统调用,将需要发送的数据拷贝到Linux进程的内核空间中的缓存区中。(数据拷贝1次,通过copy_from_user())。
  • 2.内核服务程序,唤醒接收进程的接收线程,通过系统调用将数据发送到接收进程的用户空间中,最终完成数据的传输。

示意图:


image.png

缺点:

  • 需要2次拷贝数据,效率低下
  • 接收数据的缓存要由接收方提高,但是接收方不知道到底要多大的缓存才满足要求。
使用内存映射跨进程通信

工作流程:

  • 1.创建一块共享的接收缓存区。
  • 2.实现地址映射关系
  • 3.发送进程将数据发送搭配自身的虚拟内存区域,拷贝数据1次
  • 4.由于发送进程的虚拟内存空间核接收进程的虚拟内存地址存在映射关系,故相当于也发送到了接收进程的虚拟内存地址中。实现跨进程通信

示意图:


image.png

优点:

  • 数据只拷贝一次,用户空间之间直接通过共享对象直接交互。
  • 为接收进程分配了不确定的大小的接收缓存区。

参考链接:
https://www.jianshu.com/p/719fc4758813

你可能感兴趣的:(操作系统——内存映射)