Как использовать семафоры POSIX на разветвленных процессах в C?
Я хочу развить несколько процессов, а затем использовать на них семафор. Вот что я попробовал:
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);
}
И вывод:
ребенок (0) раздвоен ребенок (1) раздвоенный Child(0) находится в критическом разделе. Ребенок (1) находится в критическом разделе. ребенок (2) раздвоенный Ребенок (2) находится в критическом разделе. ребенок (3) раздвоенный Ребенок (3) находится в критическом разделе. ребенок (4) раздвоенный Ребенок (4) находится в критическом разделе. Child(0) новое значение *p=0. Child(1) новое значение *p=1. Child(2) новое значение *p=3. Ребенок (3) новое значение * р = 3. Ребенок (4) новое значение * р =4. Родитель: все дети вышли.
Это ясно означает, что семафор не работал так, как предполагалось. Можете ли вы объяснить, как я должен использовать семафоры на разветвленных процессах?
2 ответа
Проблема, с которой вы сталкиваетесь, заключается в неправильном понимании sem_init()
функция. Когда вы прочитаете страницу руководства, вы увидите это:
Аргумент 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);
}
}
ВЫХОД
./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.
Это не плохо проверить shmkey
с каких пор ftok()
терпит неудачу, это возвращает -1. Однако, если у вас есть несколько общих переменных и если ftok()
функция не работает несколько раз, общие переменные, которые имеют shmkey
со значением -1
будет находиться в той же области общей памяти, что приведет к изменению одного, влияющего на другой. Поэтому выполнение программы будет запутанным. Чтобы избежать этого, лучше проверить, если ftok()
возвращает -1 или нет (лучше проверить исходный код, а не печатать на экран, как я, хотя я хотел показать вам значения ключей в случае коллизии).
Обратите внимание на то, как семафор объявлен и инициализирован. Это отличается от того, что вы сделали в вопросе (sem_t sem
против sem_t* sem
). Более того, вы должны использовать их, как они появляются в этом примере. Вы не можете определить sem_t*
и использовать его в sem_init()
,
Linux минимально анонимный sem_init
+ mmap
MAP_ANONYMOUS
пример
Мне нравится эта настройка, поскольку она не загрязняет глобальное пространство имен, как 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
очень вероятно, что потерпит неудачу, так как ребенок спит целую секунду:
main: main.c:39: main: Assertion `semint->i == 1' failed.
Проверено на Ubuntu 18.04. GitHub вверх по течению.