semget関数は、既存セマフォ集合のセマフォIDあるいは、新規作成したセマフォ集合のセマフォIDを取得します。セマフォとは、元々は「手旗信号」の意味で、それから派生した鉄道の腕木信号に由来します。これにより、プロセス間の待ち合わせと排他制御を行うことができます。

セマフォを操作する関数にはsemget関数以外に、semctl関数とsemop関数があります。プロセス間の待ち合わせと、排他制御の手順についてはsemop関数をご覧ください。

セマフォは、次の手順で操作します。

  1. semget関数でセマフォ集合のセマフォIDを取得します。なお、セマフォ集合は新規作成もできます。
  2. semctl関数でセマフォに初期値を設定します。
  3. それぞれのプロセスでsemop関数でセマフォのロック/アンロックを行い、プロセス間の待ち合わせや排他制御を行います。
  4. semctl関数でマフォ集合を削除します。

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

#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

keyは取得するセマフォ集合のキーを指定します。
nsemsはセマフォの数を指定します。
semflgはオプションを指定します。

戻り値として、処理が成功した場合は、セマフォIDが、失敗した場合は-1を返します。なお、セマフォIDは正の整数値です。

新規にセマフォ集合を作成するには、次の2つの方法があります。なお、セマフォ集合内に作成するセマフォの数は第2引数のnsemsで指定します。

  1. 第1引数のkeyにIPC_PRIVATEを指定します。
  2. 第1引数のkeyにユニークな値を指定し、第3引数のsemflgにIPC_CREATを指定します。なお、semflgにIPC_CREATとIPC_EXCLを指定すると、keyに対するセマフォ集合が既に存在していた場合にエラーになります。

第3引数のsemflgの下位9ビットは、そのセマフォの所有者、グループ、他人に対するアクセス許可の定義として使用します。

次の例題プログラムは、3つの子プロセスによるファイル出力を排他制御しています。

プログラム 例

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/sem.h>
#define LOCK -1
#define UNLOCK 1
/* セマフォ操作(ロック/アンロック) */
void MySemop(int SemId, int Op);

int main()
{
  FILE               *fp;
  int                semid;
  union semun {
    int              val;     /* SETVAL の値 */
    struct semid_ds  *buf;    /* IPC_STAT, IPC_SET 用のバッファ */
    unsigned short   *array;  /* GETALL, SETALL 用の配列 */
  } ctl_arg;
  char               *file = './sem.txt';
  int                child_cnt;
  int                line_cnt;

  if ((fp = fopen(file, 'w')) == NULL) {
    perror('main : fopen ');
    exit(EXIT_FAILURE);
  }
  setvbuf(fp, NULL, _IONBF, 0);

  /* 1つのセマフォを持つセマフォ集合を新規作成 */
  if ((semid = semget(IPC_PRIVATE, 1, 0600)) == -1){
    perror('main : semget ');
    exit(EXIT_FAILURE);
  }

  /* セマフォに初期値(1)を設定 */
  ctl_arg.val = 1;
  if (semctl(semid, 0, SETVAL, ctl_arg) == -1){
    perror('main : semctl ');
    exit(EXIT_FAILURE);
  }

  /* 子プロセスを3個生成 */
  for (child_cnt = 0; child_cnt < 3; ++child_cnt) {
    if (fork() == 0) {
      /* 子プロセス */
      printf('子プロセス%d開始\n', child_cnt + 1);

      MySemop(semid, LOCK);            /* ロック */
      for (line_cnt = 1; line_cnt <= 5; ++line_cnt) {
        fprintf(fp, '子プロセス%dのメッセージ%d\n', child_cnt + 1, line_cnt);
        sleep(1);
      }
      MySemop(semid, UNLOCK);          /* アンロック */

      printf('子プロセス%d終了\n', child_cnt + 1);
      exit(EXIT_SUCCESS);
    }
  }

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

  /* セマフォを削除 */
  if (semctl(semid, 0, IPC_RMID, ctl_arg) == -1){
    perror('main : semctl ');
    exit(EXIT_FAILURE);
  }

  fclose(fp);

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

/* セマフォ操作(ロック/アンロック) */
void MySemop(int p_semid, int p_op)
{
  struct sembuf    sops[1];

  sops[0].sem_num = 0;                 /* セマフォ番号 */
  sops[0].sem_op = p_op;               /* セマフォ操作 */
  sops[0].sem_flg = 0;                 /* 操作フラグ */

  if (semop(p_semid, sops, 1) == -1) {
    perror('MySemop ');
    exit(EXIT_FAILURE);
  }

  return;
}

例の実行結果

$ ./semget.exe
子プロセス1開始
子プロセス2開始
子プロセス3開始
子プロセス1終了
子プロセス2終了
子プロセス3終了
親プロセス終了
$
$ cat sem.txt
子プロセス1のメッセージ1
子プロセス1のメッセージ2
子プロセス1のメッセージ3
子プロセス1のメッセージ4
子プロセス1のメッセージ5
子プロセス2のメッセージ1
子プロセス2のメッセージ2
子プロセス2のメッセージ3
子プロセス2のメッセージ4
子プロセス2のメッセージ5
子プロセス3のメッセージ1
子プロセス3のメッセージ2
子プロセス3のメッセージ3
子プロセス3のメッセージ4
子プロセス3のメッセージ5
$