本ページはプロモーションが含まれています

Linux メモリマップ・ファイル


ハンドメイドやオーダーメイド商品がいっぱい
あなただけのお気に入りな商品が見つかる
ECモールサイト 創作品モール「あるる」

ファイルと同期するメモリ領域

mmap()システムコールは、ディスク上のファイルの内容と読み書きが同期するメモリマップ・ファイルを作成します。メモリマップ・ファイルは、ファイルの内容をメモリ領域にマッピングし、その領域への変更を自動的にファイルに反映して同期させることができます。read()やwrite()システムコールよりも高速なランダムアクセスが可能ですが、メモリサイズによりサイズが制限されます。
メモリマップ・ファイルのメモリは、設定によりプロセス間で共有することができるので、プロセス間のデータの受け渡しに利用できます。あるいは逆に共有させない設定にすることで、ファイルから読み出した内容をプロセスで独立したマッピングメモリに持つこともできます。
Picture
mmap()は、設定によりメモリとファイル間、メモリとプロセス共有、マッピングのアドレス・オフセットの指定などを調整することで、malloc()の内部実装や共有ライブラリなど、OSのメモリを扱う様々な場面で使われています。
mmap()関数の定義は、次の通りです。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
addr マッピングするアドレス(通常はカーネルの自動配置としてNULLを指定する)
length 確保するメモリサイズ(ページ単位で確保するがページサイズの倍数でなくてもよい)
prot プロテクション
PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE(ORで複数選択可)
flags オプション
MAP_SHARED、MAP_PRIVATE、MAP_ANONYMOUS(ORで複数選択可)
fd マッピングするファイルのファイルハンドル
offset ファイルのマッピング開始のオフセット位置(先頭からならば0)
return 成功: マッピングしたメモリの先頭アドレス
失敗: MAP_FAILED
mmap()の戻り値には、ファイルの内容をコピーしてマッピングしたメモリの先頭アドレスが返ります。
次の例は、maptestというテキストファイルの内容を表示します。mmap()の戻り値を参照することで、maptestのファイルの内容を読み出します。
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    char *map;

    fd = open("maptest", O_RDONLY);
    map = (char*)mmap(NULL, 100, PROT_READ, MAP_SHARED, fd, 0);
    if (map != MAP_FAILED) {
        puts(map);
        close(fd);
        munmap(map, 100);
    }
    return 0;
}
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
この例で使ったmaptestファイルのサイズは90バイトで、mmap()には100バイトを指定していますが、実際のメモリ確保はページ単位の4KBで確保されるので、100ではなく4096と指定しても同じです。指定した長さから適切なページ単位で割り当てられるので、正確にページサイズを指定しなくてもかまいません。
ファイルサイズはfstat()により正しく知ることができます。
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    char *map;
    struct stat st;

    fd = open("maptest", O_RDONLY);
    fstat(fd, &st);
    map = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
    if (map != MAP_FAILED) {
        puts(map);
        close(fd);
        munmap(map, st.st_size);
    }
    return 0;
}
正確にページサイズの倍数で指定したい場合は次のようにできます。
fstat(fd, &st);
len = (st.st_size / getpagesize() + 1) * getpagesize();
ファイルを閉じてもmmap()でマッピングした領域は自動的に消えずに残ります。マッピングメモリを開放するには、明示的にmunmap()を呼び出す必要があります。munmap()の引数にはマッピングメモリの先頭アドレスとサイズを指定します。

メモリ書き込みによるファイルへの反映

mmap()の引数prot(プロテクション)に、PROT_WRITEを加えると、マッピングしたメモリの変更をファイルへ同期して反映させることができます。次は、マッピング領域への変更が自動的にファイルに反映される例です。
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    char *map;
    struct stat st;

    fd = open("maptest", O_RDWR);
    fstat(fd, &st);
    map = (char*)mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (map != MAP_FAILED) {
        memcpy(map, "XYZ", 3);
        close(fd);
        munmap(map, st.st_size);
    }
    return 0;
}
$ cat maptest
XYZDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
mmap()の引数protには次のプロテクションをORで複数指定できます。
PROT_READ 読み出し可能
PROT_WRITE 書き込み可能
PROT_EXEC 実行可能
PROT_NONE ページアクセス不可
通常はPROT_READとPROT_WRITEの何れかか両方を組み合わせます。この指定とファイルのオープンモードは合わせなければなりません。ファイルが読み出しのみで開かれているときはPROT_WRITEの指定は無効になります。

ファイルへの同期の制御

