web-dev-qa-db-ja.com

CのフォークされたプロセスでPOSIXセマフォを使用する方法は?

複数のプロセスをフォークしてから、それらにセマフォを使用したいと思います。ここに私が試したものがあります:

sem_init(&sem, 1, 1);   /* semaphore*, pshared, value */
.
.
.
if(pid != 0){ /* parent process */
    wait(NULL); /* wait all child processes */

    printf("\nParent: All children have exited.\n");
    .
    .
    /* cleanup semaphores */
    sem_destroy(&sem);      
    exit(0);
}
else{ /* child process */
    sem_wait(&sem);     /* P operation */
    printf("  Child(%d) is in critical section.\n",i);
    sleep(1);
    *p += i%3;  /* increment *p by 0, 1 or 2 based on i */
    printf("  Child(%d) new value of *p=%d.\n",i,*p);
    sem_post(&sem);     /* V operation */
    exit(0);
}

出力は次のとおりです。

child(0)forked 
 child(1)forked 
 Child(0)はクリティカルセクションにあります。
 Child(1)はクリティカルセクションにあります。
 child( 2)forked 
 Child(2)はクリティカルセクションにあります。
 child(3)forked 
 Child(3)はクリティカルセクションにあります。
 child(4) forked 
 Child(4)はクリティカルセクションにあります。
 Child(0)新しい値* p = 0。
 Child(1)新しい値* p = 1。
 Child(2)* p = 3。
 Child(3)の新しい値* p = 3。
 
 Child(4)の新しい値* p = 4。
 Parent:すべての子が終了しました。

これは、セマフォが想定どおりに機能しなかったことを明確に示しています。フォークされたプロセスでセマフォを使用する方法を説明できますか?

21
Varaquilex

あなたが直面している問題は、sem_init()関数の誤解です。 manual page を読むと、これが表示されます:

Pshared引数は、このセマフォをプロセスのスレッド間で共有するか、プロセス間で共有するかを示します。

この時点まで読み終わったら、psharedのゼロ以外の値によってセマフォがプロセス間セマフォになると考えるでしょう。しかし、これは間違っています。読み続ける必要があり、共有メモリ領域にセマフォを配置する必要があることを理解できます。これを行うには、以下に示すように、いくつかの機能を使用できます。

Psharedがゼロ以外の場合、セマフォはプロセス間で共有され、共有メモリの領域に配置する必要があります(shm_open(3)、mmap(2)、およびshmget(2)を参照)。 (fork(2)によって作成された子は親のメモリマッピングを継承するため、セマフォにもアクセスできます。)共有メモリ領域にアクセスできるプロセスは、sem_post(3)、sem_wait(3)などを使用してセマフォを操作できます。 。

このアプローチは他のアプローチよりも複雑なアプローチであるため、sem_open()の代わりにsem_init()を使用するように人々に勧めたいと思います。

以下に、完全なプログラムが以下を示していることがわかります。

  • フォークされたプロセス間で共有メモリを割り当て、共有変数を使用する方法。
  • 共有メモリ領域でセマフォを初期化する方法。複数のプロセスで使用されます。
  • 複数のプロセスをフォークし、すべての子が終了するまで親を待機させる方法。
_#include <stdio.h>          /* printf()                 */
#include <stdlib.h>         /* exit(), malloc(), free() */
#include <sys/types.h>      /* key_t, sem_t, pid_t      */
#include <sys/shm.h>        /* shmat(), IPC_RMID        */
#include <errno.h>          /* errno, ECHILD            */
#include <semaphore.h>      /* sem_open(), sem_destroy(), sem_wait().. */
#include <fcntl.h>          /* O_CREAT, O_EXEC          */


