【问题标题】:Sharing text file between processes with open() and mmap()使用 open() 和 mmap() 在进程之间共享文本文件
【发布时间】:2016-08-31 16:27:08
【问题描述】:

我正在尝试在我的 Ubuntu x86_64 上的分叉进程之间共享一个文本文件:该文件不会太大,因为只有在文件中没有另一个相同的字符串时才会写入字符串;字符串将是访问网站的主机名,因此我假设每个主机名不超过 255 个字节。

当轮到进程写入共享对象时,可以;一旦所有进程都写入共享对象,msync 应该使写入在磁盘上生效,但是创建的mapped.txt 文件只包含来自arrayString 的一个字符串,即最后一个进程写入共享对象的字符串。

代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <string.h>

// first forked process will write "first" in file, and so on
const char *arrayString[] = {
    "first", 
    "second",
    "third"
};

int main(void) {

    int index;
    int children = 3;
    const char *filepath = "mapped.txt";
    sem_t *sem;

    sem = sem_open("semaphore", O_CREAT | O_EXCL, 0644, 1);
    sem_unlink("semaphore");
    int fd;
    fd = open(filepath, O_RDWR | O_CREAT, 0644);
    if (fd < 0) {
        perror("open:");
        return EXIT_FAILURE;
    }

    char *data;
    data = (char *)mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (data == MAP_FAILED) {
        close(fd);
        perror("mmap:");
        return EXIT_FAILURE;
    }

    for (index=0; index<children; index++) {
        if (fork() == 0) {
            sem_wait(sem);

            size_t textsize = strlen(arrayString[index])+1;

            if (ftruncate(fd, sizeof(textsize)) == -1) {
                perror("ftruncate:");
                return EXIT_FAILURE;
            }

            for (size_t i = 0; i < textsize; i++) {
                printf("%d Writing character %c at %zu\n", getpid(), arrayString[index][i], i);
                data[i] = arrayString[index][i];
            }

            printf("%d wrote ", getpid());
            for (size_t i = 0; i < textsize; i++) {
                printf("%c", data[i]);
            }
            printf("\n");

            if (msync(data, textsize, MS_SYNC) == -1) {
                perror("Could not sync the file to disk");
            }

            sem_post(sem);
            _exit(EXIT_SUCCESS);
        }
    }
    close(fd);

    return EXIT_SUCCESS;
}     

这是三个子进程的上述代码的一种可能输出(这很好):

20373 Writing character s at 0
20373 Writing character e at 1
20373 Writing character c at 2
20373 Writing character o at 3
20373 Writing character n at 4
20373 Writing character d at 5
20373 Writing character  at 6
20373 wrote second
20374 Writing character t at 0
20374 Writing character h at 1
20374 Writing character i at 2
20374 Writing character r at 3
20374 Writing character d at 4
20374 Writing character  at 5
20374 wrote third
20372 Writing character f at 0
20372 Writing character i at 1
20372 Writing character r at 2
20372 Writing character s at 3
20372 Writing character t at 4
20372 Writing character  at 5
20372 wrote first

这是mapped.txt的内容(这很糟糕):

first^@^@^@

我预计:

second
third
first

但我得到的只是最后一个进程的字符串,带有那些奇怪的符号。我想将此文件持久保存在内存中,但由于 I/O 缓慢,我正在尝试使用内存映射。 知道为什么我的文件只包含访问共享文件的最后一个进程写入的字符串吗?

编辑:我想我明白了,它现在似乎工作了:我希望它对某人有所帮助。使用g++ -g -o mapthis mapthis.cpp -lrt -pthread 编译。请注意缺少一些错误检查,例如 fsyncsnprintflseek

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

const char *arrayString[] = {
    "www.facebook.com",
    "www.google.com",
    "www.cnn.com",
    "www.speechrepository.com",
    "www.youtube.com",
    "www.facebook.com",
    "www.google.com",
    "www.cnn.com",
    "www.speechrepository.com",
    "www.youtube.com",
    "www.facebook.com",
    "www.google.com",
    "www.cnn.com",
    "www.speechrepository.com",
    "www.youtube.com"
};

