存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区,则相应字节就自动地写入文件。这样就可以在不使用read和write的情况下执行I/O。

为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中。这是由mmap函数实现的

  1. #include <sys/mman.h>
  2. void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
  3. 返回值:若成功则返回映射区的起始地址,若出错则返回MAP_FAILED

addr参数用于指定映射存储区的起始地址。通常将其设置为0,这表示由系统选择该映射区的起始地址。此函数的返回地址是该映射区的起始地址。

filedes指定要被映射文件的描述符。在映射该文件到一个地址空间之前,先要打开该文件。len是映射的字节数off是要映射字节在文件中的起始偏移量

prot参数说明对映射存储区的保护要求,见表14-9。

表14-9 映射存储区的保护要求

       prot         说明
PROT_READ

PROT_WRITE

PROT_EXEC

PROT_NONE

映射区可读

映射区可写

映射区可执行

映射区不可访问

 

可将prot参数指定为PROT_NONE,或者是PROT_READ、PROT_WRITE、PROT_EXEC任意组合的按位或。对指定映射存储区的保护要求不能超过文件open模式访问权限。例如,若该文件是只读打开的,那么对映射存储区就不能指定PROT_WRITE。

图14-11显示了一个存储映射文件。(见http://www.cnblogs.com/nufangrensheng/p/3508169.html中图7-3所示进程存储空间的典型安排情况。) 在此图中,“起始地址”是mmap的返回值。映射存储区位于堆和栈之间,这属于实现细节,各种实现之间可能不尽相同。

                                      图14-11 存储映射文件的例子

flag参数影响映射存储区的多种属性:

MAP_FIXED          返回值必须等于addr。因为这不利于可移植性,所以不鼓励使用此标志。如果未指定此标志,而且addr非0,则内核只把addr视为在何处设置映射区的一种建议,但是不保证会使用所要求的地址。将addr指定为0可获得最大可移植性。

MAP_SHARED       这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文件,也就是说,存储操作相当于对该文件的write。必须指定本标志或下一个标志(MAP_PRIVATE),但不能同时指定两者。

MAP_PRIVATE       本标志说明,对映射区的存储操作导致创建该映射文件的一个私有副本。所有后来对该映射区的引用都是引用该副本,而不是原始文件。(此标志的一种用途是用于调试程序,它将程序文件的正文部分映射至一存储区,但允许用户修改其中的指令。任何修改只影响程序文件的副本,而不影响原文件。

每种实现都可能还有另外一些MAP_xxx标志值,它们是这种实现所特有的。

off和addr的值(如果指定了MAP_FIXED)通常应当是系统虚存页长度的倍数。虚存页长可用带参数_SC_PAGESIZE或_SC_PAGE_SIZE的sysconf函数(见http://www.cnblogs.com/nufangrensheng/p/3496323.html)得到。因为off和addr常常指定为0,所以这种要求一般并不重要。

因为映射文件的起始偏移量受系统虚存页长度的限制,那么如果映射区的长度不是页长的整数倍时,将如何呢?假定文件长12字节,系统页长512字节,则系统通常提供512字节的映射区,其后的500字节被设置为0。可以修改这500字节,但任何变动都不会在文件中反映出来。于是,我们不能用mmap将数据添加到文件中。为了做到这一点,我们必须首先加长该文件,这将示于程序清单14-12中。

与映射存储区相关的有SIGSEGV和SIGBUS两个信号。信号SIGSEGV通常用于指示进程试图访问对它不可用的存储区。如果进程企图存数据到mmap指定为只读的映射存储区,那么也产生此信号。如果访问映射区的某个部分,而在访问时这一部分实际上已不存在,则产生SIGBUS信号。例如,用文件长度映射了一个文件,但在引用该映射区之前,另一个进程已将该文件截短。此时,如果进程企图访问对应于该文件已截去部分的映射区,则会收到SIGBUS信号。

在调用fork之后,子进程继承存储映射区(因为子进程复制父进程地址空间,而存储映射区是该地址空间的一部分),但是由于同样的理由,调用exec后的新程序则不继承存储映射区。

调用mprotect可以更改一个现存映射存储区的权限。

  1. #include <sys/mman.h>
  2. int mprotect(void *addr, size_t len, int prot);
  3. 返回值:若成功则返回0,若出错则返回-1

prot的许可值与mmap中prot参数一样(表14-9)。地址参数addr的值必须是系统页长的整数倍。

如果在共享存储映射区中的页已被修改,那么我们可以调用msync将该页冲洗到被映射的文件中。msync函数类似于fsync(见http://www.cnblogs.com/nufangrensheng/p/3498760.html),但作用于存储映射区。

  1. #include <sys/mman.h>
  2. int msync(void *addr, size_t len, int flags);
  3. 返回值:若成功则返回0,若出错则返回-1

如果映射是私有的,那么不修改被映射的文件。与其他存储映射函数一样,地址必须与页边界对齐。

flags参数使我们对如何冲洗存储区有某种程度的控制。我们可以指定MS_ASYNC标志以简化被写页的调度。如果我们希望在返回之前等待写操作完成,则可指定MS_SYNC标志。一定要指定MS_ASYNC和MS_SYNC中的一个。

MS_INVALIDATE是一个可选标志,使用它以通知操作系统丢弃与底层存储器没有同步的任何页。若使用了此标志,某些实现将丢弃在指定范围中的所有页,但这并不是所期望的。

进程终止时,或调用了munmap之后,存储映射区就被自动解除映射。关闭文件描述符filedes并不解除映射区。

  1. #include <sys/mman.h>
  2. int munmap(caddr_t addr, size_t len);
  3. 返回值:若成功则返回0,若出错则返回-1

munmap不会影响被映射的对象,也就是说,调用munmap不会使映射区的内容写到磁盘文件上。对于MAP_SHARED区磁盘文件的更新,在写到存储映射区时按内核虚存算法自动进行。在解除了映射后,对于MAP_PRIVATE存储区的修改被丢弃。

实例

程序清单14-12用存储映射I/O复制一个文件(类似于cp(1)命令)。

程序清单14-12 用存储映射I/O复制文件

  1. #include "apue.h"
  2. #include <fcntl.h>
  3. #include <sys/mman.h>
  4.  
  5. int
  6. main(int argc, char *argv[])
  7. {
  8. int fdin, fdout;
  9. void *src, *dst;
  10. struct stat statbuf;
  11.  
  12. if(argc != 3)
  13. err_quit("usage: %s <fromfile> <tofile>", argv[0]);
  14.  
  15. if((fdin = open(argv[1], O_RDONLY)) < 0)
  16. err_sys("can't open %s for reading", argv[1]);
  17.  
  18. if((fdout = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, FILE_MODE)) < 0)
  19. err_sys("can't create %s for writing", argv[2]);
  20.  
  21. if(fstat(fdin, &statbuf) < 0) /*need size of input file */
  22. err_sys("fstat error");
  23.  
  24. /* set size of output file */
  25. if(lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1)
  26. err_sys("lseek error");
  27. if(write(fdout, "", 1) != 1)
  28. err_sys("write error");
  29.  
  30. if((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED)
  31. err_sys("mmap error for input");
  32.  
  33. if((dst = mmap(0, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED)
  34. err_sys("mmap error for output");
  35.  
  36. memcpy(dst, src, statbuf.st_size); /* does the file copy */
  37. exit(0);
  38. }

该程序首先打开两个文件,然后调用fstat得到输入文件的长度。在为输入文件调用mmap和设置输出文件长度时都需使用输入文件长度。调用lseek,然后写一个字节以设置输出文件的长度(可参考http://www.cnblogs.com/nufangrensheng/p/3498080.html)。如果不设置输出文件的长度,则对输出文件调用mmap也可以,但是对相关存储区的第一次引用会产生SIGBUS。也可使用ftruncate函数来设置输出文件的长度,但是并非所有系统都支持该函数扩充文件长度(见http://www.cnblogs.com/nufangrensheng/p/3502755.html)。

然后对每个文件调用mmap,将文件映射到存储区,最后调用memcpy将输入缓冲区的内容复制到输出缓冲区。从输入缓冲区(src)取数据字节时,内核自动读输入文件;在将数据存入输出缓冲区(dst)时,内核自动将数据写到输出文件中。

数据被写入文件的确切时间依赖于系统的页管理算法。某些系统设置了守护进程,在系统运行期间,它“慢条斯理”地将脏页写到磁盘上。如果想要确保数据安全地写到文件中,则需在进程终止前以MS_SYNC标志调用msync。

将一个普通文件复制到另一个普通文件中时,存储映射I/O比较快。但是有一些限制,例如,不能用其在某些设备(例如网络设备或终端设备)之间进行复制,并且在对被复制的文件进行映射后,也要注意该文件的 长度是否改变。

本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

高级I/O之存储映射I/O的更多相关文章

  1. 第3章 文件I/O(7)_高级文件操作:存储映射

    8. 高级文件操作:存储映射 (1)概念: 存储映射是一个磁盘文件与存储空间的一个缓存相映射,对缓存数据的读写就相应的完成了文件的读写. (2)mmap和munmap函数 头文件 #include&l ...

  2. UNIX环境高级编程——存储映射I/O(mmap函数)

         共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式,因为进程可以直接读写内存,而不需要任何数据的拷贝.对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共 ...

  3. 【Linux编程】存储映射I/O

    存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射,对缓冲区的读.写操作就是对文件的读.写操作,从而能够不再使用read.write系统调用. 将文件映射到存储区的函数由mmap完毕,函数原型 ...

  4. 存储映射I/O

    一个进程拥有独立并且连续虚拟地址空间,在32位体系结构中进程的地址空间是4G.不过,内核在管理进程的地址空间时是以内存区域为单位.内存区域是进程整个地址空间中一个独立的内存范围,它在内核中使用vm_a ...

  5. 八、文件IO——存储映射

    8.1 存储映射介绍 8.1.1 概念 存储映射是一个磁盘文件与存储空间的一个缓存相映射,对缓存数据的读写就相应的完成了文件的读写. 文件操作部分映射到虚拟内存的一块区域,我们对虚拟内存映射的那块区域 ...

  6. 存储映射--mmap

    存储映射 使一个磁盘文件与存储空间中的一个缓冲区相映射. 当从缓冲区中取数据,就相当于读文件中的相应字节. 将数据存入缓冲区,则相应的字节就自动写入文件. 使用这种方法,首先应通知内核,将一个指定文件 ...

  7. 共享内存与存储映射(mmap)

    [前言]对这两个理解还是不够深刻,写一篇博客来记录一下. 首先关于共享内存的链接:共享内存.里面包含了创建共享内存区域的函数,以及两个进程怎么挂载共享内存通信,分离.释放共享内存. 共享内存的好处就是 ...

  8. 《Entity Framework 6 Recipes》中文翻译系列 (36) ------ 第六章 继承与建模高级应用之TPC继承映射

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-12  TPC继承映射建模 问题 你有两张或多张架构和数据类似的表,你想使用TP ...

  9. 存储映射I/O函数

    1.void  * mmap((void *addr, size_t length, int prot, int flags, int fd, off_t offset) 参数: addr:用于指定映 ...

随机推荐

  1. TestNG传参的几种方式

    1. 通过parameter传参 java代码部分: import org.testng.annotations.Parameters; import org.testng.annotations.T ...

  2. Robotium简要学习

    Robotium是一款国外的Android自动化测试框架,主要针对Android平台的应用进行黑盒自动化测试,它提供了模拟各种手势操作(点击.长按.滑动等).查找和断言机制的API,能够对各种控件进行 ...

  3. IOS-day02_OC中类的声明

    在上一个笔记中类的使用中,在编译链接的时候会有警告,原因就是我们没有对类进行声明 类的声明如下:使用关键字@interface #import <Foundation/Foundation.h& ...

  4. Tomcat中的线程池StandardThreadExecutor

    之所以今天讨论它,因为在motan的的NettyServer中利用它这个线程池可以作为业务线程池,它定制了一个自己的线程池.当然还是基于jdk中的ThreadExecutor中的构造方法和execut ...

  5. Language Basics:语言基础

    Java包含多种变量类型:Instance Variables (Non-Static Fields)(实例变量):是每个对象特有的,可以用来区分各个实例Class Variables (Static ...

  6. Windows Azure Platform (一) 云计算的出现

    <Windows Azure Platform 系列文章目录> 最近的一年一直致力于微软云计算技术的推广和研究,对于微软的云计算平台Windows Azure Platform有一定的了解 ...

  7. RabbitMQ>Erlang machine stopped instantly (distribution name conflict?). The service is not restarted as OnFail is set to ignore.-报错解决方案 原来是NNND。。。

    >Erlang machine stopped instantly (distribution name conflict?). The service is not restarted as ...

  8. 优秀android开源项目与解决方案推荐

    后来加上的,因为太强大了,android上百个可立即使用的开源库介绍:https://github.com/Trinea/android-open-project Android上的FTP服务器  S ...

  9. Spark的应用程序

    Spark的应用程序,分为两部分:Spark driver 和 Spark executor.

  10. Apache Spark MLlib的简介

    MLlib 是构建在 Spark 上的分布式机器学习库,充分利用了 Spark 的内存计算和适合迭代型计算的优势,将性能大幅度提升.同时由于 Spark 算子丰富的表现力, 让大规模机器学习的算法开发 ...