内存映射文件

4
我写了一段代码,用于将内容写入通过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个回答

21
如果您希望在磁盘文件中反映出您的更改,您必须将文件作为“MAP_SHARED”而不是“MAP_PRIVATE”映射。
此外,您不能仅通过写入超出映射结尾来扩展文件。您必须使用“ftruncate()”将文件扩展到新大小,然后更改映射以包括文件的新部分。更改映射的便携式方法是取消映射映射,然后使用新大小重新创建它;在Linux上,您可以使用“mremap()”代替。
您的“len”和“len_file”变量应该是“size_t”类型,您应该使用“memcpy()”而不是“strcat()”,因为您知道字符串的长度,知道要复制到哪里,并且不想复制空终止符。
下面对您的代码进行了修改,在Linux上可以工作(使用“mremap()”)。
#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;
}

这里不是缺少了调用close(fd)的语句吗? - Ryan
1
@Ryan:是的-我只是对问题中的代码进行了最小的更改,但你是正确的-应该调用close()以便我们可以检查错误返回。 - caf

2
请注意,您提供的文件映射大小与文件大小完全相同。如果您在调用open(2)时创建文件,则其长度将为0,如果内核不设置任何类型的从0长度映射的内存映射,我不会感到惊讶。(也许它会?我从未尝试过......)
我建议在执行映射之前使用ftruncate(2)扩展文件的长度。(请注意,使用ftruncate(2)扩展文件不是非常可移植;并非所有平台都提供扩展功能,也不是所有文件系统驱动程序都支持扩展功能。有关详细信息,请参见您系统的man页面。)
您必须使用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)函数期望在dest字符串的末尾找到ASCII NUL字符(字节值0x00,C表示为'\0'),这通常是C语言中的字符串结尾标记。 如果您在此程序中创建文件,则该文件将不包含ASCII NUL,因为它的长度为零,而我不知道通过mmap(2)尝试读取零字节文件的后果。 如果文件已经存在并且其中有数据,则可能没有ASCII NUL编码在文件中。 strcat(3)几乎肯定不是向文件中写入内容的正确工具。 (毕竟,谁都不希望在自己的文件中出现ASCII NUL)请尝试使用memcpy(3)代替。


来自匿名评审员的评论:原作者混淆了“文件是仅追加”和“fd以追加模式打开”。例如,在shell中运行chattr +a THEFILE会使文件变为仅追加。 O_APPEND决定了写系统调用的行为,但不影响内存映射。--已编辑以删除MAP_SHARED位。 :) - sarnold

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接