web-dev-qa-db-ja.com

複数のプロセス間でPOSIXセマフォを共有する

2つの子プロセスを作成する必要があります。それぞれがexecvp aterを呼び出してフォークし、実行可能ファイルはそれらの間でPOSIXセマフォを共有します。

共有メモリを作成する必要がありますか、それとも名前付きセマフォを実装する必要がありますか?

次のリンクから2つの回答を得ました。

  1. フォークされた子プロセスは同じセマフォを使用しますか?
  2. 共有メモリを使用してプロセス間でセマフォを共有する方法

しかし、実装の進め方について混乱しています。

12
dothermitian

共有メモリを作成する必要がありますか、それとも名前付きセマフォを実装する必要がありますか?

どちらの方法でも機能します。メモリを割り当てたり、共有メモリセグメントを設定したりする必要がないため、私は個人的に名前付きセマフォを好みますが、1つを選んでそれを使用してください。名前付きセマフォを作成して使用するためのインターフェースは、私の意見ではもっとフレンドリーです。

名前付きセマフォを使用したシナリオ例では、次のようになります。

  • sem_open(3)を使用して、親プロセスでセマフォを作成して初期化します。子プロセスが知っている既知の名前を付けます。この名前は、システム内のセマフォを見つけるために使用されます。
  • 親のセマフォを使用しないので、閉じてください。
  • フォークして実行
  • セマフォをsem_unlink(3)でリンク解除します。これは正確に1回実行する必要があります。それは実際にはどこでもかまいません(セマフォオブジェクトへの参照を持つすべてのプロセスがそれを実行できます)。他のプロセスがまだ開いている場合は、セマフォのリンクを解除しても問題ありません。セマフォは、他のすべてのプロセスがセマフォを閉じたときにのみ破棄されますが、名前はすぐに削除されるため、新しいプロセスは見つけられず、セマフォを開きます。

子プロセスは、既知の名前でsem_open(3)を呼び出して、セマフォへの参照を見つけて取得します。プロセスがセマフォで完了したら、sem_close(3)でプロセスを閉じる必要があります。

以下は、今説明した例です。親プロセスが名前付きセマフォを作成し、fork +が2つの子プロセスを実行します。それぞれがセマフォを見つけて開き、それを使用して互いに同期します。

親が_./sem_chld_バイナリをフォークして実行することを前提としています。セマフォの名前はスラッシュで始まり、スラッシュ以外の1つ以上の文字が続く必要があることに注意してください(_man sem_overview_を参照)。この例では、セマフォの名前は_/semaphore_example_です。

親プロセスのコードは次のとおりです。

_#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SEM_NAME "/semaphore_example"
#define SEM_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
#define INITIAL_VALUE 1

#define CHILD_PROGRAM "./sem_chld"

int main(void) {

    /* We initialize the semaphore counter to 1 (INITIAL_VALUE) */
    sem_t *semaphore = sem_open(SEM_NAME, O_CREAT | O_EXCL, SEM_PERMS, INITIAL_VALUE);

    if (semaphore == SEM_FAILED) {
        perror("sem_open(3) error");
        exit(EXIT_FAILURE);
    }

    /* Close the semaphore as we won't be using it in the parent process */
    if (sem_close(semaphore) < 0) {
        perror("sem_close(3) failed");
        /* We ignore possible sem_unlink(3) errors here */
        sem_unlink(SEM_NAME);
        exit(EXIT_FAILURE);
    }

    pid_t pids[2];
    size_t i;

    for (i = 0; i < sizeof(pids)/sizeof(pids[0]); i++) {
        if ((pids[i] = fork()) < 0) {
            perror("fork(2) failed");
            exit(EXIT_FAILURE);
        }

        if (pids[i] == 0) {
            if (execl(CHILD_PROGRAM, CHILD_PROGRAM, NULL) < 0) {
                perror("execl(2) failed");
                exit(EXIT_FAILURE);
            }
        }
    }

    for (i = 0; i < sizeof(pids)/sizeof(pids[0]); i++)
        if (waitpid(pids[i], NULL, 0) < 0)
            perror("waitpid(2) failed");

    if (sem_unlink(SEM_NAME) < 0)
        perror("sem_unlink(3) failed");

    return 0;
}
_

sem_unlink(3)は、両方の子が終了した後に呼び出されることに注意してください。これは必須ではありませんが、セマフォのリンクを解除する親プロセスと、セマフォを起動して開く両方の子プロセスとの間に競合状態が発生する前に呼び出された場合。ただし、一般的には、必要なすべてのプロセスがセマフォを開いており、新しいプロセスがそれを見つける必要がないことがわかったらすぐにリンクを解除できます。

これが_sem_chld_のコードです。これは、共有セマフォの使用法を示す小さなおもちゃのプログラムです。

_#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#define SEM_NAME "/semaphore_example"
#define ITERS 10

int main(void) {
    sem_t *semaphore = sem_open(SEM_NAME, O_RDWR);
    if (semaphore == SEM_FAILED) {
        perror("sem_open(3) failed");
        exit(EXIT_FAILURE);
    }

    int i;
    for (i = 0; i < ITERS; i++) {
        if (sem_wait(semaphore) < 0) {
            perror("sem_wait(3) failed on child");
            continue;
        }

        printf("PID %ld acquired semaphore\n", (long) getpid());

        if (sem_post(semaphore) < 0) {
            perror("sem_post(3) error on child");
        }

        sleep(1);
    }

    if (sem_close(semaphore) < 0)
        perror("sem_close(3) failed");

    return 0;
}
_

セマフォ名を共通のヘッダーファイルで定義し、各プログラムのコードに含めることで、2つのソースファイル間でセマフォ名を同期させる必要をなくすことができます。

この例ではエラー処理は理想的ではないことに注意してください(これは単なる例です)。改善の余地はたくさんあります。必要に応じてこの例を変更する場合は、適切なエラー処理を忘れないようにするためだけにあります。

17

共有メモリのアプローチもここで機能します。ここで唯一親プロセスが共有メモリを初期化する必要があるということです。このコードには2つの子ではなく1つのバグがあるようです。親関数はここで3つの子プロセスをforkします。内部にbreakステートメントがあるはずです

    if (pids[i] == 0) {
        if (execl(CHILD_PROGRAM, CHILD_PROGRAM, NULL) < 0) {
            perror("execl(2) failed");
            exit(EXIT_FAILURE);
        }
     break; //this is required
  }
0
user3871844