C言語入門講座。関数、サンプル集を参考にして、 C言語をマスターしよう。初心者から上級者まで。

共有メモリの操作を行なう

2012.08.10

shmget関数、shmat関数、shmdt関数、shmctl関数は、共有メモリの操作を行ないます。共有メモリとは、システムが管理しているメモリの一部を共有して、複数のプロセスがプロセス間通信を行う技法です。複数のプロセスが共有しますので、プロセス間の待ち合わせや排他制御が必要な場合がありますが、その場合はセマフォを使用するとよいでしょう。

共有メモリは、次の手順で操作します。

  1. shmget関数で共有メモリ・セグメントの識別子(セグメントID)を取得します。なお、共有メモリ・セグメントは新規作成もできます。
  2. それぞれのプロセスは、shmat関数で自プロセスのアドレス空間に共有メモリ・セグメントを付加(アタッチ)します。これで、共有メモリへのデータの読み書きが可能になります。
  3. それぞれのプロセスで共有メモリが不要になったら、shmdt関数で自プロセスのアドレス空間に配置された共有メモリ・セグメントを分離(デタッチ)します。
  4. shmctl関数で共有メモリ・セグメントを破棄します。

これらの関数は、C言語のライブラリ関数(標準関数)ではありませんので、コンパイラにより、使えない場合があります。

#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

keyは共有メモリ・セグメントに対するキーを指定します。
sizeは作成する共有メモリ・セグメントの大きさをバイト単位で指定します。なお、指定した値はヘッダファイルに定義してあるPAGE_SIZEの値の倍数に切り上げた(round up)大きさになります。
shmflgはオプションを指定します。

戻り値として、正常終了した場合はセグメント識別子(セグメントID)を、エラーの場合は-1を返します。なお、セグメントIDは正の整数値です。

新規に共有メモリ・セグメントを作成するには、次の2つの方法があります。

  1. 第1引数のkeyにIPC_PRIVATEを指定します。
  2. 第1引数のkeyにユニークな値を指定し、第3引数のshmflgにIPC_CREATを指定します。なお、shmflgにIPC_CREATとIPC_EXCLを指定すると、keyに対する共有メモリ・セグメントが既に存在していた場合にエラーになります。

第3引数のshmflgの下位9ビットは、その共有メモリ・セグメントの所有者、グループ、他人に対するアクセス許可の定義として使用します。

#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);

shmidは共有メモリ・セグメントに対するセグメント識別子(セグメントID)を指定します。
*shmaddrは共有メモリ・セグメントを付加(アタッチ)するアドレスを指定します。
shmflgはオプションを指定します。SHM_RDONLYを指定すると、共有メモリ・セグメントを読み込み専用で付加します。

戻り値として、正常終了した場合は付加した共有メモリ・セグメントのアドレスを、エラーの場合は(void *)-1を返します。

第2引数の*shmaddrにNULLを指定すると、システムは共有メモリ・セグメントを適切な(使用されていない)アドレスに付加します。また、NULLでないアドレスを指定して、第3引数のshmflgにSHM_RNDを指定すると、指定したアドレスはセグメントの境界アドレスの最小倍数(SHMLBA)に切り捨てた(rounding down)アドレスになります。その他の場合は、ページ境界のアドレスでなければなりません。

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmidは共有メモリ・セグメントに対するセグメント識別子(セグメントID)を指定します。
cmdは共有メモリ・セグメントに対する制御命令を指定します。
*bufは共有メモリ・セグメントに関する情報が格納されているhmid_ds構造体へのポインタを指定します。

戻り値として、正常終了した場合は0を、エラーの場合は-1を返します。

第2引数のcmdには、次のようなコマンドを指定できます。

コマンド 内容
IPC_STAT カーネルデータ構造体の情報を第3引数の*bufで指定されたshmid_ds構造体にコピーします。
IPC_SET 第3引数の*bufで指定されたshmid_ds構造体のいくつかのメンバの値を、カーネルデータ構造体に設定します。
IPC_RMID 共有メモリ・セグメントを破棄します。実際には破棄済みのマークだけを付けておき、関連する全てのプロセスが共有メモリ・セグメントを分離(デタッチ)したら破棄します。

次の例題プログラムは、子プロセス1が共有メモリにメッセージを書き込み、子プロセス2がそれを表示しています。

プログラム 例

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <string.h>

int main()
{
  int                shmid;       /* セグメントID */
  int                child_cnt;

  /* 共有メモリ・セグメントを新規作成 */
  if ((shmid = shmget(IPC_PRIVATE, 100, 0600)) == -1){
    perror('main : shmget ');
    exit(EXIT_FAILURE);
  }

  /* 1つ目の子プロセスを生成 */
  if (fork() == 0) {
    /* 子プロセス */
    char      *shmaddr;

    printf('子プロセス1開始\n');

    /* 共有メモリ・セグメントをプロセスのアドレス空間に付加 */
    if ((shmaddr = shmat(shmid, NULL, 0)) == (void *)-1) {
      perror('子プロセス1 : shmat ');
      exit(EXIT_FAILURE);
    }

    /* 共有メモリ・セグメントに文字列をコピー */
    strcpy(shmaddr, 'Hello.\nBy.\n');

    /* 共有メモリ・セグメントを分離 */
    if (shmdt(shmaddr) == -1) {
      perror('子プロセス1 : shmdt ');
      exit(EXIT_FAILURE);
    }

    printf('子プロセス1終了\n');
    exit(EXIT_SUCCESS);
  }

  /* 2つ目の子プロセスを生成 */
  if (fork() == 0) {
    /* 子プロセス */
    char      *shmaddr;

    printf('子プロセス2開始\n');

    /* 待ち合わせ */
    sleep(1);

    /* 共有メモリ・セグメントをプロセスのアドレス空間に付加 */
    if ((shmaddr = shmat(shmid, NULL, SHM_RDONLY)) == (void *)-1) {
      perror('子プロセス2 : shmat ');
      exit(EXIT_FAILURE);
    }

    printf('子プロセス2:共有メモリの内容を表示します\n');
    /* 共有メモリ・セグメントの内容を表示 */
    printf('%s', shmaddr);

     /* 共有メモリ・セグメントを分離 */
    if (shmdt(shmaddr) == -1) {
      perror('子プロセス2 : shmdt ');
      exit(EXIT_FAILURE);
    }

    printf('子プロセス2終了\n');
    exit(EXIT_SUCCESS);
  }

  /* 親プロセス。子プロセスの終了を待つ */
  for (child_cnt = 0; child_cnt < 2; ++child_cnt) {
    wait(NULL);
  }

  /* 共有メモリ・セグメントを破棄 */
  if (shmctl(shmid, IPC_RMID, NULL) == -1){
    perror('main : shmctl ');
    exit(EXIT_FAILURE);
  }

  printf('親プロセス終了\n');
  return EXIT_SUCCESS;
}

例の実行結果

$ ./shm.exe
子プロセス1開始
子プロセス1終了
子プロセス2開始
子プロセス2:共有メモリの内容を表示します
Hello.
By.
子プロセス2終了
親プロセス終了
$

関連記事