18

メモリ マップド IO を使用する可能性に興味があります。できれば、boost::interprocess の機能をクロスプラットフォーム サポートに利用して、ファイル内の連続していないシステム ページ サイズのブロックをメモリ内の連続したアドレス空間にマップすることに興味があります。

単純化された具体的なシナリオ:

私はいくつかの「plain-old-data」構造を持っています。それぞれが固定長 (システム ページ サイズよりも小さい) です。これらの構造は、構造のタイプと場所によって決定される (非常に長い) ストリームに連結されます。ストリーム内でそれらを進める構造の値。要求の厳しい同時実行環境で、待ち時間を最小限に抑え、スループットを最大化することを目指しています。

このデータをシステム ページ サイズの少なくとも 2 倍のブロックにメモリ マッピングし、最後から 2 番目のシステム ページ境界を超えて拡張された構造を読み取った新しいマッピングをすぐに確立することで、このデータを非常に効果的に読み取ることができます。これにより、単純な古いデータ構造とやり取りするコードは、これらの構造がメモリ マップされていることをまったく意識せずに済みます。たとえば、ページ境界を気にすることなく、直接 memcmp() を使用して 2 つの異なる構造を比較できます。

興味深いのは、これらのデータストリームを更新することです...それらが(同時に)読み取られている間。私が使用したい戦略は、システムページサイズの粒度での「コピーオンライト」に触発されています...本質的に「オーバーレイページ」を書き込みます-あるプロセスが古いデータを読み取り、別のプロセスが更新されたデータを読み取ることができます。

どのオーバーレイ ページをいつ使用するかを管理することは、必ずしも簡単なことではありませんが、それは私の主な関心事ではありません。私の主な懸念は、ページ 4 と 5 にまたがる構造を持っている可能性があることです。次に、ページ 5 に完全に含まれる構造を更新します...新しいページを場所 6 に書き込みます...到達不能と判断されました。つまり、ページ 4 を場所 M にマップする場合、既存の (non-memory-mapping-認識) 機能。

私は最善の戦略を確立しようとしていますが、文書が不完全であることに妨げられています。基本的に、アドレス空間の割り当てをメモリ マッピングからそのアドレス空間に分離する必要があります。mmap() を使用すると、MAP_FIXED を使用できることを認識しています-マッピングの場所を明示的に制御したい場合...しかし、これを安全に行うためにアドレス空間を予約する方法がわかりません。/dev/zero を MAP_FIXED なしで 2 つのページにマップし、次に MAP_FIXED を 2 回使用して、明示的な VM アドレスで割り当てられたスペースに 2 つのページをマップできますか? その場合、munmap() も 3 回呼び出す必要がありますか? リソースがリークしたり、その他の厄介なオーバーヘッドが発生したりしますか? 問題をさらに複雑にするために、Windows でも同等の動作が必要です... これを行う方法はありますか?クロスプラットフォームの野望を妥協する場合、適切な解決策はありますか?

--

あなたの答えをありがとう、マフムード...私は読んだ、そして私はそのコードを理解したと思う...私はそれをLinuxでコンパイルし、あなたが提案したように動作します。

私の主な関心事は 62 行目です - MAP_FIXED を使用しています。これは、mmap についていくつかの前提を置いていますが、私が見つけたドキュメントを読んでも確認できませんでした。「更新」ページを最初に返された mmap() と同じアドレス空間にマッピングしています - これは「正しい」と思います - つまり、たまたま Linux で動作するものではありませんか? また、ファイルマッピングと匿名マッピングのクロスプラットフォームで機能すると仮定する必要があります。

サンプルは間違いなく私を前進させます...私が最終的に必要とするものは、おそらく Linux の mmap() で達成可能であることを文書化しています - 少なくとも。私が本当に欲しいのは、サンプルが示すように MAP_FIXED 行が機能することを示すドキュメントへのポインターです...そして、理想的には、Linux/Unix 固有の mmap() からプラットフォームに依存しない (Boost::interprocess ) アプローチ。

4

3 に答える 3

7

あなたの質問は少し紛らわしいです。私が理解したことから、このコードはあなたが必要とすることをします:

#define PAGESIZE 4096

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>

struct StoredObject
{
    int IntVal;
    char StrVal[25];
};

