ホーム>source

いくつかのデータを含むファイルがあり、これもメモリマップされています。ファイル記述子とマップされたページへのポインターの両方があるように。ほとんどの場合、データはマッピングからのみ読み取られますが、最終的には変更されます。

変更は、ファイル内の一部のデータの変更(ヘッダーの更新の一種)に加えて、いくつかの新しいデータの追加(つまり、ファイルの現在の最後のポストへの書き込み)で構成されます。

このデータ構造は異なるスレッドからアクセスされ、衝突を防ぐために、データへのアクセスを同期します(ミューテックスとフレンド)。

変更時には、ファイルマッピングとファイル記述子の両方を使用します。ヘッダーは、マップされたメモリを変更することで暗黙的に更新されますが、新しいデータは適切なAPI( WriteFile  Windowsでは、 write  posixで)。新しいデータとヘッダーは異なるページに属していることに注意してください。

変更によりファイルサイズが変更されるため、このような変更が行われるたびにメモリマッピングが再初期化されます。つまり、マッピングが解除され、再度マッピングされます(新しいサイズで)。

マップされたメモリへの書き込みは「非同期」のwrtファイルシステムであり、順序は保証されないことを認識していますが、ファイルマッピングを明示的に閉じて(IMHO)一種のフラッシュポイントとして機能するため、問題はないと思った。

現在、これはWindowsでは問題なく動作しますが、Linux(正確にはアンドロイド)では、最終的にマップされたデータは一時的に不整合になります(つまり、再試行時にデータは問題ありません)。新しく追加されたデータを反映していないようです。

適切にフラッシュされた場合、データを確実にするために同期APIを呼び出す必要がありますか?その場合、どちらを使用する必要があります: syncmsyncsyncfs  または何か違う?

前もって感謝します。

編集:

これは、私が扱っているシナリオを説明する擬似コードです。 (実際のコードはもちろんより複雑です)

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

    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 からの文字  forwardは、他のマッピングおよびファイルリーダーに表示されます。 partaddr を使用する場合 、呼び出しは更新が完了したときにのみ戻ります。ザ・ウィズウィズ  呼び出しは、カーネルに更新を行うよう指示しますが、完了するまで待機しません。ファイルの他のメモリマップがない場合、 MS_SYNC  何もしません。しかし、もしあれば、変更がそれらにも反映されるようにカーネルに伝えます。

    2.6.19以降のLinuxカーネルでは、 MS_ASYNC  とにかくカーネルが変更を適切に追跡するため、何もしません( MS_INVALIDATE はありません  おそらく MS_ASYNC の前を除き、必要です )。 Androidカーネルにその動作を変更するパッチがあるかどうかはわかりません。私はそうは思わない。 POSIXyシステム間での移植性を確保するために、これらをコード内に保持することをお勧めします。

    msync()

    さて、あなたが munmap() を使用しない限り 、カーネルは最適と判断したときに更新を行います。

    そのため、続行する前にファイルリーダーに変更を表示する必要がある場合は、

    mapped data turns-out to be inconsistent temporarily

    を使用します  それらの更新を行うプロセスで。

    正確な瞬間を気にしない場合は、 msync(partaddr, partlen, MS_SYNC | MS_INVALIDATE) を使用してください 。現在のLinuxカーネルでは無操作になりますが、移植性を保つため(パフォーマンスのために必要に応じてコメントアウトされる可能性があります)、開発者に同期の期待(不足)を思い出させることをお勧めします。


    OPにコメントしたように、Linuxでの同期の問題はまったく観察できません。 (それは、Androidカーネルがデリバティブ Linuxカーネルの、まったく同じではありません。)

    私は msync(areaptr, arealen, MS_SYNC | MS_INVALIDATE) を信じています  マッピングがフラグ msync(areaptr, arealen, MS_ASYNC | MS_INVALIDATE) を使用する限り、2.6.19以降のLinuxカーネルでは呼び出しは不要です。 、および基になるファイルは msync() を使用して開かれません  国旗。この考えの理由は、この場合、マッピングとファイルアクセスの両方でまったく同じページキャッシュページを使用する必要があるためです。

    Linuxでこれを調べるために使用できる2つのテストプログラムを次に示します。まず、単一プロセスのテスト、test-single.c

    MAP_SHARED | MAP_NORESERVE
    
    

    を使用してコンパイルおよび実行します

    O_DIRECT
    
    

    同じプロセスで両方のアクセスが行われたときに、マッピングとファイルの内容が同期したままであるかどうかを、100万回テストします。 #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; }  私のマシンでは必要ないので、呼び出しはコメントアウトされています:テストなしでテスト中にエラー/非同期が発生することはありません。

    私のマシンでのテスト率は、1秒あたり約550,000テストです。各テストは両方の方法で実行されるため、読み取りと書き込みが含まれることに注意してください。これを取得してエラーを検出することはできません。エラーにも非常に敏感になるように書かれています。

    2番目のテストプログラムは、2つの子プロセスとPOSIXリアルタイムシグナルを使用して、他のプロセスに内容を確認するよう指示します。test-multi.c

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

    子プロセスは一時ファイルを個別に開くことに注意してください。コンパイルして実行するには、例えば

    msync()
    
    

    2番目のパラメーターは、テストの期間(秒単位)です。 (SIGINTを使用してテストを安全に中断できます(Ctrl+C)またはSIGHUP。)

    私のマシンでは、テストレートは1秒あたり約120,000テストです。 #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  および msync()  恐ろしく遅いです。どちらの場合でも、1秒あたり1000未満のテストしか取得できず、結果にまったく違いはありません。それは100倍の減速です。)

    ザ・ウィズウィズ  mmapのフラグは、スワップではなく、メモリの負荷がかかっているときにファイル自体をバッキングストレージとして使用するように指示します。そのフラグを認識しないシステムでコードをコンパイルする場合は、省略できます。マッピングがRAMから削除されない限り、フラグは操作にまったく影響しません。

    msync(ptr, len, MS_SYNC)

  • 前へ java - JPAクエリ:サブクエリをグループ化条件に結合する
  • 次へ Spring MVCでのAjax呼び出しの処理