int main (int argc, char **argv){
    int i;                        /*      loop variables          */
    key_t shmkey;                 /*      shared memory key       */
    int shmid;                    /*      shared memory id        */
    sem_t *sem;                   /*      synch semaphore         *//*shared */
    pid_t pid;                    /*      fork pid                */
    int *p;                       /*      shared variable         *//*shared */
    unsigned int n;               /*      fork count              */
    unsigned int value;           /*      semaphore value         */

    /* initialize a shared variable in shared memory */
    shmkey = ftok ("/dev/null", 5);       /* valid directory name and a number */
    printf ("shmkey for p = %d\n", shmkey);
    shmid = shmget (shmkey, sizeof (int), 0644 | IPC_CREAT);
    if (shmid < 0){                           /* shared memory error check */
        perror ("shmget\n");
        exit (1);
    }

    p = (int *) shmat (shmid, NULL, 0);   /* attach p to shared memory */
    *p = 0;
    printf ("p=%d is allocated in shared memory.\n\n", *p);

    /********************************************************/

    printf ("How many children do you want to fork?\n");
    printf ("Fork count: ");
    scanf ("%u", &n);

    printf ("What do you want the semaphore value to be?\n");
    printf ("Semaphore value: ");
    scanf ("%u", &value);

    /* initialize semaphores for shared processes */
    sem = sem_open ("pSem", O_CREAT | O_EXCL, 0644, value); 
    /* name of semaphore is "pSem", semaphore is reached using this name */

    printf ("semaphores initialized.\n\n");


    /* fork child processes */
    for (i = 0; i < n; i++){
        pid = fork ();
        if (pid < 0) {
        /* check for error      */
            sem_unlink ("pSem");   
            sem_close(sem);  
            /* unlink prevents the semaphore existing forever */
            /* if a crash occurs during the execution         */
            printf ("Fork error.\n");
        }
        else if (pid == 0)
            break;                  /* child processes */
    }


    /******************************************************/
    /******************   PARENT PROCESS   ****************/
    /******************************************************/
    if (pid != 0){
        /* wait for all children to exit */
        while (pid = waitpid (-1, NULL, 0)){
            if (errno == ECHILD)
                break;
        }

        printf ("\nParent: All children have exited.\n");

        /* shared memory detach */
        shmdt (p);
        shmctl (shmid, IPC_RMID, 0);

        /* cleanup semaphores */
        sem_unlink ("pSem");   
        sem_close(sem);  
        /* unlink prevents the semaphore existing forever */
        /* if a crash occurs during the execution         */
        exit (0);
    }

    /******************************************************/
    /******************   CHILD PROCESS   *****************/
    /******************************************************/
    else{
        sem_wait (sem);           /* P operation */
        printf ("  Child(%d) is in critical section.\n", i);
        sleep (1);
        *p += i % 3;              /* increment *p by 0, 1 or 2 based on i */
        printf ("  Child(%d) new value of *p=%d.\n", i, *p);
        sem_post (sem);           /* V operation */
        exit (0);
    }
}
_

[〜#〜] output [〜#〜]

_./a.out 
shmkey for p = 84214791
p=0 is allocated in shared memory.

How many children do you want to fork?
Fork count: 6 
What do you want the semaphore value to be?
Semaphore value: 2
semaphores initialized.

  Child(0) is in critical section.
  Child(1) is in critical section.
  Child(0) new value of *p=0.
  Child(1) new value of *p=1.
  Child(2) is in critical section.
  Child(3) is in critical section.
  Child(2) new value of *p=3.
  Child(3) new value of *p=3.
  Child(4) is in critical section.
  Child(5) is in critical section.
  Child(4) new value of *p=4.
  Child(5) new value of *p=6.

Parent: All children have exited.
_

ftok()が失敗すると-1を返すため、shmkeyを確認することは悪くありません。ただし、複数の共有変数があり、ftok()関数が複数回失敗した場合、値_-1_のshmkeyを持つ共有変数は、共有の同じ領域に存在します。一方の変更が他方に影響を与えるメモリ。そのため、プログラムの実行が面倒になります。これを回避するには、ftok()が-1を返すかどうかを確認することをお勧めします(私がやったように画面に印刷するよりも、ソースコードをチェックインする方が良いです。衝突です)。

セマフォの宣言方法と初期化方法に注意してください。質問で行ったこととは異なります(_sem_t sem_ vs _sem_t* sem_)。さらに、この例に表示されているとおりに使用する必要があります。 _sem_t*_を定義して、sem_init()で使用することはできません。

59
Varaquilex

Linuxの最小匿名_sem_init_ + mmap _MAP_ANONYMOUS_ example

_sem_open_のようにグローバル名前空間を汚染しないため、このセットアップが気に入っています。

唯一の欠点は、_MAP_ANONYMOUS_がPOSIXではなく、代替品がわからないことです。 匿名共有メモリ? _shm_open_は、たとえば_sem_open_。

main.c:

_#define _GNU_SOURCE
#include <assert.h>
#include <semaphore.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv) {
    pid_t pid;
    typedef struct {
        sem_t sem;
        int i;
    } Semint;

    Semint *semint;
    size_t size = sizeof(Semint);
    semint = (Semint *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0);
    assert(semint != MAP_FAILED);
    /* 1: shared across processes
     * 0: initial value, wait locked until one post happens (making it > 0)
     */
    sem_init(&semint->sem, 1, 0);
    semint->i = 0;
    pid = fork();
    assert(pid != -1);
    if (pid == 0) {
        sleep(1);
        semint->i = 1;
        msync(&semint->sem, size, MS_SYNC);
        sem_post(&semint->sem);
        exit(EXIT_SUCCESS);
    }
    if (argc == 1) {
        sem_wait(&semint->sem);
    }
    /* Was modified on the other process. */
    assert(semint->i == 1);
    wait(NULL);
    sem_destroy(&semint->sem);
    assert(munmap(semint, size) != -1);
    return EXIT_SUCCESS;
}
_

コンパイル:

_gcc -g -std=c99 -Wall -Wextra -o main main.c -lpthread
_

_sem_wait_で実行:

_./main
_

_sem_wait_なしで実行:

_./main 1
_

この同期なしでは、assertは失敗する可能性が非常に高くなります。これは、子が1秒間スリープするためです。

_main: main.c:39: main: Assertion `semint->i == 1' failed.
_

Ubuntu 18.04でテスト済み。 GitHubアップストリーム