Memory Map(Linux 存储映射IO)

本文主要总结自UNIX环境高级编程以及RedHat6.5系统man函数

存储映射IO

函数说明 mmap

#include <sys/mman.h>
void *mmap(void *addr,//指定映射区的起始地址。通常设置为0,由系统选择该映射区起始地址 
    size_t len, //映射的字节数
    int prot, //对映射存储区的保护要求
    int flag, //
    int filedes, //被映射文件的描述符 
    off_t off) //要映射字节在文件中的起始偏移量

//返回值:若成功则返回映射区的起始地址,若出错则返回MAP_FAILED
  • prot参数详细说明
prot说明
PROT_READ映射区可读
PROT_WRITE映射区可写
PROT_EXEC映射区可执行
PROT_NONE映射区不可访问

prot参数可以是上述4中权限的任意组合的按位或,并且对指定映射存储区的的保护要求不能超过文件open模式访问权限(如果文件是只读打开的,那么对映射存储区就不能指定PROT_WRITE)。
* flag参数详细说明

  • MAP_FIXED 返回值必须等于addr,。因为这不利于可移植性,所以不鼓励使用此标志。
  • MAP_SHARED 这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文件,也就是说,存储操作相当于对该文件write。必须指定本标志或下一个标志(MAP_PRIVATE),但不能同时指定两者
    *每种实现都可能还有另外一些MAP_XXX标志值,它们是这种实现所特有的。详细情况请参见使用系统的mmap(2)手册页。
  • MAP_32BIT (since Linux 2.4.20, 2.6)
    把映射区放到运行地址空间的前2G空间内。这个flag职位64位系统的64位程序中使用。这个标志被用来允许程序栈在前2G内存中分配空间,同时在早期64位处理器上 提高上下文切换(context-switch)执行。现在的x84-64位处理器不再有这个执行问题,所以在目前系统中不再需要这个flag了。所以在使用MAP_FIXED标志是,此标志自动被忽略了。
  • MAP_PRIVATE
    本标志说明,对映射区的存储操作导致创建该映射文件的一个私有副本。所有后来对该映射区的引用都是引用该副本,而不是原始文件。(此标志的一种用途是用于调试程序,它将依程序文件的正文部分映射至一存储区,但允许用户修改其中的指令。任何修改只影响程序文件的副本,但不影响原文件)
  • MAP_ANON
    MAP_ANONYMOUS的同义词,不赞成使用。
  • MAP_ANONYMOUS
    内存存储区并不依赖于任何文件;它的内容被初始化在地址0(?)。fileds和off变量是被忽略的,无论如何,如果要使用MAP_ANONYMOUS,一些具体实现要求fileds要置为-1,并且portable applications应该确认这些。共同使用MAP_ANONYMOUS和MAP_SHARED只在linux内核2.4以上支持。

    • off和addr补充说明
  • off和addr的值(如果指定了MAP_FIXED)通常应当是系统虚存页长度的倍数。虚存页长可用带参数_SCPAGESIZE或_SCPAGE_SIZE的sysconf函数得到。因为off和addr常常指定为0,所以这种要求一般并不重要。

映射区相关

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

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

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

函数说明 mproject

#include <sys/mman.h>

int mprotect(void *addr, //必须是系统页长度的整数倍 
    size_t len, 
    int prot); //和mmap中的prot参数一样

//返回值:若成功则返回0,出错返回-1

函数说明 msync

#include <sys/mman.h>

int msync(void *addr, //必须与页边界对齐
    size_t len, 
    int flags); 
//返回值:若成功则返回0,若出错则返回-1
  • 如果映射是私有的,那么不修改被映射的文件。
  • flags参数是我们对如何冲洗存储区有某种程度的控制。我们可以指定MS_ASYNC标志以简化被写页的调度。如果我们希望在返回之前等待写操作完成,则可指定MS_SYNV标志。一定要指定MS_ASYNC和MS_SYNC中的一个。MS_INVALIDATA是一个可选标志,使用它们以通知操作系统丢弃与底层存储器没有同步的任何页。若使用了此标志,某些实现将丢弃在指定范围中的所有页,但这并不是所期望的。

函数说明 munmap

#include <sys/mman.h>

int munmap(caddr_t addr, size_t len);
//返回值:若成功则返回0,若出错则返回-1
  • 进程终止时,或调用了munmap之后,存储映射去就被自动解除映射。关闭文件描述符filedes并不解除映射区
  • munmap不会影响被映射的对象,也就是说,调用munmap不会使映射区的内容写到磁盘文件上。对于MAP_SHARED区磁盘文件的更新,在写到存储映射区时按内核虚存算法自动执行。在解除映射后,对于MAP_PRIVATE存储区的修改被丢弃。

实例

用存储映射IO复制一个文件

#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char *argv[])
{
    int  fdin, fout;
    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((fout = open(argv[2], ORDWR | O_CREAT | O_TRUNC)) < 0)
        err_sys("can't create %s for reading", 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(fout, "", 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, fout, 0)) == MAP_FAILED)
        err_sys("mmap error for output");

    memcpy(dst, src, statbuf.st_size);// does the file copy

    //msync(dst, statbuf.st_size, MS_SYNC);//ensure write to file
    exit(0);
}

程序说明
1. 程序首先打开两个文件,然后调用fstat得到输入文件的长度,在为输入文件调用mmap的设置输出文件长度时都需要使用输入文件长度。调用lseek,然后写一个自己以设置输出文件的长度。如果不设置输出文件的长度,则对输出文件调用mmap也可以,但是对相关存储区的第一次引用会缠上SIGBUS。也可以使用ftruncate函数来设置输出文件的长度,但是并非所有操作系统都支持该函数扩充文件长度。
2. 然后对每个文件调用mmap将文件映射到存储区上,最后调用memcpy将输入缓冲区的内容复制到输出缓冲区。在从输入缓冲区(src)取数据字节时,内核自动读输入文件;在将数据存入输出缓冲区(dst)时,内核自动将数据写到输出文件中。
3. 特别的:数据被写被写入文件的确切时间依赖于系统的业管理算法。某些系统设置了守护进场,在系统运行期间,它”慢条斯理“地将脏页写到磁盘上。如果想要确保数据安全的写到文件中,则需要在进程终止前以MS_SYNC标志调用msync

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值