如何在C中的分叉进程上使用POSIX信号量?

我想分叉多个进程,然后在它们上使用信号量。 这是我尝试过的:

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)分叉
  儿童(0)处于关键部分。
  儿童(1)处于危急区域。
孩子(2)分叉
  儿童(2)处于危急区域。
孩子(3)分叉
  儿童(3)处于危急区域。
孩子(4)分叉
  儿童(4)处于危急区域。
  子(0)* p = 0的新值。
  子(1)* p = 1的新值。
  子(2)* p = 3的新值。
  子(3)* p = 3的新值。

   Child(4)* p = 4的新值。
家长:所有孩子都已退出。 

这显然意味着信号量不能像预期的那样工作。 你能解释一下我应该如何在分叉进程上使用信号量?

您遇到的问题是对sem_init()函数的误解。 阅读手册页后,您将看到:

pshared参数指示此信号量是在进程的线程之间还是在进程之间共享。

如果你已完成阅读,你会认为pshared的非零值将使信号量进程间信号量。 但是,这是错误的。 您应该继续阅读,并且您将理解您必须在共享内存区域中找到信号量。 为此,可以使用几个函数,如下所示:

如果pshared为非零,则信号量在进程之间共享,并且应位于共享内存的区域中(请参阅shm_open(3),mmap(2)和shmget(2))。 (由于fork(2)创建的子进程inheritance了父进程的内存映射,因此它也可以访问信号量。)任何可以访问共享内存区域的进程都可以使用sem_post(3),sem_wait(3)等对信号量进行操作。 。

我发现这种方法比其他方法更复杂,因此我想鼓励人们使用sem_open()而不是sem_init()

您可以在下面看到完整的程序说明以下内容:

  • 如何在分叉进程之间分配共享内存和使用共享变量。
  • 如何初始化共享内存区域中的信号量,并由多个进程使用。
  • 如何分叉多个进程并使父进程等待其所有子进程退出。
 #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); } } 

OUTPUT

 ./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()函数多次失败,则具有值为-1shmkey的共享变量将驻留在共享内存的同一区域中,从而导致一个影响另一个的更改。 因此程序执行会变得混乱。 为了避免这种情况,最好检查ftok()返回-1(更好地检查源代码而不是像我一样打印到屏幕,尽管我想在出现碰撞时向您显示关键值) 。

注意声明和初始化信号量的方式。 它与您在问题中所做的不同( sem_t sem vs 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  #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; } 

编译:

 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上游 。