子进程与父进程之间的同步c

时间:2019-01-24 09:22:04

标签: c multiprocess

我正尝试实施此操作:

  

制作一个执行以下操作的C多进程程序:

     

进程P生成两个子进程P1和P2。两个儿子P1和P2执行一个不确定的周期,在该周期中,每秒生成0到100之间的一个随机整数。在每次绘制时,孩子们交流由父P进程生成的数字,该数字将它们相加并打印在屏幕并将它们存储在一个文件中。进程P1必须处理SIGINT中断信号。特别是,在此信号到达时,P1必须显示警告消息“ P1 process busy!”。当程序验证从子进程接收到的数字的总和取值为100时,将由父P进程终止。

现在,我需要一些有关孩子与父母之间同步的帮助。我试图使用信号量,但看起来似乎不可能。我可以使用什么来同步它们?信号?怎么样?

    #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <semaphore.h>
#include <fcntl.h>
#define READ 0
#define WRITE 1


void handler(int sig){

    printf("process 1 is busy\n");


}


void codeprocess1(int pd[], sem_t *sem1){

    int i = 0;
    int numgenerated;


    while( i = 0){
      signal(SIGUSR1, handler);
       numgenerated = rand()%101;
       close(pd[READ]);
       write(pd[WRITE], &numgenerated, sizeof(int));
       sleep(1);

       sem_wait(sem1);
    }
}

void codeprocess2(int pd[], sem_t *sem2){
    int i = 0;
    int numgenerated;



    while( i = 0){

     numgenerated = rand()%101;
     close(pd[READ]);

     write(pd[WRITE], &numgenerated, sizeof(int));

     sleep(1);

     sem_wait(sem2);
    }
}


int main(){


 pid_t pid1, pid2;
 int sum, numread1, numread2, pipe1[2], pipe2[2];

    sem_t *sem2 = sem_open("semaph2", O_CREAT | O_EXCL, 1, 0);
    sem_t *sem1 = sem_open("semaph1", O_CREAT | O_EXCL, 1, 0);


 if(pipe(pipe1)<0){
     exit(1);

 }

 if(pipe(pipe2)<0){
     exit(1);

 }


 pid1 = fork();
 switch(pid1){

     case -1:

       exit(1);

     case 0:

       codeprocess1(pipe1, sem1);
       break;

     default:

        pid2= fork();
        switch( pid2){

       case -1:
          exit(1);

       case 0:

          codeprocess2(pipe2, sem2);
          break;

         default:

           while(sum!=1000){
           close(pipe1[WRITE]);

           read(pipe1[READ], &numread1, sizeof(int));
            close(pipe2[WRITE]);
           read(pipe2[READ], &numread2, sizeof(int));
           sum = sum + numread1 + numread2;
           printf("%d\n", sum);
           sem_post(sem1);
           sem_post(sem2);
       }

          kill(0, SIGKILL);
     }
   }

}

2 个答案:

答案 0 :(得分:1)

我在这里报告sem_overview(7)手册页的相关部分:

   POSIX  semaphores come in two forms: named semaphores and unnamed sema‐
   phores.

   Named semaphores
          A named semaphore is identified by a name of the form /somename;
          that  is,  a  null-terminated  string of up to NAME_MAX-4 (i.e.,
          251) characters consisting of an initial slash, followed by  one
          or  more  characters,  none of which are slashes.  Two processes
          can operate on the same named semaphore by passing the same name
          to sem_open(3).

          The  sem_open(3) function creates a new named semaphore or opens
          an existing named  semaphore.   After  the  semaphore  has  been
          opened, it can be operated on using sem_post(3) and sem_wait(3).
          When a process has finished using  the  semaphore,  it  can  use
          sem_close(3)  to  close  the semaphore.  When all processes have
          finished using the semaphore, it can be removed from the  system
          using sem_unlink(3).

   Unnamed semaphores (memory-based semaphores)
          An  unnamed  semaphore  does not have a name.  Instead the sema‐
          phore is placed in a region of memory  that  is  shared  between
          multiple  threads  (a  thread-shared  semaphore) or processes (a
          process-shared semaphore).  A thread-shared semaphore is  placed
          in  an  area  of memory shared between the threads of a process,
          for example, a global variable.  A process-shared semaphore must
          be  placed  in  a  shared memory region (e.g., a System V shared
          memory segment created using shmget(2), or a POSIX shared memory
          object built created using shm_open(3)).

          Before  being  used,  an  unnamed  semaphore must be initialized
          using sem_init(3).  It can then be operated on using sem_post(3)
          and  sem_wait(3).  When the semaphore is no longer required, and
          before the memory in which it is  located  is  deallocated,  the
          semaphore should be destroyed using sem_destroy(3).

