存储映射I/O

一个进程拥有独立并且连续虚拟地址空间,在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。虽然通过映射地址可以访问并修改剩余字节,但是任何变动都不会在映射文件中反应出来。由此可见,使用内存映射进行大数据量的拷贝比较有效。

原文地址:https://www.cnblogs.com/alantu2018/p/8482923.html

时间: 2024-10-17 17:42:32

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

mmap存储映射IO

mmap,munmap-- #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length); int mprotect(void *addr, size_t len, int prot); int msync(void *addr, size_t length, int fla

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

8. 高级文件操作:存储映射 (1)概念: 存储映射是一个磁盘文件与存储空间的一个缓存相映射,对缓存数据的读写就相应的完成了文件的读写. (2)mmap和munmap函数 头文件 #include<sys/types.h> #include<sys/mman.h> 函数 void* mmap(void* addr, size_t length, int prot, int flags,                       int fd, off_t offset); int

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

存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射,对缓冲区的读.写操作就是对文件的读.写操作,从而可以不再使用read.write系统调用. 将文件映射到存储区的函数由mmap完成,函数原型如下: #include <sys/mman.h> /* 成功返回映射区起始地址,出错返回MAP_FAILED */ void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off); 参数说明: addr

存储映射IO

3.6.7.1.mmap函数3.6.7.2.LCD显示和IPC之共享内存3.6.7.3.存储映射IO的特点(1)共享而不是复制,减少内存操作(2)处理大文件时效率高,小文件不划算 总结:存储映射IO其实本质上就是共享内存,和进程间通信的IPC共享内存是一样的,区 别在于: 存储映射IO:是访问设备文件的说法,例如访问LCD设备,我们的进程是一个视频播放器 ,那么他需要将硬盘中的视频数据读取到内存中,然后再把内存中的数据拷贝到LCD驱动 维护的显存中去,之后硬件会自动实现视频的显示:这个是一般的流

八、文件IO——存储映射

8.1 存储映射介绍 8.1.1 概念 存储映射是一个磁盘文件与存储空间的一个缓存相映射,对缓存数据的读写就相应的完成了文件的读写. 文件操作部分映射到虚拟内存的一块区域,我们对虚拟内存映射的那块区域进行读写操作,读写之后,那块区域自动同步到文件当中. 4G空间分布: 共享内存映射区就是文件映射到的内存区. 8.1.2 mmap---建立内存映射 1 #include <unistd.h> 2 #include <sys/mman.h> 3 //mmap(建立内存映射) 4 voi

存储映射--mmap

存储映射 使一个磁盘文件与存储空间中的一个缓冲区相映射. 当从缓冲区中取数据,就相当于读文件中的相应字节. 将数据存入缓冲区,则相应的字节就自动写入文件. 使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中.这个映射工作可以通过mmap函数来实现.不通过IO.直接操作内存,效率更高. mmap函数 函数原型 #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags,int fd, o

Duanxx的STM32学习: STM32的存储映射

Lenovo DS存储Linux下ISCSI 多路径映射配置

Lenovo  DS存储Linux下ISCSI 多路径映射配置 Renren  2018-01-17 实验环境: DS4200存储,B控制器池划分一个大小为18G的volume,通过两个1G ISCSI口port2和port3同时映射给客户机.Port B2口IP为172.16.1.14/24 :port B3口IP为172.16.1.15/24. 客户机系统为centos 6.9,两个以太网口,eth0口IP为172.16.1.195/24,eth1口的IP为172.16.1.196/24.网

数据对象映射模式

定义:将对象和数据存储映射起来,对一个对象的操作会映射为数据存储的操作 综合应用实例:在代码中实现数据对象映射模式,我们将实现一个ORM类,将复杂的SQL语句映射成对象属性的操作,结合使用数据对象映射模式,工程模式,注册模式 $page = new Page();$page->index(); class Page{ function index() { $user = \IMooc\Factory::getUser(1); $user->name= 'fango0'; $this->t