Wie verwendet man POSIX-Semaphoren für gegabelte Prozesse in C?

Lesezeit: 8 Minuten

Benutzer-Avatar
Varaquilex

Ich möchte mehrere Prozesse forken und dann ein Semaphor für sie verwenden. Folgendes habe ich versucht:

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

Und die Ausgabe ist:

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

  Child(4) new value of *p=4.
Parent: All children have exited.

Dies bedeutet eindeutig, dass die Semaphore nicht so funktionierte, wie sie sollte. Können Sie erklären, wie ich Semaphoren für gegabelte Prozesse verwenden soll?

Das Problem, mit dem Sie konfrontiert sind, ist das Missverständnis von sem_init() Funktion. Beim Lesen der Handbuchseite
Sie werden dies sehen:

Das Argument pshared gibt an, ob dieses Semaphor von den Threads eines Prozesses oder von Prozessen gemeinsam genutzt werden soll.

Wenn Sie bis zu diesem Punkt gelesen haben, werden Sie denken, dass der Nicht-Null-Wert von pshared das Semaphor zu einem prozessübergreifenden Semaphor macht. Dies ist jedoch falsch. Wenn Sie weiterlesen, werden Sie verstehen, dass Sie die Semaphore in einem gemeinsam genutzten Speicherbereich lokalisieren müssen. Dazu können mehrere Funktionen verwendet werden, wie Sie unten sehen können:

Wenn pshared ungleich Null ist, dann wird das Semaphor von Prozessen gemeinsam genutzt und sollte sich in einem Bereich des gemeinsam genutzten Speichers befinden (siehe shm_open(3), mmap(2) und shmget(2)). (Da ein durch fork(2) erzeugtes Kind die Speicherzuordnungen seines Elternteils erbt, kann es auch auf die Semaphore zugreifen.) Jeder Prozess, der auf die gemeinsam genutzte Speicherregion zugreifen kann, kann mit sem_post(3), sem_wait(3) usw. auf der Semaphore operieren .

Ich finde diesen Ansatz komplizierter als andere, deshalb möchte ich die Leute dazu ermutigen, ihn zu verwenden sem_open() Anstatt von sem_init().

Unten sehen Sie ein vollständiges Programm, das Folgendes veranschaulicht:

  • So weisen Sie gemeinsam genutzten Speicher zu und verwenden gemeinsam genutzte Variablen zwischen gegabelten Prozessen.
  • So wird ein Semaphor in einem gemeinsam genutzten Speicherbereich initialisiert und von mehreren Prozessen verwendet.
  • Wie man mehrere Prozesse verzweigt und den Elternteil warten lässt, bis alle seine Kinder beendet sind.
#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);
    }
}

AUSGANG

./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.

Es ist nicht schlecht zu überprüfen shmkey seit wann ftok() fehlschlägt, gibt es -1 zurück. Wenn Sie jedoch mehrere gemeinsam genutzte Variablen haben und wenn die ftok() Funktion schlägt mehrmals fehl, die gemeinsam genutzten Variablen, die a shmkey mit Wert -1 befinden sich in derselben Region des gemeinsam genutzten Speichers, was zu einer Änderung in einer führt, die sich auf die andere auswirkt. Daher wird die Programmausführung unordentlich. Um dies zu vermeiden, wird besser geprüft, ob die ftok()
gibt -1 zurück oder nicht (es ist besser, den Quellcode einzuchecken, als wie ich auf dem Bildschirm zu drucken, obwohl ich Ihnen die Schlüsselwerte zeigen wollte, falls es zu einer Kollision kommt).

Achten Sie darauf, wie das Semaphor deklariert und initialisiert wird. Es ist anders als das, was Sie in der Frage getan haben (sem_t sem vs sem_t* sem). Darüber hinaus sollten Sie sie so verwenden, wie sie in diesem Beispiel erscheinen. Sie können nicht definieren sem_t* und verwende es darin sem_init().

  • Das Problem ist, dass einige Manpages nicht so explizit sind. Schauen Sie sich SUSv2 (zum Beispiel) an: If the pshared argument has a non-zero value, then the semaphore is shared between processes; in this case, any process that can access the semaphore sem can use sem for performing sem_wait(), sem_trywait(), sem_post(), and sem_destroy() operations. Es ist viel schwieriger zu verstehen, dass man Shared Memory verwenden sollte!

    – Jean-Baptiste Yunes

    26. Juni 2013 um 15:33 Uhr

  • leider… Ich habe 2 Hausaufgaben gebraucht, um das herauszufinden 🙂 Das ist es, was Linux meiner Meinung nach fehlt: Sie gehen davon aus, dass die Leute die Dinge bereits wissen, als ob sie Teil der Entwickler-Community wären. Auch hier sollte es meiner Meinung nach im Gegensatz zu diesen Handbüchern erklärend sein.

    – Varaquilex

    26. Juni 2013 um 17:51 Uhr


  • Ich denke, Sie sollten den gemeinsamen Speicher auch von den untergeordneten Prozessen trennen After a fork(2) the child inherits the attached shared memory segments (Mann Schmdt).

    – Chris

    2. Dezember 2013 um 11:42 Uhr

  • Ich hatte auch Mühe, dies herauszufinden. Dieser Beitrag hat wirklich gehalten. +1 auf die Frage und Antworten.

    – RootPhoenix

    2. Mai 2014 um 17:48 Uhr

  • @GNA Ich kenne den Kampf, ich dachte, die Leute könnten ein solches Beispiel verwenden 🙂 Vielen Dank für +1. Hoffe es hat geholfen.

    – Varaquilex

    2. Mai 2014 um 23:50 Uhr

Benutzer-Avatar
Ciro Santilli OurBigBook.com

Linux minimal anonym sem_init + mmap MAP_ANONYMOUS Beispiel

Ich mag dieses Setup, da es keinen globalen Namespace verschmutzt sem_open tut.

Der einzige Nachteil ist das MAP_ANONYMOUS ist nicht POSIX, und ich kenne keinen Ersatz: Anonymous shared memory? shm_open nimmt zum Beispiel eine globale Kennung wie sem_open.

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

Kompilieren:

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

Lauf mit sem_wait:

./main

Laufen ohne sem_wait:

./main 1

Ohne diese Synchronisierung wird die assert wird sehr wahrscheinlich scheitern, da das Kind eine ganze Sekunde schläft:

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

Getestet auf Ubuntu 18.04. GitHub-Upstream.

1365860cookie-checkWie verwendet man POSIX-Semaphoren für gegabelte Prozesse in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy