Come utilizzare i semafori POSIX sui processi biforcati in C?

Voglio eseguire il fork di più processi e quindi utilizzare un semaforo su di essi. Ecco cosa ho provato:

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

E l’output è:

  bambino (0) biforcuto
 bambino (1) biforcuto
   Il bambino (0) si trova nella sezione critica.
   Il bambino (1) si trova nella sezione critica.
 bambino (2) biforcuto
   Child (2) è nella sezione critica.
 bambino (3) biforcuto
   Il bambino (3) è nella sezione critica.
 bambino (4) biforcuto
   Il bambino (4) è nella sezione critica.
   Bambino (0) nuovo valore di * p = 0.
   Bambino (1) nuovo valore di * p = 1.
   Bambino (2) nuovo valore di * p = 3.
   Bambino (3) nuovo valore di * p = 3.

   Bambino (4) nuovo valore di * p = 4.
 Genitore: tutti i bambini sono usciti. 

Ciò significa chiaramente che il semaforo non ha funzionato come previsto. Puoi spiegare come dovrei usare i semafori sui processi biforcati?

Il problema che stai affrontando è l’incomprensione della funzione sem_init() . Quando leggi la pagina del manuale vedrai questo:

L’argomento pshared indica se questo semaforo deve essere condiviso tra i thread di un processo o tra processi.

Se hai finito di leggere fino a questo punto, penserai che il valore diverso da zero di pshared renderà il semaforo semaforo del semaforo. Tuttavia, questo è sbagliato. Dovresti continuare a leggere e capirai che devi localizzare il semaforo in un’area di memoria condivisa. Per farlo, puoi utilizzare diverse funzioni, come puoi vedere qui sotto:

Se pshared è diverso da zero, il semaforo viene condiviso tra i processi e deve trovarsi in una regione di memoria condivisa (vedere shm_open (3), mmap (2) e shmget (2)). (Poiché un figlio creato da fork (2) eredita i mapping della memoria del genitore, può anche accedere al semaforo.) Qualsiasi processo che può accedere all’area di memoria condivisa può operare sul semaforo usando sem_post (3), sem_wait (3), ecc. .

Trovo che questo approccio sia un approccio più complicato di altri, quindi voglio incoraggiare le persone a usare sem_open() invece di sem_init() .

Qui sotto puoi vedere un programma completo che illustra quanto segue:

  • Come allocare memoria condivisa e utilizzare variabili condivise tra processi biforcati.
  • Come inizializzare un semaforo in un’area di memoria condivisa e viene utilizzato da più processi.
  • Come eseguire il fork di più processi e far attendere il genitore fino all’uscita di tutti i suoi figli.
 #include  /* printf() */ #include  /* exit(), malloc(), free() */ #include  /* key_t, sem_t, pid_t */ #include  /* shmat(), IPC_RMID */ #include  /* errno, ECHILD */ #include  /* sem_open(), sem_destroy(), sem_wait().. */ #include  /* 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); } } 

PRODUZIONE

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

Non è male controllare shmkey dato che quando ftok() fallisce, restituisce -1. Tuttavia se si hanno più variabili condivise e se la funzione ftok() fallisce più volte, le variabili condivise che hanno un valore shmkey con valore -1 risiederanno nella stessa regione della memoria condivisa, determinando una modifica in una che influenza l'altra. Pertanto l'esecuzione del programma diventerà disordinata. Per evitare questo, è meglio controllare se ftok() restituisce -1 o meno (meglio controllare il codice sorgente piuttosto che stampare sullo schermo come ho fatto io, sebbene volessi mostrarti i valori chiave nel caso ci fosse una collisione) .

Presta attenzione a come viene dichiarato e inizializzato il semaforo. È diverso da quello che hai fatto nella domanda ( sem_t sem vs sem_t* sem ). Inoltre, dovresti usarli come appaiono in questo esempio. Non puoi definire sem_t* e usarlo in sem_init() .

sem_init anonimo minimo per Linux + mmap MAP_ANONYMOUS

Mi piace questa configurazione in quanto non inquina nessuno spazio dei nomi globale come fa sem_open .

L’unico lato negativo è che MAP_ANONYMOUS non è POSIX e non conosco alcuna sostituzione: memoria condivisa anonima? per esempio, shm_open accetta un identificatore globale come sem_open .

main.c:

 #define _GNU_SOURCE #include  #include  #include  #include  #include  #include  #include  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; } 

Compilare:

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

Esegui con sem_wait :

 ./main 

Esegui senza sem_wait :

 ./main 1 

Senza questa sincronizzazione, è molto probabile che l’ assert fallisca, poiché il bambino dorme per un intero secondo:

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

Testato su Ubuntu 18.04. GitHub a monte .