共有メモリ

  • 共有メモリは、最も高速なIPC機構

  • 一旦メモリ領域を共有するプロセスのアドレス空間にメモリがマップされると、カーネルの介在なしにプロセス間でのデータの受け渡しが行われる

  • カーネルの介在なしに」とは、データの受け渡しの際にプロセスがシステムコールを全く実行しないことを指す


  • パイプ、FIFO、メッセージキューによるIPCの問題は、2つのプロセスが情報を交換する際に、それがカーネルを経由しなければならないということ。

  • 共有メモリは、2つ以上の往路セスにメモリ領域を共有させることで、データがカーネルを経由するという問題を回避する。

  • プロセスは、共有メモリの使用に関して、同期処理をする必要がある。


  • mmap, munmap, msync関数

    mmap関数は、ファイルまたはPosixの共有メモリオブジェクトを、呼び出したプロセスのアドレス空間にマップする。


    mmapの3つの目的

  • 1.通常ファイルのマップによるメモリマップI/O機能の提供

  • 2.特殊ファイルのマップによる匿名メモリマッピング機能の提供

  • 3.shm_openを用いた無関係なプロセス間でのPosix共有メモリ機能の提供
  • #include <sys/mman.h>
    
    void *mmap(void *addr, 
               size_t len, 
               int prot, 
               int flags, 
               int fd, 
               off_t offset);
    
    戻り値:成功ならマップされた領域の先頭アドレス、エラーならMAP_FAILED
    

  • addrには、ディスクリプタfdをマップする、呼び出しプロセス内の先頭アドレスを指定する。addrは、カーネルに先頭アドレスを選択させることを意味するヌルポインタを与えることが多い。

  • lenは、呼び出しプロセスのアドレス空間にマップするバイト数

  • offsetはファイルの先頭からのバイトオフセット。普通ゼロを指定する

  • メモリマップされた領域の保護は、prot引数で指定する。

  • prot引数は、PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONEを指定する。論理和を用いて複数指定することも可

  • flagsには、MAP_SHARED(変更の共有)かMAP_PRIVATE(変更の非共有)のどちらかを必ず指定する。

  • mmapが成功した場合、fd引数に与えたファイルディスクリプタは閉じてよい。


  • プロセスのアドレス空間からメモリマップされた領域を取り除くにはmunmapを呼び出す

    #include <sys/mman.h>
    
    int munmap(void *addr, size_t n);

  • addrはmmapが返したアドレス

  • lenはマップされた領域の大きさ

  • マップが取り除かれた領域のアドレスへの参照は、SIGSEGVを発生させる


  • ディスク上のファイルが、メモリマップされた領域と同一の内容を持っていることを保障したい場合、msyncを呼び出して強制的に同期を行わせることが出来る。

    #include <sys/mman.h>
    
    int msync(void *addr, size_t len, int flags);
    
    戻り値:成功なら0、エラーなら−1

    flags引数

  • MS_ASYNC : 非同期書き込み

  • MS_SYNC : 同期書き込み

  • MS_INVALIDATE : キャッシュされているデータの無効化
  • MS_ASYNCはカーネル書き込み操作をキューイングする。
    MS_SYNCは書き込みが完了してから制御を戻す。

    //複数のプロセスから、mmapによる
    //メモリマップファイル中のカウンタを増加させる。
    
    #include <semaphore.h>
    #include <stdio.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <sys/mman.h>
        
    #define SEM_NAME "/sem"
    #define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)        sem_t *sem; int counter = 0;
    
    int
    main()
    {
        int fd, i, nloop, zero = 0;
        int *ptr;
        sem_t *mutex;
        nloop = 1000;
        
        fd = open("/tmp/mmap", O_RDWR|O_CREAT, FILE_MODE);
        write(fd, &zero, sizeof(int));
        ptr = mmap(NULL,
                sizeof(int),
                PROT_READ|PROT_WRITE,
                MAP_SHARED, 
                fd,
                0);
    
        close(fd);
    
        mutex = sem_open(SEM_NAME, 
                         O_CREAT|O_EXCL, 
                         FILE_MODE, 
                         1);
    
        sem_unlink(SEM_NAME);
        setbuf(stdout, NULL);
    
        if (fork() == 0) {//Child
            for (i = 0; i < nloop; i++) {
                sem_wait(mutex);
                (*ptr)++;
                (*ptr)--;
                (*ptr)++;
                printf("child: %d\n", *ptr);
                sem_post(mutex);
            }
            exit(0);
        }
    
        //Parent
        for (i = 0; i < nloop; i++) {
            sem_wait(mutex);
            (*ptr)++;
            (*ptr)--;
            (*ptr)++;
            printf("parent: %d\n", *ptr);
            sem_post(mutex);
        }
    
        return 0;
    }
    

    コンパイル、実行
    >gcc -lrt mmap.c
    >./a.out



    メモリベースセマフォによるメモリマップファイル上のカウンタの増加

    #include <semaphore.h>
    #include <stdio.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    
    #define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
    
    struct shared {
        sem_t mutex;
        int count;
    } shared;
    
    int
    main()
    {
        int fd, i, nloop;
        struct shared *ptr;
    
        nloop = 10000;
    
        fd = open("/tmp/mmap", O_RDWR|O_CREAT, FILE_MODE);
        write(fd, &shared, sizeof(struct shared));
        ptr = mmap(NULL, 
                sizeof(struct shared),
                PROT_READ|PROT_WRITE,
                MAP_SHARED, fd, 0);
        close(fd);          
    
        /* Memory based semaphore */
        sem_init(&ptr->mutex, 1, 1);
        setbuf(stdout, NULL);
            
        if (fork() == 0) {
            for (i = 0; i < nloop; i++) {
                sem_wait(&ptr->mutex);
                ptr->count++;
                ptr->count--;
                ptr->count++;
                printf("child: %d\n", ptr->count);
                sem_post(&ptr->mutex);
            }
            exit(0);
        }
    
        for (i = 0; i < nloop; i++) {
            sem_wait(&ptr->mutex);
            ptr->count++;
            ptr->count--;
            ptr->count++;
            printf("parent: %d\n", ptr->count);
            sem_post(&ptr->mutex);
        }
       
        return 0;
    }




    //メモリマップファイルをftruncateで拡張する
    
    #include <sys/mman.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    
    #define FILE "/tmp/mmap"
    #define SIZE 32768
    
    #define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
    
    int
    main()
    {
        int fd, i;
        char *ptr;
        fd = open(FILE, O_RDWR|O_CREAT|O_TRUNC, FILE_MODE);
        ptr = mmap(NULL,
                 SIZE,
                 PROT_READ|PROT_WRITE,
                 MAP_SHARED,
                 fd,         
                 0);         
    
        for (i = 4096; i <= SIZE; i += 4096) {
            printf("setting file size to %d\n", i);
            ftruncate(fd, i);
            printf("ptr[%d] = %d\n", i-1, ptr[i-1]);
        }   
    
        return 0;
    }