マッピングメモリへの書き込みのファイルへの反映や、プロセスで共有している場合の、ファイルに変更があったときの各プロセスのマッピングメモリの同期更新は、カーネルが適切なタイミングで実行します。
msync()の呼び出しにより、マッピングメモリのファイルへの反映、およびファイルの変更の同期更新を即座に行います。
int msync(void *addr, size_t length, int flags)
addr 同期するマッピングメモリの先頭
length 同期するサイズ
flag 同期方法
MS_ASYNC、MS_SYNC、MS_INVALIDATE
return 成功:0 失敗:-1
flagに指定する同期方法には次の何れかを指定します。
MS_ASYNC マッピングメモリ領域と一致するようにファイルの内容を同期して更新する。
関数は同期処理をスケジュールして即座に戻る。
MS_SYNC マッピングメモリ領域と一致するようにファイルの内容を同期して更新する。
関数は書き込み作業が完了するまで戻らない。
MS_INVALIDATE 現在のファイルの内容と一致するように、マッピングメモリ領域を同期して更新する。
次の例は、ファイルを閉じる前に、msync()で変更を確実にファイルへ反映させています。
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    char *map;
    struct stat st;

    fd = open("maptest", O_RDWR);
    fstat(fd, &st);
    map = (char*)mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (map != MAP_FAILED) {
        memcpy(map, "XYZ", 3);
        msync(map, st.st_size, MS_SYNC);
        close(fd);
        munmap(map, st.st_size);
    }
    return 0;
}

プロセス間の共有メモリ

mmap()のflagsにMAP_SHAREDを指定した場合は、同じファイルのマッピング領域はプロセス間で共有されます。
次の例は、maptestファイルをマッピングして、その内容を最初に表示して2秒後にもう一度メモリの内容を表示させています。
mmap01
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    char *map;
    struct stat st;

    fd = open("maptest", O_RDWR);
    fstat(fd, &st);
    map = (char*)mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (map != MAP_FAILED) {
        puts(map);
        sleep(2);
        puts(map);
        close(fd);
        munmap(map, st.st_size);
    }
    return 0;
}
同じmaptestファイルのメモリマップを取得するもう一つのプロセスで、次のようにマッピングしてから1秒後に内容の一部を変更させます。
mmap02
int main(int argc, char *argv[])
{
    int fd;
    char *map;
    struct stat st;

    fd = open("maptest", O_RDWR);
    fstat(fd, &st);
    map = (char*)mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (map != MAP_FAILED) {
        sleep(1);
        memcpy(map, "vvv", 3); 
        close(fd);
        munmap(map, st.st_size);
    }
    return 0;
}
上の2つのプロセスを同時に実行すると、mmap01によるマッピング内容の表示は、初期状態から2秒後の間に、mmap02による変更を同期して変化していることがわかります。プロセス毎の論理アドレス空間は、同一のマッピングしている物理メモリ領域をアクセスします。
$ ./mmap01 &
$ ./mmap02 &
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
    〜2秒〜
vvvDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789

プロセス共有しないメモリマップ・ファイル

mmap()のflagsにMAP_PRIVATEを指定した場合は、同じファイルのメモリマップでもプロセス間で共有されずに、プロセスごとに物理メモリが割り当てられます(※)。この場合は、メモリ変更によるファイルへの同期は行われません。ファイルを読み出した内容のコピーを、プロセスが独立して使うことができます。
※ 実際は、書き込みが行われない間はMAP_SHAREDと同様に物理メモリを共有しており、書き込み操作が行われた時点でそのプロセス用に物理メモリ領域を用意してコピーするという動きになっています。これは「copy-on-write」というしくみで、なるべく物理メモリを消費しないようになっています。
次の例は、maptestファイルのメモリマップが、プロセス毎に独立して割り当てられている様子を示しています。fork()により生成された子プロセスと親プロセスが、それぞれにポインタ変数のmapが示すメモリマップの内容を変更していますが、それぞれが独立した領域を変更していることがわかります。
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    char *map;
    struct stat st;

    fd = open("maptest", O_RDWR);
    fstat(fd, &st);
    map = (char*)mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
    if (map != MAP_FAILED) {
        if (fork() == 0) {
            memcpy(map, "vvv", 3);
            puts(map);
        } else {
            memcpy(map, "xxx", 3);
            puts(map);
        }
        close(fd);
        munmap(map, st.st_size);
    }
    return 0;
}
xxxDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789

vvvDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789
ABCDEFG0123456789

ファイルをマッピングしないメモリ確保

mmap()の引数flagにMAP_ANONYMOUS(無名)を指定すると、内容をマッピングするファイルのない、単なるページメモリを取得することができます。これはmalloc()と同じ働きですが、実際にmalloc()はmmap()のこの機能を利用して実装されています。
次の例は、MAP_ANONYMOUSを指定して32バイト(1ページ)のメモリ領域を確保しています。
#include <sys/mman.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *map;
    map = (char*)mmap(NULL, 32, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (map != MAP_FAILED) {
        sprintf(map, "ABCDEFG");
        puts(map);
        munmap(map, 32);
    }
    return 0;
}
ファイルはマッピングしないので引数fdには-1を指定します。