IPC: 共有メモリ

共有のメモリ領域を、複数のプログラムから参照。

SYNOPSIS

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, void *shmaddr, int shmflg);
int shmdt(void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

DESCRIPTION

共有メモリは、2つ以上のプロセスでのメモリを共有させることを実現します。異なるプロセスが、共有メモリ内に割り当てられたデータを共有できるようになります。メモリを共有することで、プロセ ス間で高速なデータの交換が行えます。

● 共有メモリを作成するには、shmget()システムコールを使用します。

    int shmget(key_t key, size_t size, int shmflg);

shmget()システムコールは、共有メモリの領域獲得に成功するとユニークな ID を返し、失敗すると -1 を返します。

key は、作成したい共有メモリでユニークなものを識別子として指定します(通常、ftok()関数を利用します)。size で指定したバイト数分共有メモリセグメントが生成されます。shmflgには、IPC_CREAT を指定することで共有メモリの作成を指示します。また、アクセスパーミッションも指定できます。shmflgに IPC_CREATIPC_EXCL を指定した場合、key がそれに対応する共有メモリをすでに確保している場合エラーを返します(open()システムコールと同じ様な動き)。

共有メモリのサイズには、限りがあります。OSにより異なりますが、OS起動時から固定されてます(Solaris では /etc/system ファイルでサイズを指定できます)。共有メモリを確保できる最大サイズなどを変更することも可能ですが、メモリ内の一部をロックすることになるのであまり大きく確保すると通常使用されるプロセス用のメモリ領域が減ってしまいます。

● 共有メモリセグメントへの読み書きを許可するアタッチ操作は、shmat()システムコールを使用します。

    void *shmat(int semid, void *shmaddr, int shmflg);

semid は、shmget()システムコールによって獲得された共有メモリの ID です。共有メモリセグメントが、いかなるプロセスによっても結び付けられていなければ、 shmaddr0 を指定しなければなりません。すると、共有メモリセグメントがオペレーティングシステムが選んだ位置に結び付けられます。shmflg は、 SHM_RDONLYが指定されれば読取り専用として結び付けられ、 そうでなければ読取りと書込み可能として結び付けられます。 セグメントは書込み専用として結び付けることはできないので、読み取り専用でなければ通常 0 を指定します。

アタッチが成功すれば、結び付けられた共有メモリセグメントの開始アドレスを返します。失敗すれば -1 を返します。

● 読み取りと書き込み

共有メモリへのアタッチが成功したら、そのデータポイントに対して char ポインタとしてアクセスすることで読み取り書き込みが可能になります。

    char *data;
    ...
    data = (char *)shmat(shmid, (void *)0, 0);
    if (data == (char *)-1) {
        perror("shmat");
        exit(1);
    }
          

これに対し最も簡単に書き込みを行うには(実際には、意図した処理が行えるよう共有メモリのサイズのチェックをする)

    printf("Enter a string: ");
    gets(data);

と記述し、以下のようにすることで読み取りを行えます。

    printf("shared contents: %s\n", data);

● 共有メモリセグメントへのアクセスの終了(デタッチ)は、shmdt()システムコールを使用します。

    int shmdt(void *shmaddr);

共有メモリの利用を終了する場合、必ずデタッチをしなければなりません。shmaddr は、shmat() システムコールによって結び付けられた共有メモリの開始アドレスを指定します。デタッチが成功すれば0 を返し、失敗すれば -1 を返します。

● 共有メモリセグメントの破棄は、 shmctl()システムコールを使用します。

    int shmctl(int semid, int cmd, struct shmid_ds *buf);

semidは、shmget()システムコールによって獲得された共有メモリの ID です。cmd は、IPC_RMID を指定します。IPC_RMID の場合、buf は NULL を指定します。 このほかに cmd は、セグメントをロックしたり、ステータスを見たり、パーミッションを変更するコマンドを指定できます。

共有メモリセグメントの破棄は、ipcs コマンドで詳細を調べ ipcrm コマンドを利用しても可能です。

SAMPLE

共有メモリ内のデータを表示するサーバ(shmsvr.c)

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
#define SHMSZ     512
 
int main()
{
    char    c;
    int     shmid;
    key_t   key;
    char   *data, *s;
 
    if ((key = ftok("shm.dat", 'R')) == -1) {
        perror("ftok");
        exit(1);
    }
 
    /* Create the segment. */
    if ((shmid = shmget(key, SHMSZ, IPC_CREAT | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
 
    /* attach the segment to data space. */
    data = (char *)shmat(shmid, (void *)0, 0);
    if (data == (char *)-1) {
        perror("shmat");
        exit(1);
    }
 
    /* print data from segment, every 5 sec.*/
    strcpy(data, "initialization...");
    while(1) {
        printf("%s\n",data);
        if (strcmp(data, "end") == 0) {
            break;
        }
        sleep(5);
    }
 
    /* dettach the segment to data space */
    if (shmdt(data) == -1){
        perror("shmdt");
        exit(1);
    }
    /* Destroy the segment */
    if (shmctl(shmid, IPC_RMID, 0) == -1){
        perror("shmctl");
        exit(1);
    }
 
    return 0;
}

共有メモリ内にデータを書き込むクライアント(shmcli.c)

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
#define SHMSZ     512
 
int main()
{
    int     shmid;
    key_t   key;
    char   *data, *s;
 
    if ((key = ftok("shm.dat", 'R')) == -1) {
        perror("ftok");
        exit(1);
    }
 
    /* Locate the segment. */
    if ((shmid = shmget(key, SHMSZ, 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
 
    /* attach the segment to data space. */
    data = (char *)shmat(shmid, (void *)0, 0);
    if (data == (char *)-1) {
        perror("shmat");
        exit(1);
    }
 
    /* write data to segment */
    printf("Enter a string: ");
    gets(data);
 
    /* dettach the segment to data space */
    if (shmdt(data) == -1){
        perror("shmdt");
        exit(1);
    }
 
    return 0;
}

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

  << SHELL-1 >>
    # gcc shmsvr.c -o shmsvr
    # gcc shmcli.c -o shmcli
    # touch shm.dat
    # ./shmsvr
    initialization...

SHELL-1 で共有メモリ内を表示するサーバプログラムを起動したら、別のシェルで書き込むクライアントプログラムを起動します。

  << SHELL-2 >>
    # ./shmcli
    Enter a string: hello, world.
      (10秒ぐらい待つ)
    # ./shmcli
    Enter a string: end
    #

サーバは、クライアントから共有メモリ内に "end" という文字を書き込まれるまで共有メモリ内を5秒おきに表示し続けます。

  << SHELL-1 >>
    initialization...
    hello, world.
    hello, world.
    end
    #

SEE ALSO

shmget(2), shmat(2), shmdt(2), shmctl(2)

 

戻る