C 言語で共有メモリを割り当てるために shmget を使用する

胡金庫 2023年10月12日
C 言語で共有メモリを割り当てるために shmget を使用する

この記事では、shmget 関数を使って C 言語で共有メモリを確保する方法を複数紹介します。

C 言語で共有メモリを割り当てるには shmget を使用する

共有メモリは、2つ以上のプロセスがユーザースペースでデータを交換し、高速に通信することを可能にするプロセス間通信の方法の 1つです。共有メモリは、複数のプロセスがメモリ内の同じ領域を共有し、必要に応じてこのセグメントを変更/アクセスできることを意味します。

以下の例で説明するインターフェースは、System V の共有メモリと呼ばれるもので、4つの関数を使って提供されます。これは、shmgetshmatshmdtshmctl の 4つの関数を使って提供されます。

  • shmget は新しい共有メモリセグメントを作成したり、既に作成されたメモリセグメントの識別子を取得したりするのに使われます。
  • shmget は新しい共有メモリセグメントを作成したり、既に作成されたメモリセグメントの識別子を取得したりするために用いられます。
  • shmdt は、与えられたメモリセグメントを切り離すために用いることができます。
  • shmctl は、複数のオプションを指定してセグメントを変更したり、解放したりするために使用します。

次のサンプルコードは、新しい共有セグメントを作成し、そこにテキストを書き込むプロセスのために shmgetshmat 関数の基本的な使い方を実装したものです。関数 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 が有効なポインタを返すと、それを任意のメモリアドレスとして扱い、必要に応じて操作することができます。最後に、shmdtshmctl 関数が呼び出され、与えられたセグメントを切り離してメモリを解放します。割り当てられたメモリセグメントに対して 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
著者: 胡金庫
胡金庫 avatar 胡金庫 avatar

DelftStack.comの創設者です。Jinku はロボティクスと自動車産業で8年以上働いています。自動テスト、リモートサーバーからのデータ収集、耐久テストからのレポート作成が必要となったとき、彼はコーディングスキルを磨きました。彼は電気/電子工学のバックグラウンドを持っていますが、組み込みエレクトロニクス、組み込みプログラミング、フロントエンド/バックエンドプログラミングへの関心を広げています。

LinkedIn Facebook

関連記事 - C Memory