代码之家  ›  专栏  ›  技术社区  ›  valdo

文件映射与文件系统同步

  •  -1
  • valdo  · 技术社区  · 8 年前

    我有一个包含一些数据的文件,它也是内存映射的。这样我既有文件描述符又有指向映射页的指针。大多数数据只从映射中读取,但最终也会被修改。

    这个数据结构是从不同的线程访问的,为了防止冲突,我同步了对它的访问(mutex和friends)。

    WriteFile 在windows上, write

    由于修改会更改文件大小,因此每次修改后都会重新初始化内存映射。也就是说,它是未映射的,然后再次映射(使用新大小)。

    现在,这在windows上可以正常工作,但在linux(确切地说是android)上,映射的数据最终会暂时不一致(即在重试时数据是可以的)。似乎它没有反映新添加的数据。

    sync , msync , syncfs

    提前谢谢。

    编辑:

    这是一个伪代码,演示了我正在处理的场景。

    struct CompressedGrid
    {
        mutex m_Lock;
        int m_FileHandle;    
        void* m_pMappedMemory;
    
        Hdr* get_Hdr() { return /* the mapped memory with some offset*/; }
    
        void SaveGridCell(int idx, const Cell& cCompressed)
        {
            AutoLock scope(m_Lock);
    
            // Write to mapped memory
            get_Hdr()->m_pCellOffset[Idx] = /* current end of file */;
    
            // Append the data
            lseek64(m_FileHandle, 0, FILE_END);
            write(m_FileHandle, cCompressed.pPtr, cCompressed.nSize);
    
            // re-map
            munmap(...);
            m_pMappedMemory = mmap(...); // specify the new file size of course
        }
    
        bool DecodeGridCell(int idx, Cell& cRaw)
        {
            AutoLock scope(m_Lock);
    
            uint64_t nOffs = get_Hdr()->m_pCellOffset[Idx] = /* ;
            if (!nOffs)
                return false; // unavail
    
            const uint8_t* p = m_pMappedMemory + nOffs;
    
            cRaw.DecodeFrom(p); // This is where the problem appears!
    
            return true;
        }
    
    1 回复  |  直到 8 年前
        1
  •  2
  •   Nominal Animal    8 年前

    使用 addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, offset)

    如果文件大小更改,请使用 newaddr = mremap(addr, len, newlen, MREMAP_MAYMOVE) 更新映射以反映它。要扩展文件,请使用 ftruncate(fd, newlen)

    你可以用 mprotect(addr, len, protflags) 更改映射中任何页上的保护(读/写)(两者必须在页边界上对齐)。您还可以通过 madvise()

    当您更改映射时,使用 msync(partaddr, partlen, MS_SYNC | MS_INVALIDATE) 或 msync(partaddr, partlen, MS_ASYNC | MS_INVALIDATE) 以确保 partlen 来自的字符 partaddr 转发对其他映射和文件读取器可见。如果你使用 MS_SYNC ,仅当更新完成时才返回调用。这个 MS_ASYNC MS_INVALIDATE 不执行任何操作;但如果存在,则告诉内核确保这些更改也反映在这些更改中。

    在2.6.19之后的Linux内核中, 异步 不执行任何操作,因为内核仍然正确地跟踪更改(不 msync() 是必要的,除了可能以前 munmap() ). 我不知道Android内核是否有改变这种行为的补丁;我想没有。为了在POSIXy系统中的可移植性,将它们保存在代码中仍然是一个好主意。

    好吧,除非你真的用 ,内核将在它认为最好的时候进行更新。

    因此,如果在继续之前需要对文件读取器可见一些更改,请使用 msync(areaptr, arealen, MS_SYNC | MS_INVALIDATE)

    如果你不在乎确切的时刻,用 msync(areaptr, arealen, MS_ASYNC | MS_INVALIDATE) . 对于当前的Linux内核,这是一个禁止操作的操作,但是为了便于移植(如果性能需要的话,可能会被注释掉)和提醒开发人员(缺少)同步期望是一个好主意。


    正如我对OP所评论的,我根本无法观察到Linux上的同步问题。(这并不意味着它不会在Android上发生,因为Android内核是 衍生产品 Linux内核,不完全相同。)

    我相信 msync() 在Linux内核上根本不需要调用,因为2.6.19版本,只要映射使用标志 MAP_SHARED | MAP_NORESERVE ,并且未使用 O_DIRECT 旗帜。这种观点的原因是,在这种情况下,映射和文件访问应该使用完全相同的页面缓存页。

    测试单c

    #define  _POSIX_C_SOURCE  200809L
    #define  _GNU_SOURCE
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <string.h>
    #include <stdio.h>
    #include <errno.h>
    
    static inline int read_from(const int fd, void *const to, const size_t len, const off_t offset)
    {
        char       *p = (char *)to;
        char *const q = (char *)to + len;
        ssize_t     n;
    
        if (lseek(fd, offset, SEEK_SET) != offset)
            return errno = EIO;
    
        while (p < q) {
            n = read(fd, p, (size_t)(q - p));
            if (n > 0)
                p += n;
            else
            if (n != -1)
                return errno = EIO;
            else
            if (errno != EINTR)
                return errno;
        }
    
        return 0;
    }
    
    static inline int write_to(const int fd, const void *const from, const size_t len, const off_t offset)
    {
        const char *const q = (const char *)from + len;
        const char       *p = (const char *)from;
        ssize_t           n;
    
        if (lseek(fd, offset, SEEK_SET) != offset)
            return errno = EIO;
    
        while (p < q) {
            n = write(fd, p, (size_t)(q - p));
            if (n > 0)
                p += n;
            else
            if (n != -1)
                return errno = EIO;
            else
            if (errno != EINTR)
                return errno;
        }
    
        return 0;
    }
    
    int main(int argc, char *argv[])
    {
        unsigned long  tests, n, merrs = 0, werrs = 0;
        size_t         page;
        long          *map, data[2];
        int            fd;
        char           dummy;
    
        if (argc != 3) {
            fprintf(stderr, "\n");
            fprintf(stderr, "Usage: %s FILENAME COUNT\n", argv[0]);
            fprintf(stderr, "\n");
            fprintf(stderr, "This program will test synchronization between a memory map\n");
            fprintf(stderr, "and reading/writing the underlying file, COUNT times.\n");
            fprintf(stderr, "\n");
            return EXIT_FAILURE;
        }
    
        if (sscanf(argv[2], " %lu %c", &tests, &dummy) != 1 || tests < 1) {
            fprintf(stderr, "%s: Invalid number of tests to run.\n", argv[2]);
            return EXIT_FAILURE;
        }
    
        /* Create the file. */
        page = sysconf(_SC_PAGESIZE);
        fd = open(argv[1], O_RDWR | O_CREAT | O_EXCL, 0644);
        if (fd == -1) {
            fprintf(stderr, "%s: Cannot create file: %s.\n", argv[1], strerror(errno));
            return EXIT_FAILURE;
        }
        if (ftruncate(fd, page) == -1) {
            fprintf(stderr, "%s: Cannot resize file: %s.\n", argv[1], strerror(errno));
            unlink(argv[1]);
            return EXIT_FAILURE;
        }
    
        /* Map it. */
        map = mmap(NULL, page, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, 0);
        if (map == MAP_FAILED) {
            fprintf(stderr, "%s: Cannot map file: %s.\n", argv[1], strerror(errno));
            unlink(argv[1]);
            close(fd);
            return EXIT_FAILURE;
        }
    
        /* Test loop. */
        for (n = 0; n < tests; n++) {
    
            /* Update map. */
            map[0] = (long)(n + 1);
            map[1] = (long)(~n);
    
            /* msync(map, 2 * sizeof map[0], MAP_SYNC | MAP_INVALIDATE); */
    
            /* Check the file contents. */
            if (read_from(fd, data, sizeof data, 0)) {
                fprintf(stderr, "read_from() failed: %s.\n", strerror(errno));
                munmap(map, page);
                unlink(argv[1]);
                close(fd);
                return EXIT_FAILURE;
            }
            werrs += (data[0] != (long)(n + 1) || data[1] != (long)(~n));
    
            /* Update data. */
            data[0] = (long)(n * 386131);
            data[1] = (long)(n * -257);
            if (write_to(fd, data, sizeof data, 0)) {
                fprintf(stderr, "write_to() failed: %s.\n", strerror(errno));
                munmap(map, page);
                unlink(argv[1]);
                close(fd);
                return EXIT_FAILURE;
            }
            merrs += (map[0] != (long)(n * 386131) || map[1] != (long)(n * -257));
        }
    
        munmap(map, page);
        unlink(argv[1]);
        close(fd);
    
        if (!werrs && !merrs)
            printf("No errors detected.\n");
        else {
            if (!werrs)
                printf("Detected %lu times (%.3f%%) when file contents were incorrect.\n",
                       werrs, 100.0 * (double)werrs / (double)tests);
            if (!merrs)
                printf("Detected %lu times (%.3f%%) when mapping was incorrect.\n",
                       merrs, 100.0 * (double)merrs / (double)tests);
        }
    
        return EXIT_SUCCESS;
    }
    

    使用例如。

    gcc -Wall -O2 test-single -o single
    ./single temp 1000000
    

    要测试一百万次,当两个访问都在同一个进程中完成时,映射和文件内容是否保持同步。请注意 msync()

    我机器上的测试速度大约是每秒55万次。请注意,每个测试都是双向的,所以包括读和写。我只是不能让它检测出任何错误。它的写作对错误也相当敏感。

    第二个测试程序使用两个子进程和一个POSIX实时信号告诉另一个进程检查内容。 测试multi.c

    #define  _POSIX_C_SOURCE  200809L
    #define  _GNU_SOURCE
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <string.h>
    #include <stdio.h>
    #include <errno.h>
    
    #define  NOTIFY_SIGNAL  (SIGRTMIN+0)
    
    int mapper_process(const int fd, const size_t len)
    {
        long       value = 1, count[2] = { 0, 0 };
        long      *data;
        siginfo_t  info;
        sigset_t   sigs;
        int        signum;
    
        if (fd == -1) {
            fprintf(stderr, "mapper_process(): Invalid file descriptor.\n");
            return EXIT_FAILURE;
        }
    
        data = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0);
        if (data == MAP_FAILED) {
            fprintf(stderr, "mapper_process(): Cannot map file.\n");
            return EXIT_FAILURE;
        }
    
        sigemptyset(&sigs);
        sigaddset(&sigs, NOTIFY_SIGNAL);
        sigaddset(&sigs, SIGINT);
        sigaddset(&sigs, SIGHUP);
        sigaddset(&sigs, SIGTERM);
    
        while (1) {
            /* Wait for the notification. */
            signum = sigwaitinfo(&sigs, &info);
            if (signum == -1) {
                if (errno == EINTR)
                    continue;
                fprintf(stderr, "mapper_process(): sigwaitinfo() failed: %s.\n", strerror(errno));
                munmap(data, len);
                return EXIT_FAILURE;
            }
            if (signum != NOTIFY_SIGNAL)
                break;
    
            /* A notify signal was received. Check the write counter. */
            count[ (data[0] == value) ]++;
    
            /* Update. */
            data[0] = value++;
            data[1] = -(value++);
    
            /* Synchronize */
            /* msync(data, 2 * sizeof (data[0]), MS_SYNC | MS_INVALIDATE); */
    
            /* And let the writer know. */
            kill(info.si_pid, NOTIFY_SIGNAL);
        }
    
        /* Print statistics. */
        printf("mapper_process(): %lu errors out of %lu cycles (%.3f%%)\n",
               count[0], count[0] + count[1], 100.0 * (double)count[0] / (double)(count[0] + count[1]));
        fflush(stdout);
    
        munmap(data, len);
        return EXIT_SUCCESS;
    }
    
    static inline int read_from(const int fd, void *const to, const size_t len, const off_t offset)
    {
        char       *p = (char *)to;
        char *const q = (char *)to + len;
        ssize_t     n;
    
        if (lseek(fd, offset, SEEK_SET) != offset)
            return errno = EIO;
    
        while (p < q) {
            n = read(fd, p, (size_t)(q - p));
            if (n > 0)
                p += n;
            else
            if (n != -1)
                return errno = EIO;
            else
            if (errno != EINTR)
                return errno;
        }
    
        return 0;
    }
    
    static inline int write_to(const int fd, const void *const from, const size_t len, const off_t offset)
    {
        const char *const q = (const char *)from + len;
        const char       *p = (const char *)from;
        ssize_t           n;
    
        if (lseek(fd, offset, SEEK_SET) != offset)
            return errno = EIO;
    
        while (p < q) {
            n = write(fd, p, (size_t)(q - p));
            if (n > 0)
                p += n;
            else
            if (n != -1)
                return errno = EIO;
            else
            if (errno != EINTR)
                return errno;
        }
    
        return 0;
    }
    
    int writer_process(const int fd, const size_t len, const pid_t other)
    {
        long       data[2] = { 0, 0 }, count[2] = { 0, 0 };
        long       value = 0;
        siginfo_t  info;
        sigset_t   sigs;
        int        signum;
    
        sigemptyset(&sigs);
        sigaddset(&sigs, NOTIFY_SIGNAL);
        sigaddset(&sigs, SIGINT);
        sigaddset(&sigs, SIGHUP);
        sigaddset(&sigs, SIGTERM);
    
        while (1) {
    
            /* Update. */
            data[0] = ++value;
            data[1] = -(value++);
    
            /* then write the data. */
            if (write_to(fd, data, sizeof data, 0)) {
                fprintf(stderr, "writer_process(): write_to() failed: %s.\n", strerror(errno));
                return EXIT_FAILURE;
            }
    
            /* Let the mapper know. */
            kill(other, NOTIFY_SIGNAL);
    
            /* Wait for the notification. */        
            signum = sigwaitinfo(&sigs, &info);
            if (signum == -1) {
                if (errno == EINTR)
                    continue;
                fprintf(stderr, "writer_process(): sigwaitinfo() failed: %s.\n", strerror(errno));
                return EXIT_FAILURE;
            }
            if (signum != NOTIFY_SIGNAL || info.si_pid != other)
                break;
    
            /* Reread the file. */
            if (read_from(fd, data, sizeof data, 0)) {
                fprintf(stderr, "writer_process(): read_from() failed: %s.\n", strerror(errno));
                return EXIT_FAILURE;
            }
    
            /* Check the read counter. */
            count[ (data[1] == -value) ]++;
        }
    
        /* Print statistics. */
        printf("writer_process(): %lu errors out of %lu cycles (%.3f%%)\n",
               count[0], count[0] + count[1], 100.0 * (double)count[0] / (double)(count[0] + count[1]));
        fflush(stdout);
    
        return EXIT_SUCCESS;
    }
    
    int main(int argc, char *argv[])
    {
        struct timespec  duration;
        double           seconds;
        pid_t            mapper, writer, p;
        size_t           page;
        siginfo_t        info;
        sigset_t         sigs;
        int              fd, status;
        char             dummy;
    
        if (argc != 3) {
            fprintf(stderr, "\n");
            fprintf(stderr, "Usage: %s FILENAME SECONDS\n", argv[0]);
            fprintf(stderr, "\n");
            fprintf(stderr, "This program will test synchronization between a memory map\n");
            fprintf(stderr, "and reading/writing the underlying file.\n");
            fprintf(stderr, "The test will run for the specified time, or indefinitely\n");
            fprintf(stderr, "if SECONDS is zero, but you can also interrupt it with\n");
            fprintf(stderr, "Ctrl+C (INT signal).\n");
            fprintf(stderr, "\n");
            return EXIT_FAILURE;
        }
    
        if (sscanf(argv[2], " %lf %c", &seconds, &dummy) != 1) {
            fprintf(stderr, "%s: Invalid number of seconds to run.\n", argv[2]);
            return EXIT_FAILURE;
        }
        if (seconds > 0) {
            duration.tv_sec = (time_t)seconds;
            duration.tv_nsec = (long)(1000000000 * (seconds - (double)(duration.tv_sec)));
        } else {
            duration.tv_sec = 0;
            duration.tv_nsec = 0;
        }
    
        /* Block INT, HUP, CHLD, and the notification signal. */
        sigemptyset(&sigs);
        sigaddset(&sigs, SIGINT);
        sigaddset(&sigs, SIGHUP);
        sigaddset(&sigs, SIGCHLD);
        sigaddset(&sigs, NOTIFY_SIGNAL);
        if (sigprocmask(SIG_BLOCK, &sigs, NULL) == -1) {
            fprintf(stderr, "Cannot block the necessary signals: %s.\n", strerror(errno));
            return EXIT_FAILURE;
        }
    
        /* Create the file. */
        page = sysconf(_SC_PAGESIZE);
        fd = open(argv[1], O_RDWR | O_CREAT | O_EXCL, 0644);
        if (fd == -1) {
            fprintf(stderr, "%s: Cannot create file: %s.\n", argv[1], strerror(errno));
            return EXIT_FAILURE;
        }
        if (ftruncate(fd, page) == -1) {
            fprintf(stderr, "%s: Cannot resize file: %s.\n", argv[1], strerror(errno));
            unlink(argv[1]);
            return EXIT_FAILURE;
        }
        close(fd);
        fd = -1;
    
        /* Ensure streams are flushed before forking. They should be, we're just paranoid here. */
        fflush(stdout);
        fflush(stderr);
    
        /* Fork the mapper child process. */
        mapper = fork();
        if (mapper == -1) {
            fprintf(stderr, "Cannot fork mapper child process: %s.\n", strerror(errno));
            unlink(argv[1]);
            return EXIT_FAILURE;
        }
        if (!mapper) {
            fd = open(argv[1], O_RDWR);
            if (fd == -1) {
                fprintf(stderr, "mapper_process(): %s: Cannot open file: %s.\n", argv[1], strerror(errno));
                return EXIT_FAILURE;
            }
            status = mapper_process(fd, page);
            close(fd);
            return status;
        }
    
        /* For the writer child process. (mapper contains the PID of the mapper process.) */
        writer = fork();
        if (writer == -1) {
            fprintf(stderr, "Cannot fork writer child process: %s.\n", strerror(errno));
            unlink(argv[1]);
            kill(mapper, SIGKILL);
            return EXIT_FAILURE;
        }
        if (!writer) {
            fd = open(argv[1], O_RDWR);
            if (fd == -1) {
                fprintf(stderr, "writer_process(): %s: Cannot open file: %s.\n", argv[1], strerror(errno));
                return EXIT_FAILURE;
            }
            status = writer_process(fd, page, mapper);
            close(fd);
            return status;
        }
    
        /* Wait for a signal. */
        if (duration.tv_sec || duration.tv_nsec)
            status = sigtimedwait(&sigs, &info, &duration);
        else
            status = sigwaitinfo(&sigs, &info);
    
        /* Whatever it was, we kill the child processes. */
        kill(mapper, SIGHUP);
        kill(writer, SIGHUP);
        do {
            p = waitpid(-1, NULL, 0);
        } while (p != -1 || errno == EINTR);
    
        /* Cleanup. */
        unlink(argv[1]);
    
        printf("Done.\n");                 
        return EXIT_SUCCESS;
    }
    

    注意,子进程单独打开临时文件。要编译和运行,请使用。

    gcc -Wall -O2 test-multi.c -o multi
    ./multi temp 10
    

    第二个参数是测试的持续时间,以秒为单位。(您可以使用SIGINT安全地中断测试( Ctrl键 + )或者叹气。)

    在我的机器上,测试速度大约是每秒120000次测试 这里还注释了调用,因为即使没有它,我也看不到任何错误/取消同步。(另外, msync(ptr, len, MS_SYNC) 和 msync(ptr, len, MS_SYNC | MS_INVALIDATE)

    这个 MAP_NORESERVE