IPC: メモリマップド・ファイル

プロセス間で共有される情報を、ファイルを利用して読み書きする。

SYNOPSIS

#include <sys/mman.h>
 
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
int msync(void *addr, size_t len, int flags);
int munmap(void *addr, size_t len);

DESCRIPTION

複数のプロセスが、ひとつのファイルを同時にアクセス(読み書き)することがあります。その際に、fseek()関数を利用してファイルを操作するのはとても面倒です。もし、メモリにファイルの内容をマッピングすることができ、それへのポインタを得ることができたならファイルの操作は非常に楽なものになります。それを実現するのがメモリマップド・ファイルです。

まず始めに、マッピングしたいファイルをオープンします。ファイルの内容を読み書きしたい場合は O_RDWR を指定します。

    int fd;
    fd = open("mapdemofile", O_RDWR);

ファイルのオープンが成功したら、mmap() システムコールを利用してメモリにマッピングすることが出来ます。

● メモリへのマッピングには、mmap()システムコールを使用します。

    void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);

msgget()システムコールは、作成や接続に成功するとユニークな ID を返し、失敗すると -1 を返します。

addr ファイルがマッピングされるアドレスです。アドレスの指定は、(caddr_t)0 とセットしてOSに選ばせることが最も良い方法です。OSによって選ばれるのが嫌で自分で選択する場合、きちんとマッピングが可能なアドレス空間を指定できなければなりません。
len マッピングしたいデータのサイズを指定します。指定したサイズがページサイズの整数倍で無い場合、ページサイズの最小の倍数に切り上げられます。
prot マップされる領域に対する、読み取り(PROT_READ)、書き込み(PROT_WRITE)、実行(PROT_EXEC)、またはそれらの組合せのアクセス権を指定します。これらは、ファイルをオープンしたときの open() システムコールのアクセスモードも影響します。
flags マップした領域の扱いに関する、その他の情報を提供します。MAP_PRIVATE と MAP_SHARED フラグは、メモリー領域への書き込みの可視性を制御します。 これらフラグのいずれかが必ず指定されなければなりません。MAP_SHARED を指定すると、書き込みが行われたときにマッピングされた領域が変更されます。MAP_PRIVATE を指定すると、マッピングされた領域に対して書き込みが行われたときに領域(ページサイズ単位)のコピーが作成されます(その領域の最初の書き込み時)。それ以降の書き込みは、コピーが参照されます。コピーが作成されるのは、変更された領域(ページサイズ単位)だけです。とはいっても、MAP_PRIVATE を指定したプログラムってあまり見ないなあ〜(malloc が使っているけど)。フラグはこれ以外にも、たくさん用意されています。mmap(2) 参照。
fildes オープンしたファイルのファイル記述子を指定します。
off ファイルのどこからマッピングしたいのか、オフセットを指定します。ページサイズの整数倍でなければなりません。ページサイズの取得には getpagesize()関数やsysconf(_SC_PAGE_SIZE)関数を利用します。

マッピングが成功すれば、マッピングされた領域へのポインタを返し、失敗すれば -1 が返ります。

ファイルを参照するためにメモリにマッピングする場合、以下のような記述になります。

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/mman.h>
 
    ...
    int fd, pagesize;
    char *data;
    struct stat sbuf;
 
    fd = open("map_file", O_RDONLY);
    stat("mmapdemo.c", &sbuf);
    data = mmap((caddr_t)0, sbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);

mmap() システムコールを使用すると、 read()/write() を使わなくてもアドレス空間を操作することでリソースにアクセスできます。たとえば、以下のような lseek の記述は

    fd = open(...);
    lseek(fd, some_offset)
    read(fd, buf, len)
    /* use data in buf */

mmap では以下のようになります。

    fd = fopen(...);
    address = mmap((caddr_t)0, len, PROT_READ, MAP_SHARED, fd, some_offset);
    /* use data at address */

● メモリにマッピングされた内容と実際のファイルとの同期を取るには msync()関数を使用します。

    int msync(void *addr, size_t len, int flags);

msync()関数は、指定したアドレス addr から指定した長さ len の範囲にある領域(ページサイズ単位)をファイルに書き込みます。flags は、非同期の書き込みを行なう(MS_ASYNC)、同期の書き込みを行なう(MS_SYNC)、マッピングを無効にする(MS_INVALIDATE) のいずれかを指定します。正常終了の場合、 0 を返し、失敗の場合は -1 を返します。

MS_ASYNC メモリー領域の現在の内容と一致するようにファイルの内容を同期させます。関数はすべての書き込み作業をスケジュールすると直ちに戻ります。
MS_SYNC メモリー領域の現在の内容と一致するようにファイルの内容を同期させます。関数はすべての書き込み作業が完了するまで戻りません。
MS_INVALIDATE 現在のファイルの内容と一致するように、メモリー領域の内容を同期させます。

● メモリへのマッピングを開放するには、munmap()システムコールを使用します。

    int munmap(void *addr, size_t len);

munmap()システムコールは、指定したアドレス addr から指定した長さ len の範囲にある領域(ページサイズ単位)のマッピングを開放します。正常終了の場合、 0 を返し、失敗の場合は -1 を返します。

SAMPLE

自分自身のソースファイルをメモリにマップして、指定したオフセットの文字を表示します。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
 
int main(int argc, char *argv[])
{
    int fd, offset;
    char *data;
    struct stat sbuf;
 
    if (argc != 2) {
        fprintf(stderr, "Usage: %s offset\n", argv[0]);
        exit(1);
    }
 
    if ((fd = open("mmap_sample.c", O_RDONLY)) == -1) {
        perror("open");
        exit(1);
    }
 
    if (stat("mmap_sample.c", &sbuf) == -1) {
        perror("stat");
        exit(1);
    }
 
    offset = atoi(argv[1]);
    if (offset < 0 || offset > sbuf.st_size-1) {
        fprintf(stderr, "offset must be in the range 0-%ld\n", sbuf.st_size-1);
        exit(1);
    }
 
    data = (char *)mmap((caddr_t)0, sbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
    if (data == (char *)-1) {
        perror("mmap");
        exit(1);
    }
 
    printf("Offset %d is '%c'\n", offset, data[offset]);
 
    if (munmap(data, sbuf.st_size) == -1) {
        perror("munmap");
        exit(1);
    }
 
    return 0;
}

これをコンパイルし実行すると以下のようになります。

    # gcc mmap_sample.c -o mmap_sample
    # ./mmap_sample 312
    Offset 312 is '%'
    # 

SEE ALSO

mmap(2), msync(3), munmap(2). getpagesize(3), sysconf(3)

 

戻る