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

セマフォの制御操作を行なう

2012.08.10

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

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

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

#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …);

semidは操作するセマフォ集合のセマフォIDを指定します。
semnumはセマフォ集合内の操作するセマフォを番号で指定します。セマフォ番号は0から始まります。
cmdは操作の種類を表すコマンドを指定します。コマンドの種類により、第4引数に次のsemun共用体を指定します。

union semun {
  int              val;    /* SETVAL の値 */
  struct semid_ds *buf;    /* IPC_STAT, IPC_SET 用のバッファ */
  unsigned short  *array;  /* GETALL, SETALL 用の配列 */
};

正常終了した場合の戻り値は、コマンドの種類により異なります。失敗した場合は-1を返します。

第3引数のcmdには、次のものがあります。

コマンド 内容
IPC_STAT 第1引数に指定したセマフォIDに関するカーネルデータ構造体の情報を、第4引数に指定したsemun共用体の*bufで指定されたsemid_ds構造体へコピーします。第2引数のsemnumは無視します。
IPC_SET 第4引数に指定したsemun共用体の*bufで指定されたsemid_ds構造体のメンバのいくつかの値を、第1引数に指定したセマフォIDに関連づいたカーネルデータ構造体に書き込みます。
IPC_RMID 第1引数に指定したセマフォIDに対応するセマフォ集合を削除し、その集合上のブロック(休眠状態)されている全てのプロセスを目覚めさせます。
GETALL 第1引数に指定したセマフォIDに対応するセマフォ集合の全てのセマフォの値(semvalの値)を第4引数に指定したsemun共用体の*arrayに設定します。第2引数のsemnumは無視します。
GETNCNT 第2引数のsemnumに対応するセマフォの値(semvalの値)が増加するのを待っているプロセスの数(semncntの値)を戻り値として返します。
GETPID 第2引数のsemnumに対応するセマフォに対して、最後にsemop関数を実行したプロセスのプロセスIDを戻り値として返します。
GETVAL 第2引数のsemnumに対応するセマフォの値(semval値の)を戻り値として返します。
GETZCNT 第2引数のsemnumに対応するセマフォの値が0になるのを待っているプロセスの数(semzcntの値)を戻り値として返します。
SETALL 第1引数に指定したセマフォIDに対応するセマフォ集合の全てのセマフォに、第4引数に指定したsemun共用体の*arrayの値を設定します。セマフォの値の変更により、休眠状態のプロセスは起こされます。第2引数のsemnumは無視します。
SETVAL 第2引数のsemnumに対応するセマフォに、第4引数に指定したsemun共用体のvalの値を設定します。セマフォの値の変更により、休眠状態のプロセスは起こされます。第2引数のsemnumは無視します。

次の例題プログラムは、3つのプロセスの待ち合わせを行っています。子プロセス1のファイル出力完了を待って、子プロセス2が行数をカウントして表示し、子プロセス3が容量(バイト数)を表示しています。

プログラム 例

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

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;

  /* 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);
  }

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

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

    if ((fp = fopen(file, 'w')) == NULL) {
      perror('子プロセス1 ');
      exit(EXIT_FAILURE);
    }
    for (line_cnt = 1; line_cnt <= 5; ++line_cnt) {
      fprintf(fp, '子プロセス1のメッセージ%d\n', line_cnt);
      sleep(1);
    }
    fclose(fp);

    /* 待ち合わせ解除 */
    ctl_arg.val = 0;
    if (semctl(semid, 0, SETVAL, ctl_arg) == -1){
      perror('子プロセス1 ');
      exit(EXIT_FAILURE);
    }

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

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

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

    /* 待ち合わせ */
    MySemop(semid, WAIT);    /* 自プロセスをロック */

    if ((fp = fopen(file, 'r')) == NULL) {
      perror('子プロセス2 ');
      exit(EXIT_FAILURE);
    }
    while ((in_char = fgetc(fp)) != EOF) {
      if (in_char == '\n') {
        ++line_cnt;          /* 行数カウント */
      }
    }
    fclose(fp);
    printf('子プロセス2:%sの行数は%d行です\n', file, line_cnt);

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

  /* 3つ目の子プロセスを生成 */
  if (fork() == 0) {
    /* 子プロセス */
    int  in_char;
    int  byte_cnt = 0;

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

    /* 待ち合わせ */
    MySemop(semid, WAIT);    /* 自プロセスをロック */

    if ((fp = fopen(file, 'r')) == NULL) {
      perror('子プロセス3 ');
      exit(EXIT_FAILURE);
    }
    while ((in_char = fgetc(fp)) != EOF) {
      ++byte_cnt;            /* 文字数(バイト)数カウント */
    }
    fclose(fp);
    printf('子プロセス3:%sの容量は%dバイトです\n', file, byte_cnt);

    printf('子プロセス3終了\n');
    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);
  }

  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;
}

例の実行結果

$ ./semctl.exe
子プロセス1開始
子プロセス2開始
子プロセス3開始
子プロセス2:./sem.txtの行数は5行です
子プロセス2終了
子プロセス3:./sem.txtの容量は190バイトです
子プロセス3終了
子プロセス1終了
親プロセス終了
$
$ cat sem.txt
子プロセス1のメッセージ1
子プロセス1のメッセージ2
子プロセス1のメッセージ3
子プロセス1のメッセージ4
子プロセス1のメッセージ5
$

関連記事