int main(int argc, char **argv)
{
    int fd = open("mmapfile", O_RDWR | O_CREAT | O_TRUNC, (mode_t) 0600);
    //Set the file to the size of our data (2 pages)
    lseek(fd, PAGESIZE*2 - 1, SEEK_SET);
    write(fd, "", 1); //The final byte

    unsigned char *mapPtr = (unsigned char *) mmap(0, PAGESIZE * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    struct StoredObject controlObject;
    controlObject.IntVal = 12;
    strcpy(controlObject.StrVal, "Mary had a little lamb.\n");

    struct StoredObject *mary1;
    mary1 = (struct StoredObject *)(mapPtr + PAGESIZE - 4); //Will fall on the boundary between first and second page
    memcpy(mary1, &controlObject, sizeof(StoredObject));

    printf("%d, %s", mary1->IntVal, mary1->StrVal);
    //Should print "12, Mary had a little lamb.\n"

    struct StoredObject *john1;
    john1 = mary1 + 1; //Comes immediately after mary1 in memory; will start and end in the second page
    memcpy(john1, &controlObject, sizeof(StoredObject));

    john1->IntVal = 42;
    strcpy(john1->StrVal, "John had a little lamb.\n");

    printf("%d, %s", john1->IntVal, john1->StrVal);
    //Should print "12, Mary had a little lamb.\n"

    //Make sure the data's on the disk, as this is the initial, "read-only" data
    msync(mapPtr, PAGESIZE * 2, MS_SYNC);

    //This is the inital data set, now in memory, loaded across two pages
    //At this point, someone could be reading from there. We don't know or care.
    //We want to modify john1, but don't want to write over the existing data
    //Easy as pie.

    //This is the shadow map. COW-like optimization will take place: 
    //we'll map the entire address space from the shared source, then overlap with a new map to modify
    //This is mapped anywhere, letting the system decide what address we'll be using for the new data pointer
    unsigned char *mapPtr2 = (unsigned char *) mmap(0, PAGESIZE * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    //Map the second page on top of the first mapping; this is the one that we're modifying. It is *not* backed by disk
    unsigned char *temp = (unsigned char *) mmap(mapPtr2 + PAGESIZE, PAGESIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANON, 0, 0);
    if (temp == MAP_FAILED)
    {
        printf("Fixed map failed. %s", strerror(errno));
    }
    assert(temp == mapPtr2 + PAGESIZE);

    //Make a copy of the old data that will later be changed
    memcpy(mapPtr2 + PAGESIZE, mapPtr + PAGESIZE, PAGESIZE);

    //The two address spaces should still be identical until this point
    assert(memcmp(mapPtr, mapPtr2, PAGESIZE * 2) == 0);

    //We can now make our changes to the second page as needed
    struct StoredObject *mary2 = (struct StoredObject *)(((unsigned char *)mary1 - mapPtr) + mapPtr2);
    struct StoredObject *john2 = (struct StoredObject *)(((unsigned char *)john1 - mapPtr) + mapPtr2);

    john2->IntVal = 52;
    strcpy(john2->StrVal, "Mike had a little lamb.\n");

    //Test that everything worked OK
    assert(memcmp(mary1, mary2, sizeof(struct StoredObject)) == 0);
    printf("%d, %s", john2->IntVal, john2->StrVal);
    //Should print "52, Mike had a little lamb.\n"

    //Now assume our garbage collection routine has detected that no one is using the original copy of the data
    munmap(mapPtr, PAGESIZE * 2);

    mapPtr = mapPtr2;

    //Now we're done with all our work and want to completely clean up
    munmap(mapPtr2, PAGESIZE * 2);

    close(fd);

    return 0;
}

私の修正された答えはあなたの安全上の懸念に対処するはずです。MAP_FIXED2番目の呼び出しでのみ使用しmmapます(上記のように)。すばらしい点は、既存のアドレスセクションMAP_FIXEDを上書きできることです。mmapオーバーラップしている範囲をアンロードし、新しいマップされたコンテンツに置き換えます。

 MAP_FIXED
              [...] If the memory
              region specified by addr and len overlaps pages of any existing
              mapping(s), then the overlapped part of the existing mapping(s) will be
              discarded. [...]

このようにして、OSに数百メガの連続したメモリブロックを見つけさせることができます(MAP_FIXED確実に利用できないアドレスを呼び出さないでください)。MAP_FIXED次に、変更するデータを使用して、現在マッピングされている巨大なスペースのサブセクションを呼び出します。多田。


Windowsでは、次のようなものが機能するはずです(私は現在、Macを使用しているため、テストされていません)。

int main(int argc, char **argv)
{
    HANDLE hFile = CreateFile(L"mmapfile", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    //Set the file to the size of our data (2 pages)
    SetFilePointer(hFile, PAGESIZE*2 - 1, 0, FILE_BEGIN);
    DWORD bytesWritten = -1;
    WriteFile(hFile, "", 1, &bytesWritten, NULL);

    HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, PAGESIZE * 2, NULL);
    unsigned char *mapPtr = (unsigned char *) MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, PAGESIZE * 2);

    struct StoredObject controlObject;
    controlObject.IntVal = 12;
    strcpy(controlObject.StrVal, "Mary had a little lamb.\n");

    struct StoredObject *mary1;
    mary1 = (struct StoredObject *)(mapPtr + PAGESIZE - 4); //Will fall on the boundary between first and second page
    memcpy(mary1, &controlObject, sizeof(StoredObject));

    printf("%d, %s", mary1->IntVal, mary1->StrVal);
    //Should print "12, Mary had a little lamb.\n"

    struct StoredObject *john1;
    john1 = mary1 + 1; //Comes immediately after mary1 in memory; will start and end in the second page
    memcpy(john1, &controlObject, sizeof(StoredObject));

    john1->IntVal = 42;
    strcpy(john1->StrVal, "John had a little lamb.\n");

    printf("%d, %s", john1->IntVal, john1->StrVal);
    //Should print "12, Mary had a little lamb.\n"

    //Make sure the data's on the disk, as this is the initial, "read-only" data
    //msync(mapPtr, PAGESIZE * 2, MS_SYNC);

    //This is the inital data set, now in memory, loaded across two pages
    //At this point, someone could be reading from there. We don't know or care.
    //We want to modify john1, but don't want to write over the existing data
    //Easy as pie.

    //This is the shadow map. COW-like optimization will take place: 
    //we'll map the entire address space from the shared source, then overlap with a new map to modify
    //This is mapped anywhere, letting the system decide what address we'll be using for the new data pointer
    unsigned char *reservedMem = (unsigned char *) VirtualAlloc(NULL, PAGESIZE * 2, MEM_RESERVE, PAGE_READWRITE);
    HANDLE hMap2 = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, PAGESIZE, NULL);
    unsigned char *mapPtr2 = (unsigned char *) MapViewOfFileEx(hMap2, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, PAGESIZE, reservedMem);

    //Map the second page on top of the first mapping; this is the one that we're modifying. It is *not* backed by disk
    unsigned char *temp = (unsigned char *) MapViewOfFileEx(hMap2, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, PAGESIZE, reservedMem + PAGESIZE);
    if (temp == NULL)
    {
        printf("Fixed map failed. 0x%x\n", GetLastError());
        return -1;
    }
    assert(temp == mapPtr2 + PAGESIZE);

    //Make a copy of the old data that will later be changed
    memcpy(mapPtr2 + PAGESIZE, mapPtr + PAGESIZE, PAGESIZE);

    //The two address spaces should still be identical until this point
    assert(memcmp(mapPtr, mapPtr2, PAGESIZE * 2) == 0);

    //We can now make our changes to the second page as needed
    struct StoredObject *mary2 = (struct StoredObject *)(((unsigned char *)mary1 - mapPtr) + mapPtr2);
    struct StoredObject *john2 = (struct StoredObject *)(((unsigned char *)john1 - mapPtr) + mapPtr2);

    john2->IntVal = 52;
    strcpy(john2->StrVal, "Mike had a little lamb.\n");

    //Test that everything worked OK
    assert(memcmp(mary1, mary2, sizeof(struct StoredObject)) == 0);
    printf("%d, %s", john2->IntVal, john2->StrVal);
    //Should print "52, Mike had a little lamb.\n"

    //Now assume our garbage collection routine has detected that no one is using the original copy of the data
    //munmap(mapPtr, PAGESIZE * 2);

    mapPtr = mapPtr2;

    //Now we're done with all our work and want to completely clean up
    //munmap(mapPtr2, PAGESIZE * 2);

    //close(fd);

    return 0;
}
于 2012-05-07T02:48:11.143 に答える
3

しかし、これを安全に行うためにアドレス空間を予約する方法がわかりません

それはOSによって異なりますが、mmapのmsdnを少し掘り下げると(msdn検索で「xp mmap」から始めました)、Microsoftはmmapの一部を実装する(多くの)関数に対して通常のVerboseAndHelpfullyCapitalizedNamesを持っていることがわかります。ファイル マッパーと匿名マッパーの両方が、POSIX-2001 システムとまったく同じように固定アドレス リクエストを処理できます。つまり、アドレス空間内の他の何かがカーネルと通信している場合、それを整理することができます。「安全に」触れるつもりはありません。不特定のプラットフォームに移植したいコードに「安全に」などというものはありません。事前にマップされた匿名メモリの独自のプールを構築する必要があります。これは、後で自分の制御下でマップを解除して分割できます。

于 2012-05-13T04:55:51.023 に答える