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

セマフォの操作を行なう

2012.08.10

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

セマフォを操作する関数にはsemop関数以外に、semget関数とsemctl関数があります。なお、セマフォの操作手順についてはsemget関数をご覧ください。

ここではセマフォによる、2つのプロセスの待ち合わせと、3つ以上のプロセスの待ち合わせ及び、排他制御の手順を説明します。なお、ロックとはプロセスを休眠状態(停止)にする操作で、アンロックとはプロセスを目覚めさせる操作です。

2つのプロセスの待ち合わせは、次の手順でセマフォ操作を行います。

  1. セマフォを2つ用意して、両方共、0で初期化します。
  2. それぞれのプロセスは、自分のセマフォを決めます。例えば、プロセスAはセマフォ0、プロセスBはセマフォ1というようにします。
  3. 待ち合わせのところまで実行したら、相手のプロセスのセマフォを「アンロック」、自分のセマフォを「ロック」します。例えば、プロセスAの場合は、セマフォ1を「アンロック」、セマフォ0を「ロック」します。

3つ以上のプロセスの待ち合わせは、次の手順でセマフォ操作を行います。(例題プログラムはsemctl関数をご覧ください。)

  1. セマフォを1つ用意して、1で初期化します。
  2. 1つのプロセス以外は自プロセスを「ロック」します。そして、1つのプロセスは待ち合わせのところまで実行したら、他のプロセスを「アンロック」します。

排他制御は、次の手順でセマフォ操作を行います。(例題プログラムはsemget関数をご覧ください。)

  1. セマフォを1つ用意して、1で初期化します。
  2. 排他制御を行うところで「ロック」を行い、排他制御を解除するところで「アンロック」を行います。

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

#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);

semidは操作するセマフォ集合のセマフォIDを指定します。
*sopsはセマフォ操作の内容を指定します。連続してセマフォ操作を行う場合は、その数だけの要素の配列にします。(1つの操作だけの場合は配列でなくても構いません。)
nsopsは第2引数の*sopsの要素数を指定します。

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

第2引数の*sopsの型はsembuf構造体で、次の様に定義してあります。

struct sembuf
{
  unsigned short int sem_num;     /* セマフォ番号 */
  short int sem_op;               /* セマフォ操作 */
  short int sem_flg;              /* 操作フラグ */
};

sem_numには操作するセマフォをセマフォ番号として指定します。これは、操作するセマフォ集合中の何番目のセマフォかということを示します。セマフォ番号は0から始まります。

sem_opには整数値で操作内容を指定します。整数の値により、次のような操作になります。

sem_opの値 操作内容
sem_op > 0 指定した値をセマフォ値(semval)に加算します。この操作は必ず実行でき、プロセスの休眠状態(停止)は起こりません。(アンロックの操作)
sem_op = 0 セマフォ値(semval)が0になるまで待ち(プロセスの停止)ます。(ロックの操作)
sem_op < 0 セマフォ値(semval)が指定した値の絶対値以上の場合は、セマフォ値から指定した値の絶対値を減算し、プロセスを休眠状態(停止)にします。セマフォ値が指定した値の絶対値以上になると、プロセスは目覚めます。(ロックの操作)

sem_flgには休眠状態(停止)を避ける為のIPC_NOWAITと、シグナル等でプロセスが終了した場合に、セマフォ値を元に戻す為のSEM_UNDOが設定できます。どちらも指定しない場合は0を指定します。

次の例題プログラムは、2つのプロセスの待ち合わせを行っています。子プロセス1のファイル出力完了を待って、子プロセス2がファイルの内容を表示しています。

プログラム 例

#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 SemNum, 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;
  unsigned short     vals[2] = {0, 0};  /* セマフォの初期値 */
  char               *file = './sem.txt';
  int                child_cnt;
  int                line_cnt;

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

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

  /* 1つ目の子プロセスを生成 */
  if (fork() == 0) {
    /* 子プロセス */
    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);

    /* 待ち合わせ */
    MySemop(semid, 1, UNLOCK);    /* 子プロセス2をアンロック */
    MySemop(semid, 0, LOCK);      /* 自プロセスをロック */

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

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

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

    /* 待ち合わせ */
    MySemop(semid, 1, LOCK);      /* 自プロセスをロック */
    MySemop(semid, 0, UNLOCK);    /* 子プロセス1をアンロック */

    if ((fp = fopen(file, 'r')) == NULL) {
      perror('子プロセス2 ');
      exit(EXIT_FAILURE);
    }
    printf('子プロセス2:ファイルの内容を表示します\n');
    while (fgets(buff, 1024, fp) != NULL) {
      printf('%s', buff);
    }
    fclose(fp);

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

  /* 親プロセス。子プロセスの終了を待つ */
  for (child_cnt = 0; child_cnt < 2; ++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_semnum, int p_op)
{
  struct sembuf    sops[1];

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

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

  return;
}

例の実行結果

$ ./semop.exe
子プロセス1開始
子プロセス2開始
子プロセス2:ファイルの内容を表示します
子プロセス1終了
子プロセス1のメッセージ1
子プロセス1のメッセージ2
子プロセス1のメッセージ3
子プロセス1のメッセージ4
子プロセス1のメッセージ5
子プロセス2終了
親プロセス終了
$
$ cat sem.txt
子プロセス1のメッセージ1
子プロセス1のメッセージ2
子プロセス1のメッセージ3
子プロセス1のメッセージ4
子プロセス1のメッセージ5
$

関連記事