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()
.
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.