内存映射文件

时间:2011-12-07 07:19:34

标签: c unix mmap memory-mapped-files

我编写了一个代码,用于将内容写入映射缓冲区,该缓冲区使用mmap()系统调用进行映射。 在我对映射缓冲区进行了一些更改后,我调用了msync()。它应该更新到磁盘上的文件。

但是,它没有对磁盘上的文件进行任何更改。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h> 
#include <sys/types.h>
#include <sys/stat.h>
#include<sys/mman.h>
#include<fcntl.h>
#define FILEMODE S_IRWXU | S_IRGRP | S_IROTH
#define MAX 150

main(int argc,char *argv[])
{
int fd,ret,len;
long int len_file;
struct stat st;
char *addr;
char buf[MAX];


if(argc > 1)
{
    if((fd = open(argv[1],O_RDWR | O_APPEND | O_CREAT ,FILEMODE)) < 0)
        perror("Error in file opening");

    if((ret=fstat(fd,&st)) < 0)
        perror("Error in fstat");

    len_file = st.st_size;

           /*len_file having the total length of the file(fd).*/


    if((addr=mmap(NULL,len_file,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0)) == MAP_FAILED)
        perror("Error in mmap");

    len = len_file; 

    while((fgets(buf,MAX,stdin)) != NULL)
    {
        strcat(addr+len,buf);
        printf( "Val:%s\n",addr ) ; //Checking purpose
        len = len + (strlen(buf));
    }
    if((msync(addr,len,MS_SYNC)) < 0)
        perror("Error in msync");

    if( munmap(addr,len) == -1)
        printf("Error:\n");
    printf("addr %p\n",addr);
}
else
{
    printf("Usage a.out <filename>\n");
}
}

2 个答案:

答案 0 :(得分:17)

如果您希望更改反映在磁盘文件中,则必须将文件映射为MAP_SHARED,而不是MAP_PRIVATE

此外,您无法通过写入超出映射的末尾来扩展文件。您必须使用ftruncate()将文件扩展为新大小,然后更改映射以包含文件的新部分。更改映射的可移植方法是取消映射映射,然后使用新大小重新创建映射;在Linux上,您可以使用mremap()

您的lenlen_file变量应该是size_t类型,并且您应该使用memcpy()而不是strcat(),因为您确切知道mremap()的长度字符串,您要复制它的确切位置,并且想要复制空终止符。

以下修改代码适用于Linux(使用#define _GNU_SOURCE #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include <sys/types.h> #include <sys/stat.h> #include<sys/mman.h> #include<fcntl.h> #define FILEMODE S_IRWXU | S_IRGRP | S_IROTH #define MAX 150 int main(int argc,char *argv[]) { int fd, ret; size_t len_file, len; struct stat st; char *addr; char buf[MAX]; if (argc < 2) { printf("Usage a.out <filename>\n"); return EXIT_FAILURE; } if ((fd = open(argv[1],O_RDWR | O_CREAT, FILEMODE)) < 0) { perror("Error in file opening"); return EXIT_FAILURE; } if ((ret = fstat(fd,&st)) < 0) { perror("Error in fstat"); return EXIT_FAILURE; } len_file = st.st_size; /*len_file having the total length of the file(fd).*/ if ((addr = mmap(NULL,len_file,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED) { perror("Error in mmap"); return EXIT_FAILURE; } while ((fgets(buf,MAX,stdin)) != NULL) { len = len_file; len_file += strlen(buf); if (ftruncate(fd, len_file) != 0) { perror("Error extending file"); return EXIT_FAILURE; } if ((addr = mremap(addr, len, len_file, MREMAP_MAYMOVE)) == MAP_FAILED) { perror("Error extending mapping"); return EXIT_FAILURE; } memcpy(addr+len, buf, len_file - len); printf( "Val:%s\n",addr ) ; //Checking purpose } if((msync(addr,len,MS_SYNC)) < 0) perror("Error in msync"); if (munmap(addr,len) == -1) perror("Error in munmap"); if (close(fd)) perror("Error in close"); return 0; } ):

{{1}}

答案 1 :(得分:2)

请注意,您已为文件提供了与文件大小完全相同的映射。如果你在调用open(2)时创建文件,它的长度为0,如果内核不打扰设置任何类型的内存映射,我也不会感到惊讶0长度映射。 (也许它确实如此?我从未尝试过......)

我建议在执行映射之前使用ftruncate(2)扩展文件的长度。 (请注意,使用ftruncate(2)扩展文件不是非常便携;并非所有平台都提供扩展功能,并非所有文件系统驱动程序都支持扩展功能。有关详细信息,请参阅系统的联机帮助页。)

您必须使用MAP_SHARED映射将文件修改保存到磁盘。

您对perror(3)的使用并不完全正确; perror(3)终止您的程序,因此会以错误的假设继续执行:

if((ret=fstat(fd,&st)) < 0)
    perror("Error in fstat");

应阅读:

if((ret=fstat(fd,&st)) < 0) {
    perror("Error in fstat");
    exit(1);
}

(或exit(EXIT_FAILURE)如果你想要更便携 - 我觉得眼睛有点难,但我住在Linux版本。)

strcat(3)期望找到一个ASCII NUL字符(字节值0x00,C表示'\0') - 通常的C字符串结尾标记 - 在结尾处dest字符串。如果你在这个程序中创建它,你的文件将不包含ASCII NUL--毕竟它的长度为零 - 我不知道尝试通过mmap(2)读取零字节文件的后果。如果文件已存在并且其中包含数据,则可能在文件中没有编码的ASCII NUL。 strcat(3)几乎肯定是写入文件的错误工具。 (无论如何,没有人想要在他们的文件中使用ASCII NUL。)请尝试memcpy(3)