Benterzo
Benterzo

Reputation: 1

Synchronization between childs and parent processes c

im trying to implement this:

Make a C multi-process program that does the following:

A process P generates two child processes P1 and P2. The two sons P1 and P2 perform an indeterminate cycle in which generate, each second, a random integer between 0 and 100. With each draw, the children communicate the numbers generated by the parent P process which provides for adding them, printing them on the screen and storing them in one file. Process P1 must handle the SIGINT interrupt signal. In particular, at the arrival of this signal P1 must display the warning message "P1 process busy!". The program is terminated by the parent P process when it verifies that the sum of the numbers, which it has received from the child processes, assumes the value 100.

Now, I need some help with the synchronization between childs and parent. Im trying to use semaphores but it looks like impossible. what can i use to synchronize them? signals? how?

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

}

Upvotes: 0

Views: 2150

Answers (2)

Sigi
Sigi

Reputation: 4926

I'm reporting here the relevant part of the man page of 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).

You are trying to use unnamed semaphores in standard memory. But they are meant to synchronize threads only, not processes.

I suggest to use either named semaphores (that should be easier) or unnamed semaphores backed by shared memory (get it with shmget() or shm_open(), then use it with sem_init() - the parent and the forked processes must use the same shared memory segment to have access to the inter-process semaphore).

In fact, in your code sem1 and sem2, initialized in the main process, won't be propagated to the forked processes: they have independent memory regions and addresses, and cannot be shared.

After the edit, regarding the semaphores there are many problems:

  • the most logically wrong: you cannot pass the pointer of one process to another process: the addresses are not shared. Every process must independently open the semaphore and use it with his own handler.
  • while (i=0)... ouch, try compiling with -Wall.
  • You wasn't checking the return code of sem_open() it was failing with errno=13 (EACCESS)
  • You wasn't properly setting the permission of the semaphore... it's a (sort of) file. Note that once you crete it with the wrong permissions, it stays there and it won't be possible to create it again with the same name (until you reboot the system). You can see them with: ls -l /dev/shm, and eventually just remove them with rm.
  • You was requesting O_EXCL, that is, exclusive access to one process, that's not what you want. See man 2 open.
  • the name of the semaphore must begin with /, see man sem_overview

Here is the revised code, some comments in-line:

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

Upvotes: 2

roschach
roschach

Reputation: 9396

There is really a lot going on in your question.

As posted in the answer @Sigismondo, you are confusing multithreading with multiprocess programming. They have different method of communications.

To oversimplify threads share the same memory, so a thread can see for example values of global variables such as semaphores mutex and so on: if a thread modifies it, the other thread will be affected.

In multiprocessing when you fork(), a new process is generated with its own memory space. Right after the fork() variable values are almost the same (apart pid, ppid and so on) but they are in a different memory space: if you have a code block executed by only one process, modifying it will not affect the variables (the semaphores in your program) of the other process.

In your case: first of all if the children process do the same stuff (i.e. generate a random number) why do you have to different functions? Can't you do something like:

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

HANDLING SIGNALS

Process P1 must handle the SIGINT interrupt signal. In particular, at the arrival of this signal P1 must display the warning message "P1 process busy!". The program is terminated by the parent P process when it verifies that the sum of the numbers, which it has received from the child processes, assumes the value 100.

This is really unclear, in my opinion. The parent should catch the SIGINT signal. What should the children do? From what you say it seems they shouldn't catch that signal. In this case you must take a look at signal masks: basically you have to block the signal in the parent, the call the fork()s and then put back the original mask. Now you should go deeper but somehting like this (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, although for multithreading should be a little clearer.

SIGNAL HANDLER

Why are you registering the signal handler in codeprocess1(int pd[])? I don't get it at all. And why SIGUSR1?

You should do it in the parent (before or after the fork()s shouldn't change since the signal is blocked for children: it depends if you want to have the user exit the program before starting the forks() or not: in the first case register the signal handler after the fork() otherwise put it at the beginning of main(). In both case you should do:

signal(SIGINT, handler); 

Now the core to your program: to communicate your program you can use pipe() in a blocking way together with file descriptors: check here.

You need two file descriptors (one per child process and close the end (read/write) not used by the process). Consider a single child process:

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


}

The exit condition is both based on the value of the sum (100) and the fact that CTRL+C has been pressed. The former is obvious in the code above. For the latter you can declare a global variable (I used exit) that if 0 CTRL+C has not been pressed, if 1 it has. This value is checked in the exit condition of the code above. Your handler will be responsible to write this variable:

//global variable here
int exit = 0;

void handler(int signo)
{
    print("Parent busy doing stuff\n");
    exit =1;
}

Note one thing exit is written by the parent since it is written ONLY in the handler which is called only by the parent and it is read in the part of the code executed only by the parent: the the children read its value it will be always 0 for them.

Being your question too general I tried to give some hints: there might be errors in my code since I haven't tried. You should study your own. If you will provide a minimal working example I will try to help.

Upvotes: 0

Related Questions