虚拟内存三

虚拟内存三

一、内存映射

​ Linux通过将一个虚拟内存区域和一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程叫内存映射。

1.映射对象

Linux文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分,如一个可执行文件。文件区被分成页大小的片,每一片初始化一个虚拟页的内容。(不发生数据传输)按需进行页面调度,文件并不进入物理内存中。直到cpu第一次引用到页面。(即发射一个虚拟地址,落在地址空间这个页面的范围之内)

匿名文件:一个区域页可以映射到一个匿名文件。匿名文件是由内核创建的,包含的全是二进制0。cpu第一次引用这样的页面时,内核在物理内存中找一个合适的牺牲页面,若它被修改过,就将它换出,并用二进制零覆盖并更新页表。注意磁盘和内存之间没有数据传输。因此,有时映射到匿名文件区域中的页面也叫请求二进制零的页

​ 无论哪种情况,一旦一个虚拟页面被初始化了,它就在内核维护的一个交换文件之间换来换去。

2.共享对象和私有对象

​ 一个对象可以被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象。如果一个进程将共享对象映射到它的虚拟地址空间,那它对该对象的写操作会反映到其他引用该对象的进程上和磁盘原始文件上。

​ 因为每个共享文件只有一个文件名,所以内核可以很清楚地知道这个文件是否被共享。所以即使对象被映射到了多个共享区域,物理内存中也只有共享对象的一个副本。

​ 对私有对象的写操作不会反映到其他引用该对象的进程,也不会反映到磁盘原始文件上。

​ 私有对象一开始的管理行为和共享文件相同,即使对象被映射到了多个私有区域,物理内存中也只有私有对象的一个副本。但它采用写时复制技术。即内存中的相应区域是只读的,当有进程对其进行写操作时,发生保护故障,异常处理程序发现这块内存是写时复制的,它会在物理内存中创建这个页面的一个副本,更新页表条目执行这个新的副本,然后恢复可写权限。避免了不必要的复制。

应用

​ 许多程序运行时需要访问相同的代码副本,例如标准C库,我们将这些共享库作为共享对象映射到进程虚拟地址空间中,内存中就只需要保留一份相关代码副本,节约空间。

3.用户级内存映射—-mmap

​ Linux进程可以使用mmap函数来创建新的虚拟内存区域,并将这些对象映射到这些区域中。

1
void* mmap(void* start,size_t length,int prot,int flags.int fd,off_t offset);

​ mmap要求内核创建一个新的虚拟内存区域,从地址start开始。并将fd指定的对象的一个连续的片映射到这个区域,片大小为length,从距文件偏移量offset字节开始。注意:start只是一个暗示,通常为NULL,由内核选定地址。

​ 参数prot:映射区域的保护方式。可以为以下几种方式的组合:

​ PROT_EXEC 映射区域可被执行
​ PROT_READ 映射区域可被读取
​ PROT_WRITE 映射区域可被写入
​ PROT_NONE 映射区域不能存取

​ 参数flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

​ MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
​ MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
​ MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
​ MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
​ MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
​ MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)

4.mmap和普通文件读写的区别

​ 常规文件系统操作(调用read/fread等类函数)中,函数的调用过程:

​ 1、进程发起读文件请求。

​ 2、内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode。

​ 3、inode在address_space上查找要请求的文件页是否已经缓存在页缓存中。如果存在,则直接返回这片文件页的内容。

​ 4、如果不存在,则通过inode定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后再次发起读页面过程,进而将页缓存中的数据发给用户进程。

​ 总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

​ 而使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

​ 总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。

5.内存映射的好处

​ 1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。

​ 2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。

​ 3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。

​ 同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。

​ 4、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。