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

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

#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
返回值:若成功则返回映射区的起始地址,若出错则返回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可以更改一个现存映射存储区的权限。

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

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

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

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

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

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

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

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

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

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

实例

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

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

#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h> int
main(int argc, char *argv[])
{
int fdin, fdout;
void *src, *dst;
struct stat statbuf; if(argc != 3)
err_quit("usage: %s <fromfile> <tofile>", argv[0]); if((fdin = open(argv[1], O_RDONLY)) < 0)
err_sys("can't open %s for reading", argv[1]); if((fdout = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, FILE_MODE)) < 0)
err_sys("can't create %s for writing", argv[2]); if(fstat(fdin, &statbuf) < 0) /*need size of input file */
err_sys("fstat error"); /* set size of output file */
if(lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1)
err_sys("lseek error");
if(write(fdout, "", 1) != 1)
err_sys("write error"); if((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED)
err_sys("mmap error for input"); if((dst = mmap(0, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED)
err_sys("mmap error for output"); memcpy(dst, src, statbuf.st_size); /* does the file copy */
exit(0);
}

该程序首先打开两个文件,然后调用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. 使用Redis的理由

    Redis是一个远程内存数据库,它不仅性能强劲,而且还具有复制特性以及为解决问题而生的独一无二的数据模型.Redis提供了5种不同类型的数据结构,各式各样的问题都可以很自然地映射到这些数据结构上:Re ...

  2. C语言的代码内存布局

    由以下3个部分组成: 1)BSS 段 BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英文Block Started by Symbol的简称.BSS ...

  3. 使用Google Chart API绘制组合图

    Google Chart API 绘图 组合图作者:方倍工作室 地址: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN& ...

  4. bzoj 1778 [Usaco2010 Hol]Dotp 驱逐猪猡(高斯消元)

    [题意] 炸弹从1开始运动,每次有P/Q的概率爆炸,否则等概率沿边移动,问在每个城市爆炸的概率. [思路] 设M表示移动一次后i->j的概率.Mk为移动k次后的概率,则有: Mk=M^k 设S= ...

  5. Uva 11198 - Dancing Digits

    Problem D Dancing Digits 题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid ...

  6. asp.net mvc 实体类成员变量标识示例

    检查不能为空 [Required] public string ID { get; set; } 检查最大长度 [StringLength(36, ErrorMessage = "长度不可超 ...

  7. flask中的request.form对象方法

    'add','clear','copy','deepcopy','fromkeys','get','gtlist','has_key','items','iteritems','iterkeys',' ...

  8. uva10474 简单排序查找 一次AC

    题目很简单,加上读题20分钟一次AC.还是用到了快排qsort. #include<iostream> #include<cstdlib> using namespace st ...

  9. Codeforces Round #350 (Div. 2) E. Correct Bracket Sequence Editor (链表)

    题目链接:http://codeforces.com/contest/670/problem/E 给你n长度的括号字符,m个操作,光标初始位置是p,'D'操作表示删除当前光标所在的字符对应的括号字符以 ...

  10. 关于block以及__bridge的一些笔记

    问题概要 _block是否是一个OC对象? __bridge相关. _block是否是一个OC对象? 结论 一般来说,block可以看做一个OC对象,但是在编译器底层,block又可以被细分为bloc ...