您正在尝试在标准内存中使用未命名信号量。但是它们仅用于同步线程,而不用于同步进程。

我建议使用由共享内存支持的命名信号量(应该更容易)或未命名信号量(通过shmget()shm_open()获取,然后与sem_init()一起使用-父进程和分叉进程必须使用相同的共享内存段才能访问进程间信号量。

实际上,在您的代码sem1sem2中,它们在主进程中初始化,不会传播到分支的进程:它们具有独立的内存区域和地址,并且不能共享。

编辑后,关于信号量,存在许多问题:

    在逻辑上最错误的是:您不能将一个进程的指针传递给另一进程:地址不共享。每个进程都必须独立打开信号量,并将其与自己的处理程序一起使用。
  • while (i=0) ...哎呀,尝试使用-Wall进行编译。
  • 您没有检查errno = 13(EACCESS)失败的返回sem_open()的返回码
  • 您没有正确设置信号灯的权限...这是一个(某种)文件。请注意,一旦使用错误的权限创建它,它就会保留在该位置,并且无法再次使用相同的名称创建它(直到重新引导系统)。您可以使用ls -l /dev/shm查看它们,最后只需使用rm删除它们即可。
  • 您正在请求O_EXCL,即,对一个进程的独占访问权不是您想要的。参见man 2 open
  • 信号量的名称必须以/开头,请参见man sem_overview

这是修改后的代码,内嵌一些注释:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <semaphore.h>
#include <fcntl.h>
#include <errno.h>

#define READ 0
#define WRITE 1

#define SEM1_NAME "/semaph_1a"
#define SEM2_NAME "/semaph_2a"


void handler(int sig) {
  printf("process 1 is busy\n");
}

void codeprocess1(int pd[]) {
  int i = 0;
  int numgenerated;

  // each process must open the handle to the same named semaphore.
  // they cannot share a local memory address.
  sem_t *my_sem = sem_open(SEM1_NAME, O_CREAT , 0777, 0);
  if (my_sem==SEM_FAILED) {
    printf("semaphore creation failed, errno=%d\n", errno);
    exit(1);
  }

  // the seed for the two children must be different or they will be generating the same
  // sequence of random numbers. 
  srand(3333);

  while(i == 0) {
    signal(SIGUSR1, handler);
    numgenerated = rand()%101;
    // close(pd[READ]);
    write(pd[WRITE], &numgenerated, sizeof(int));
    sleep(1);

    sem_wait(my_sem);
  }
}

void codeprocess2(int pd[]){
  int i = 0;
  int numgenerated;

  sem_t *my_sem = sem_open(SEM2_NAME, O_CREAT, 0777, 0);
  if (my_sem==SEM_FAILED) {
    printf("semaphore creation failed, errno=%d\n", errno);
    exit(1);
  }

  srand(1111);

  while(i == 0) {
    numgenerated = rand()%101;
    // close(pd[READ]);
    write(pd[WRITE], &numgenerated, sizeof(int));
    sleep(1);
    sem_wait(my_sem);
  }
}


int main(){
  pid_t pid1, pid2;
  int sum, numread1, numread2, pipe1[2], pipe2[2];


  // O_EXCL removed
  // the mode flag must be set to 0777 for example, not "1".
  // return value check added
  sem_t *sem1 = sem_open(SEM1_NAME, O_CREAT , 0777, 0);
  if (sem1==SEM_FAILED) {
    printf("semaphore sem1 creation failed, errno=%d\n", errno);
    exit(1);
  }

  sem_t *sem2 = sem_open(SEM2_NAME, O_CREAT, 0777, 0);
  if (sem2==SEM_FAILED) {
    printf("semaphore sem2 creation failed, errno=%d\n", errno);
    exit(1);
  }

  if (pipe(pipe1) < 0 ) {
    exit(1);
  }

  if (pipe(pipe2) < 0) {
    exit(1); 
  }

  pid1 = fork();
  switch(pid1){

  case -1:
    exit(1);

  case 0:
    codeprocess1(pipe1);
    break;

  default:

    pid2= fork();
    switch( pid2) {      
    case -1:
      exit(1);
    case 0:      
      codeprocess2(pipe2);
      break;      
    default:      
      // 100, not 1000
      while (sum != 100) {
    // all the "close()" calls  are commented out
    // close(pipe1[WRITE]);
    read(pipe1[READ], &numread1, sizeof(int));
    // close(pipe2[WRITE]);
    read(pipe2[READ], &numread2, sizeof(int));
    // sum must not be incremented
    sum = numread1 + numread2;
    printf("%d\n", sum);
    sem_post(sem1);
    sem_post(sem2);
      }

      kill(0, SIGKILL);
    }
  }  
}

答案 1 :(得分:0)

您的问题确实有很多事情发生。

如答案@Sigismondo中所述,您将多线程与多进程编程混淆了。他们有不同的沟通方式。

为简化起见,线程共享相同的内存,因此一个线程可以查看诸如信号量互斥等全局变量的值:如果一个线程对其进行了修改,则另一个线程将受到影响。

在多处理中,当您fork()时,将使用其自己的内存空间来生成一个新进程。紧接着fork()变量值几乎相同(除了pidppid等),但它们位于不同的存储空间中:如果您有一个仅由一个执行的代码块进程,对其进行修改不会影响其他进程的变量(程序中的信号灯)。

在您的情况下:首先,如果子进程执行相同的操作(即生成一个随机数),为什么您必须使用不同的功能?你不能做类似的事情吗?

#include<stdlib.h>
int generateRand()
{
     n = rand() % 100 + 1; //should be random in [1, 100]
}

处理信号

  

进程P1必须处理SIGINT中断信号。特别是在   该信号P1的到来必须显示警告消息“ P1   进程忙!”。   它验证从其接收到的数字的总和   子进程将假定值为100。

我认为这还不清楚。父级应该捕获SIGINT信号。孩子们应该怎么办?从您所说的看来,他们似乎不应该抓住这个信号。在这种情况下,您必须查看信号掩码:基本上,您必须在父级中阻塞信号,调用fork(),然后放回原始掩码。现在您应该更深入一些,但是像这样(here

sigset_t *parent_mask, *child_mask
//get the current mask 
if (int res =  sigprocmask (0, NULL, child_mask)<0)
    printf("some error\n");
//make the mask block the signal
if (int res =  sigaddset(child_mask, SIGINT)<0)
    printf("some error in sigaddset \n");
// block the signal with the new mask
if (int res =  sigprocmask (SIG_SETMASK, child_mask, parent_mask)<0)
    printf("some error\n");
//do your forks: children will inherit the current mask and will not catch SIGINT
...
fork()
...
fork()
....
//set back the original mask so the parent catches SIGINT
if (int res =  sigprocmask (SIG_SETMASK, parent_mask, NULL)<0)
    printf("some error\n");

This answer of mine,尽管对于多线程来说应该更清楚一些。

信号处理程序

为什么要在codeprocess1(int pd[])中注册信号处理程序?我一点都不明白。以及为什么SIGUSR1

您应该在父级执行此操作(在fork()之前或之后不应更改,因为该信号已被子级阻止:这取决于您是否希望用户在启动{{是否为1}}:在第一种情况下,请在forks()之后注册信号处理程序,否则将其放在fork()的开头。在两种情况下,您都应该这样做:

main()

现在是程序的核心:要进行程序通信,您可以将signal(SIGINT, handler); 与文件描述符一起使用:检查here

您需要两个文件描述符(每个子进程一个,然后关闭该进程未使用的结尾(读/写))。 考虑一个子进程:

pipe()

退出条件既基于总和(100)的值,也取决于按下CTRL + C的事实。前者在上面的代码中很明显。对于后者,您可以声明一个全局变量(我使用int p = fork(); int fd1[2]; //file descriptor for child1 int fd2[2]; //file descriptor for child2 if (p>0)//parent { close(fd1[1]);//close writing end int n; read(fd1[0], &n, sizeof(n)); //you might to call the other fork here and redo the same stuff int p2 = fork(); if (p2>0) { close(fd2[1]);//close writing end int n2; read(fd2[0], &n2, sizeof(n2)); sum = n2+n1 if (sum==100 && exit = 1) { kill(p, SIGKILL); kill(p2, SIGKILL); } } } else if(p==0)//child { close(fd1[0]);//close read end int rand_n = generateRand();//or whaterver the name wrote(fd1[1], &rand_n, sizeof(rand_n)); } ),如果尚未按下0 CTRL + C,则它是一个全局变量。在上面代码的退出条件中检查此值。您的处理程序将负责编写此变量:

exit

请注意,//global variable here int exit = 0; void handler(int signo) { print("Parent busy doing stuff\n"); exit =1; } 是由父母写的一件事,因为它仅写在仅由父母调用的处理程序中,并且在仅由父母执行的部分代码中读取:子元素读取它的值,对他们来说总是0。

由于您的问题过于笼统,我尝试给出一些提示:由于我没有尝试过,所以我的代码中可能存在错误。你应该学习自己的。如果您将提供一个最小的工作示例,我将尽力提供帮助。