C 言語で共有メモリを割り当てるために shmget を使用する
 
この記事では、shmget 関数を使って C 言語で共有メモリを確保する方法を複数紹介します。
C 言語で共有メモリを割り当てるには shmget を使用する
    
共有メモリは、2つ以上のプロセスがユーザースペースでデータを交換し、高速に通信することを可能にするプロセス間通信の方法の 1つです。共有メモリは、複数のプロセスがメモリ内の同じ領域を共有し、必要に応じてこのセグメントを変更/アクセスできることを意味します。
以下の例で説明するインターフェースは、System V の共有メモリと呼ばれるもので、4つの関数を使って提供されます。これは、shmget、shmat、shmdt、shmctl の 4つの関数を使って提供されます。
- shmgetは新しい共有メモリセグメントを作成したり、既に作成されたメモリセグメントの識別子を取得したりするのに使われます。
- shmgetは新しい共有メモリセグメントを作成したり、既に作成されたメモリセグメントの識別子を取得したりするために用いられます。
- shmdtは、与えられたメモリセグメントを切り離すために用いることができます。
- shmctlは、複数のオプションを指定してセグメントを変更したり、解放したりするために使用します。
次のサンプルコードは、新しい共有セグメントを作成し、そこにテキストを書き込むプロセスのために shmget と shmat 関数の基本的な使い方を実装したものです。関数 shmget は 3つの引数を取り、最初の引数はメモリセグメントのキーです。キーの値には、新しいセグメントを作成する場合は IPC_PRIVATE マクロを、メモリの識別子を取得する場合は既存のセグメントのキーの値を指定することができます。第 2 引数の shmget はセグメントのサイズを指定し、第 3 引数のパーミッションフラグは複数の値を含むように OR することができます。
メモリセグメントが作成されると、セグメント識別子が得られるので、それを shmat 関数に渡してメモリをアタッチすることができます。ユーザは shmat の第二引数として、与えられたメモリセグメントをアタッチするための特定のアドレスを渡すことができます。しかし、通常はカーネルにアドレスを選択させて NULL を指定することが望ましい。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wait.h>
enum { SEGMENT_SIZE = 0x6400 };
const char *data = "Hello there!";
int main(int argc, char *argv[]) {
  int status;
  int segment_id;
  segment_id = shmget(IPC_PRIVATE, SEGMENT_SIZE,
                      IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
  char *sh_mem = (char *)shmat(segment_id, NULL, 0);
  printf("Segment ID %d\n", segment_id);
  printf("Attached at %p\n", sh_mem);
  memmove(sh_mem, data, strlen(data) + 1);
  printf("%s\n", sh_mem);
  shmdt(sh_mem);
  shmctl(segment_id, IPC_RMID, 0);
  exit(EXIT_SUCCESS);
}
出力:
Segment ID 1540195
Attached at 0x7f6ba1437000
Hello there!
関数 shmat が有効なポインタを返すと、それを任意のメモリアドレスとして扱い、必要に応じて操作することができます。最後に、shmdt と shmctl 関数が呼び出され、与えられたセグメントを切り離してメモリを解放します。割り当てられたメモリセグメントに対して shmctl が呼び出されなかったとします。その場合、プログラムが終了した後もシステムのメモリに残ってしまい、システム全体の共有メモリセグメントの上限に達してしまう可能性があります。
一方、次のサンプルコードでは、共有メモリを使用して 2つのプロセスがどのように相互作用するかを示しています。このコードは、"Hello there!"という文字列を出力した後、メインプロセスがフォークされ、同じアドレスに別の文字列を格納する子プロセスが作成されることを除いては、前の例と同じです。その間、親プロセスはサスペンドし、子プロセスの終了を待ち、成功コードで終了します。複数のプロセスが共有メモリセグメントを同時に変更してアクセスする必要がある場合、プログラマはセマフォのような同期化ツールを使用する必要があります。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wait.h>
enum { SEGMENT_SIZE = 0x6400 };
const char *data = "Hello there!";
int main(int argc, char *argv[]) {
  int status;
  int segment_id;
  segment_id = shmget(IPC_PRIVATE, SEGMENT_SIZE,
                      IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
  char *sh_mem = (char *)shmat(segment_id, 0, 0);
  printf("Segment ID %d\n", segment_id);
  printf("Attached at %p\n", sh_mem);
  memmove(sh_mem, data, strlen(data) + 1);
  printf("%s\n", sh_mem);
  pid_t child_pid = fork();
  if (child_pid == -1) perror("fork");
  if (child_pid == 0) {
    strcpy(sh_mem, "NEW DATA Stored by Child Process\0");
    printf("child pid - %d\n", getpid());
    exit(EXIT_SUCCESS);
  } else {
    pid_t ret = waitpid(child_pid, &status, WUNTRACED | WCONTINUED);
    if (ret == -1) perror("waitpid");
    if (WIFEXITED(status))
      printf("Child exited, status - %d\n", WEXITSTATUS(status));
    if (WEXITSTATUS(status) == 0) printf("%s\n", sh_mem);
  }
  shmdt(sh_mem);
  shmctl(segment_id, IPC_RMID, 0);
  exit(EXIT_SUCCESS);
}
出力:
Segment ID 1540195
Attached at 0x7fd825a25000
Hello there!
child pid - 27291
Child exited, status - 0
NEW DATA Stored by Child Process