int main(void) {

    int index;
    int children = sizeof(arrayString) / sizeof(char*);;
    const char *filepath = "mapped.txt";
    sem_t *sem;
    char *data;
    struct stat filestats;

    sem = sem_open("semaphore", O_CREAT | O_EXCL, 0644, 1);
    sem_unlink("semaphore");
    int fd;
    fd = open(filepath, O_RDWR | O_CREAT, 0644);
    if (fd < 0) {
        perror("open:");
        return EXIT_FAILURE;
    }

    if (fstat(fd, &filestats) < 0) {
        close(fd);
        perror("fstat:");
        return EXIT_FAILURE;
    }

    data = (char *)mmap(NULL, filestats.st_size ? filestats.st_size : 1, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (data == MAP_FAILED) {
        close(fd);
        perror("first map:");
        return EXIT_FAILURE;
    }

    for (index=0; index<children; index++) {
        sleep(1);
        pid_t pid = fork();
        if (pid == 0) {
            int nw = 0;
            int hostnameSize = 0;
            const size_t origsize = filestats.st_size;
            char *hostPos = NULL;
            char *numPos = NULL;
            char *backslashPos = NULL;
            char tempBuff[64];
            memset((char *)tempBuff, 0, sizeof(tempBuff));
            sem_wait(sem);
            // remap to current file size if it changed
            fstat(fd, &filestats);
            // file empty, just insert
            if (filestats.st_size == 0) {
                nw = snprintf(tempBuff, sizeof(tempBuff), "%s %010lu\n", arrayString[index], (unsigned long)time(NULL));
                write(fd, tempBuff, nw);
                fsync(fd);
            }
            else {
                // file not empty, let's look for string
                hostPos = strstr(data, arrayString[index]);
                if (hostPos) {
                    // string is already inserted, search for offset of number of seconds
                    lseek(fd, hostPos-data, SEEK_SET);
                    numPos = strchr(hostPos, ' ')+1;
                    backslashPos = strchr(numPos, '\n');
                    long unsigned before = atoi(numPos);
                    long unsigned now = (unsigned long)time(NULL);
                    long unsigned difference = now - before;
                    printf("%s visited %ld seconds ago (%ld - %ld)\n", 
                        arrayString[index], difference, now, before);
                    nw = snprintf(tempBuff, backslashPos-hostPos+1, "%s %010lu", arrayString[index], now);
                    write(fd, tempBuff, nw);
                    write(fd, "\n", 1);
                    fsync(fd);
                }
                else {
                    data = (char *)mremap(data, origsize, filestats.st_size, MREMAP_MAYMOVE);
                    if (data == MAP_FAILED) {
                        close(fd);
                        sem_post(sem);
                        perror("mmap:");
                        _exit(EXIT_FAILURE);
                    }
                    lseek(fd, 0, SEEK_END);
                    nw = snprintf(tempBuff, sizeof(tempBuff), "%s %010lu\n", arrayString[index], (unsigned long)time(NULL));
                    write(fd, tempBuff, nw);
                    fsync(fd);
                }
            }
            munmap(data, filestats.st_size);
            close(fd);
            sem_post(sem);
            _exit(EXIT_SUCCESS);
        }
        else if (pid > 0) {
            wait(NULL);
        }
    }
    munmap(data, filestats.st_size);
    close(fd);

    return EXIT_SUCCESS;
}     

【问题讨论】:

    标签: c fork ipc mmap


    【解决方案1】:

    这行有问题:

    if (ftruncate(fd, sizeof(textsize)) == -1) {
    

    textsize 是一个size_t,取它的sizeof 只会得到 4 或 8 个(在 32 位和 64 位系统上)。看起来您使用的是 64 位系统,因此在这种情况下,您在每次写入之前都会无条件地将文件截断为 8 个字节。 “奇怪的符号”就是您的编辑器显示NUL/零字节的方式。即使你使用了ftruncate(fd, textsize),你仍然会截断你要写的字符串,覆盖其他孩子可能写的任何数据;我怀疑你想在这里ftruncate

    对于来自不同进程的持续追加(它们无法共享有关正在添加的数据的大小或偏移量的信息),内存映射没有意义;你为什么不让他们每个人都把锁,lseek 到文件末尾,然后打电话给write?您仍然可以使用内存映射进行重复检查(其中一些没有锁定),只是有点不同。像这样的:

    int main(void) {
        struct stat filestats;
        int index;
        int children = 3;
        const char *filepath = "mapped.txt";
        sem_t *sem;
        char *data;
    
        sem = sem_open("semaphore", O_CREAT | O_EXCL, 0644, 1);
        sem_unlink("semaphore");
        int fd;
        fd = open(filepath, O_RDWR | O_CREAT, 0644);
        if (fd < 0) {
            perror("open:");
            return EXIT_FAILURE;
        }
    
        // Mostly just to ensure it's mappable, we map the current size of the file
        // If the file might already have values, and many child workers won't add
        // to it, this might save some mapping work in the children; you could
        // just map in the children when needed though
        if (fstat(fd, &filestats) != 0) {
            close(fd);
            perror("fstat:");
            return EXIT_FAILURE;
        }
        data = mmap(NULL, filestats.st_size, PROT_READ, MAP_SHARED, fd, 0);
        if (data == MAP_FAILED) {
            close(fd);
            perror("mmap:");
            return EXIT_FAILURE;
        }
    
        for (index=0; index<children; index++) {
            if (fork() == 0) {
                const size_t origsize = filestats.st_size;
                sem_wait(sem);
    
                // remap to current file size if it changed
                // If you're not on Linux, you'd just have to mmap from scratch
                // since mremap isn't standard
                fstat(fd, &filestats);
                if (origsize != filestats.st_size) {
                    data = mremap(data, origsize, filestats.st_size, MREMAP_MAYMOVE);
                    if (data == MAP_FAILED) {
                        close(fd);
                        sem_post(sem);
                        perror("mmap:");
                        _exit(EXIT_FAILURE);
                    }
                }
    
                // Not safe to use strstr since mapping might not end with NUL byte
                // You'd need to workaround this, or implement a your own memstr-like function
                if (!memstr(data, arrayString[index])) {
                    // Move fd to end of file, so we append new data
                    lseek(fd, 0, SEEK_END);
                    write(fd, arrayString[index], strlen(arrayString[index]));
                    write(fd, "\n", 1);
                    fsync(fd);
                }
                munmap(data, filestats.st_size);
                close(fd);
                sem_post(sem);
                _exit(EXIT_SUCCESS);
            }
        }
        munmap(data, filestats.st_size);
        close(fd);
    
        return EXIT_SUCCESS;
    }
    

    我引用的 memstr 需要手动实现(或者您需要做一些糟糕的事情,例如确保文件末尾始终有一个 NUL 字节,以便您可以在其上使用 strstr) ;你可以得到一些关于here的提示。

    【讨论】:

    • 你的答案肯定值得测试,让我在我的代码上试试吧。
    • 原来你的答案是最好的:你对mmap的调用有invalid argument,因为我通过0作为size_t length,所以我第一次通过getpagesize();我用snprint写入一个临时缓冲区,然后writedata这个缓冲区中写入的字节数;现在我可以查看是否已在共享文件中插入了一个字符串。更新字符串仍然有困难:真正的结构是hostname time_t,即使我能得到time_t秒,我也需要写最后一次访问的新time_t秒,但那是另一回事。
    • @elmazzun:啊,是的,忘记了长度 0 在某些操作系统上被禁止使用;您可以轻松地使用 filestats.st_size ? filestats.st_size : 1 的长度(无论如何它都会映射整个页面,因为 mmap 就是这样操作的,并且无需检查实际页面大小)。
    • 还有一件事:我文件中的每一行都是string int;如果string 已经插入取int,计算time_t now = time(NULL)int 字段之间的差异,更新在int 字段中插入time_t now 以供将来访问和差异;当我在int 字段中写入新的time_t 值时,是否需要再次mremap 来更新int 字段?
    • @elmazzun:如果时间可能会在 2001 年 9 月上旬之前进入跨度,但不是在 2286 年 11 月中旬之后(以及是的,应该正确处理 2001 年 9 月之前的时间;有时计算机时钟会出错,如果发生这种情况,您不想损坏文件)。如果要处理超过 2286,请使用%011ld
    【解决方案2】:

    您正在文件的偏移量 0 处写入所有字符串,每个字符串都在前一个的顶部。循环的核心应该类似于

    struct stat status;
    fstat(fd, &status);
    size_t cursize = status.st_size;
    ftruncate(fd, cursize + textsize);
    for (size_t i = 0; i < textsize; i++) {
        data[cursize + i] = arrayString[index][i];
    }
    

    【讨论】:

    • 这个循环会在共享文件中写入first^@second^@third^@;如果我在arrayString 的每个字符串中添加\n,并在没有+1 的情况下计算size_t textsize,则文件将被完美格式化,每行一个字符串;字符串格式对我来说很重要,因为我需要解析这个文件中的字符串。
    • @elmazzun:在实现时,如果您的文件超过页面大小(您mmaped 的数量),映射将不够大。请参阅my answer 来处理这个问题(这意味着如果在 Linux 上使用mremap,或者只是推迟mmap 步骤,直到孩子在其他操作系统上持有信号量)。
    猜你喜欢
    • 1970-01-01
    • 2011-06-26
    • 2012-01-22
    • 2019-11-28
    • 2014-09-30
    • 1970-01-01
    • 2021-05-16
    • 2022-08-14
    • 1970-01-01
    相关资源
    最近更新 更多