web-dev-qa-db-fra.com

Comment utiliser les sémaphores POSIX sur les processus forkés en C?

Je veux bifurquer plusieurs processus, puis utiliser un sémaphore dessus. Voici ce que j'ai essayé:

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

Et la sortie est:

enfant (0) bifurqué 
 enfant (1) bifurqué 
 L'enfant (0) est dans la section critique. 
 L'enfant (1) est dans la section critique. 
 enfant ( 2) fourche 
 Enfant (2) est dans la section critique. 
 Enfant (3) fourche 
 Enfant (3) est dans la section critique. 
 Enfant (4) fourche 
 L'enfant (4) est dans la section critique. 
 Enfant (0) nouvelle valeur de * p = 0. 
 Enfant (1) nouvelle valeur de * p = 1. 
 Enfant (2) nouvelle valeur de * p = 3. 
 Enfant (3) nouvelle valeur de * p = 3. 
 
 Enfant (4) nouvelle valeur de * p = 4. 
 Parent: Tous les enfants sont sortis.

Cela signifie clairement que le sémaphore n'a pas fonctionné comme il était censé le faire. Pouvez-vous expliquer comment je dois utiliser des sémaphores sur des processus bifurqués?

21
Varaquilex

Le problème auquel vous êtes confronté est l'incompréhension de la fonction sem_init(). Lorsque vous lisez page de manuel vous verrez ceci:

L'argument partagé indique si ce sémaphore doit être partagé entre les threads d'un processus ou entre les processus.

Si vous avez terminé la lecture jusqu'à ce point, vous penserez que la valeur non nulle de pshared fera le sémaphore inter-processus sémaphore. Mais c'est faux. Vous devriez continuer à lire et vous comprendrez que vous devez localiser le sémaphore dans une région de mémoire partagée. Pour ce faire, plusieurs fonctions peuvent être utilisées comme vous pouvez le voir ci-dessous:

Si pshared est différent de zéro, le sémaphore est partagé entre les processus et doit être situé dans une région de mémoire partagée (voir shm_open (3), mmap (2) et shmget (2)). (Puisqu'un enfant créé par fork (2) hérite des mappages de mémoire de son parent, il peut également accéder au sémaphore.) Tout processus pouvant accéder à la région de mémoire partagée peut fonctionner sur le sémaphore à l'aide de sem_post (3), sem_wait (3), etc. .

Je trouve cette approche plus compliquée que les autres, donc je veux encourager les gens à utiliser sem_open() au lieu de sem_init().

Ci-dessous, vous pouvez voir un programme complet illustrant ce qui suit:

  • Comment allouer de la mémoire partagée et utiliser des variables partagées entre des processus bifurqués.
  • Comment initialiser un sémaphore dans une région de mémoire partagée et est utilisé par plusieurs processus.
  • Comment bifurquer plusieurs processus et faire attendre le parent jusqu'à ce que tous ses enfants se terminent.
#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);
    }
}

[~ # ~] sortie [~ # ~]

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

Il n'est pas mauvais de vérifier shmkey car lorsque ftok() échoue, il renvoie -1. Cependant, si vous avez plusieurs variables partagées et si la fonction ftok() échoue plusieurs fois, les variables partagées qui ont un shmkey avec la valeur -1 Résideront dans la même région du mémoire partagée entraînant un changement de l'un affectant l'autre. Par conséquent, l'exécution du programme deviendra désordonnée. Pour éviter cela, il est préférable de vérifier si la ftok() renvoie -1 ou non (mieux vaut archiver le code source plutôt que d'imprimer à l'écran comme je l'ai fait, bien que je voulais vous montrer les valeurs clés au cas où il y aurait est une collision).

Faites attention à la façon dont le sémaphore est déclaré et initialisé. C'est différent de ce que vous avez fait dans la question (sem_t sem Vs sem_t* sem). De plus, vous devez les utiliser tels qu'ils apparaissent dans cet exemple. Vous ne pouvez pas définir sem_t* Et l'utiliser dans sem_init().

59
Varaquilex

Linux minimal anonyme sem_init + mmapMAP_ANONYMOUS exemple

J'aime cette configuration car elle ne pollue aucun espace de noms global comme sem_open Est-ce que.

Le seul inconvénient est que MAP_ANONYMOUS n'est pas POSIX, et je ne connais aucun remplaçant: Mémoire partagée anonyme?shm_open, par exemple, prend un identifiant global comme sem_open.

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

Compiler:

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

Courir avec sem_wait:

./main

Exécutez sans sem_wait:

./main 1

Sans cette synchronisation, le assert est très susceptible d'échouer, car l'enfant dort pendant une seconde entière:

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

Testé sur Ubuntu 18.04. GitHub en amont .