一个进程拥有独立并且连续虚拟地址空间,在32位体系结构中进程的地址空间是4G。不过,内核在管理进程的地址空间时是以内存区域为单位。内存区域是进程整个地址空间中一个独立的内存范围,它在内核中使用vm_area_struct数据结构来描述。每个内存区域都有自己访问权限以及操作函数,因此进程只能对有效范围的内存地址进行访问。

存储映射I/O是一种基于内存区域的高级I/O操作,它将磁盘文件与进程地址空间中的一个内存区域相映射。当从这段内存中读数据时,就相当于读磁盘文件中的数据,将数据写入这段内存时,则相当于将数据直接写入磁盘文件。这样就可以在不使用基本I/O操作函数read和write的情况下执行I/O操作。

1.基本实现方法

实现存储映射I/O的核心操作是通过mmap系统调用将一个给定的磁盘文件映射到一个存储区域中。

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

关于该函数定义中各个参数说明Linux上的man手册已经解释的很清楚,在此不再赘述。这里需要特别说明的是prot和flags参数。prot用来指定对映射区域的保护要求,但是它的保护范围不能超过文件open时指定的打开权限。比如以只读(PROT_READ)方式打开一个文件,那么以读写(PROT_READ|PROT_WRITE)方式保护内存区域是不合法的。flags用来指定内存区域的多种属性,两个典型的取值是MAP_SHARED和MAP_PRIVATE。MAP_SHARED标志指定了进程对内存区域的修改会影响到映射文件。而当对flags指定MAP_PRIVATE时,进程会为该映射内存区域创建一个私有副本,对该内存区的所有操作都是在这个副本上进行的,此时对内存区域的修改并不会影响到映射文件。

下面列出一个简单的示例程序,它将磁盘文件映射到一个内存区域中,通过mmap返回的指针先读文件,再写文件。可以看到对文件的读和写操作都是通过内存映射I/O的方式完成的。

int main()
    {
        int fd;
        char *buf = NULL;
        int i;
    
        //打开一个文件
        if (-1 == (fd = open("./mapping_file.txt", O_RDWR))) {
            printf("open file error!\n");
            exit(1);
        }
    
        //将文件映射到进程的一个内存区域
        buf = mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (!buf) {
            printf("mmap error!\n");
            exit(1);
        }
    
        //对映射内存读数据
        for (i = 0; i < 100; i++)
        printf("%c", buf[i]);
    
        //对映射内存写数据
        if (buf[0] == 'H')
            buf[0] = 'h';
        else
            buf[0] = 'H';
    
        system("cat ./mapping_file.txt");
        return 0;
    }

2.使用内存映射I/O进行文件拷贝

使用基本I/O操作函数如何实现一个类似cp命令的程序?比如我们要将A文件复制到B文件,那么程序的基本框架是这样的:

1.open()文件A和文件B

2.将A文件的内容read()到buffer

3.将buffer中的数据write()到文件B

4.close()文件A和文件B

如果使用内存映射I/O来实现cp命令,那么它的基本框架是这样的:

1.open()文件A和文件B

2.mmap()文件A和文件B,其中src和dest分别为两个文件映射到内存的地址

3.将以src为起始的len长字节数据memcpy()到dest

4.close()文件A和文件B

示例程序如下:

int main()
    {
        int srcfd, destfd;
        struct stat statbuf;
        char *src = NULL, *dest = NULL;
    
        //打开两个文件
        if (-1 == (srcfd = open("./src.txt", O_RDONLY))) {
            printf("open src file error!\n");
            exit(1);
        }
    
        if (-1 == (destfd = open("./dest.txt", O_RDWR | O_CREAT | O_TRUNC))) {
            printf("open dest file error!\n");
            exit(1);
        }
    
        //获取原始文件的长度
        if (-1 == fstat(srcfd, &statbuf)) {
            printf("fstat src file error!\n");
            exit(1);
        }
    
        //设置输出文件的大小
        if (-1 == lseek(destfd, statbuf.st_size - 1, SEEK_SET)) {
            printf("lseek error!\n");
            exit(1);
        }
        if (-1 == write(destfd, "", 1)) {
            printf("write error!\n");
            exit(1);
        }
    
        if ((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, srcfd, 0)) == MAP_FAILED) {
            printf("mmaping src file error!\n");
            exit(1);
        }
    
        if ((dest = mmap(0, statbuf.st_size + 2, PROT_READ | PROT_WRITE, MAP_SHARED, destfd, 0)) == MAP_FAILED) {
            printf("mmaping dest file error!\n");
            exit(1);
        }
    
        memcpy(dest, src, statbuf.st_size);
    
        printf("src file:\n");
        system("cat ./src.txt");
        printf("dest file:\n");
        system("cat ./dest.txt");
    
        close(srcfd);
        close(destfd);
    
        return 0;
    }

按照上述列出的基本框架,该程序首先打开两个文件,通过fstat()获得源文件的长度。因为在mmap两个文件以及设置目的文件长度时都需要源文件的长度。设置目的文件通过lseek()即可完成,如果没有设置目的文件的长度,那么将会产生总线错误(引发信号SIGBUS)。然后分别mmap()两个文件到进程的地址空间,最后调用memcpy()将源文件内存区的数据拷贝到目的文件内存区。

通过基本I/O和内存映射I/O均可以进行文件拷贝,那么两者的效率谁更高一些?这其实是个很难回答的问题。不管是使用基本I/O操作函数还是mmap方式,操作系统都会在内存中进行缓存(cache),而且在不同的应用场景、不同的平台下结果都会收到影响。但是抛开这些因素单从对文件操作这个方面来说,内存映射方式比read和write方式要快。

如果使用read/write方式进行文件拷贝,首先将数据从用内核缓冲区复制到用户空间缓冲区,这是read的过程;再将数据从用户空间缓冲区复制到内核缓冲区,这是write过程。如果是内存映射方式,则直接是用户空间中数据的拷贝,也就是将源文件所映射内存中的数据拷贝到目的文件所映射的内存中。这样就避免了用户空间和内核空间之间数据的来回拷贝。

但是内存映射方式并不是完美的,它所映射的文件只能是固定大小,因为文件所映射的内存区域大小在mmap时通过len已经指定。另外,文件映射的内存区域的大小必须以页大小为单位。比如系统页大小为4096字节,假定映射文件的大小为20字节,那么该页剩余的4076字节全部被填充为0。虽然通过映射地址可以访问并修改剩余字节,但是任何变动都不会在映射文件中反应出来。由此可见,使用内存映射进行大数据量的拷贝比较有效。

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

  1. 高级I/O之存储映射I/O

    存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射.于是当从缓冲区中取数据,就相当于读文件中的相应字节.与此类似,将数据存入缓冲区,则相应字节就自动地写入 ...

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

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

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

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

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

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

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

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

  6. 存储映射--mmap

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

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

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

  8. 存储映射I/O函数

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

  9. 存储映射IO

    mmap 将文件映射到内存, 对这块内存的修改会自动同步到相应的文件中 void *mmap(void *addr, size_t len, int prot, int flag, int fd, o ...

随机推荐

  1. 如何查看ETW Trace?

    三种方法: LogParser Tractrpt - C:\Windows\System32 SvcTraceViewer.exe   后面的链接中有使用的详细步骤.   参考资料 ========= ...

  2. CSS结构和层叠

    每个合法的文档都会生成一个文档树,从而能根据元素的祖先,属性,兄弟元素等创建选择器来选择元素.有了这个结构树,选择器才能起作用,这也是CSS继承的核心.继承是从一个元素向其后代元素传递属性值所采用的机 ...

  3. URAL 1807

    题目大意:给出一个正整数n(n为合数),求n的一个划分(a1,a2,...,ak,...)(k>=2).使得其在存在最大的最大公约数之下,存在最大的最小公倍数. KB     64bit IO ...

  4. (转)[unity3d]easytouch的使用

    对于移动平台上的RPG类的游戏,我们常用虚拟摇杆来控制人物角色的行走和一些行为,相信我们对它并不陌生,之前尝试了EasyTouch2.5,发现并没有最新版的3.1好用,2.5版本的对于自适应没有做的很 ...

  5. 反汇编基本原理与x86指令构造

    反汇编基本原理与x86指令构造 概要:旨在讲述程序的二进制代码转换到汇编.即反汇编的基本原理.以及 x86 架构的 CPU 的指令构造,有这个基础后就能够自己编写汇编程序了,也能够将二进制代码数据转换 ...

  6. shell常用调试方法

    检查语法 -n选项只做语法检查,而不执行脚本. sh -n script_name.sh 启动调试 sh -x script_name.s 进入调试模式后,Shell依次执行读入的语句,产生的输出中有 ...

  7. Python中的乱码

        我把写好的Python脚本导入到ArcGIS中的ToolBox中,在本机测试是没有问题的.为了把工具分享给其他人,即在其他电脑上使用,我必须将脚本文件(*.py)导入到工具箱文件(*.tbx) ...

  8. rmdir 命令(转)

    原文:http://www.cnblogs.com/peida/archive/2012/10/27/2742076.html rmdir命令.rmdir是常用的命令,该命令的功能是删除空目录,一个目 ...

  9. 【Qt】splitter

    一段简单的切割窗体的程序: <span style="font-size:18px;">#include "mainwindow.h" #inclu ...

  10. Centering window on the screen

    The following script shows how we can center a window on the desktop screen. #!/usr/bin/python # -*- ...