Parent and Child Process Signal Synchronization Technique in IPC

1.  Concepts

1.1. Parent process vs Child process

-       Parent process is considered the first process running when a program starts.

-       Child process is created from 1 parent process by fork().

-       When a child process is created, the followings are copied from Parent process to Child process:

·       Memory stack, heap, text segment (containing machine language instruction of the program) [5]

·       Signal mask: “A child created via fork() inherits a copy of its parent's signal mask” [6]

-       The Parent and Child process could run in parallel/concurrently; no assumption should be made on which process could run first. “it is indeterminate which of the two processes is next scheduled to use the CPU [4].

-       The following codes are employed when calling fork():

pid_t childPid; /* Used in parent after successful fork() to record PID of child */

switch (childPid = fork()) {

   case -1: /* fork() failed */

            /* Handle error */

   case 0:  /* Child of successful fork() comes here */

            /* Perform actions specific to child */

   default: /* Parent comes here after successful fork() */

            /* Perform actions specific to parent */

}

Notes:

-       When a Child process is running, childPid=fork() returns value of 0.

  • getpid() returns the PID of Child process.
  • getppid() returns the PID of Parent process.

-       When a Parent process is running, childPid=fork() returns default PID of child process.

getpid() returns the PID of Parent process.

1.2. Signal:

-       “A signal is an event generated by the system in response to a specific condition. When a process receives a signal it may take an action. A standard signal has a default disposition and it determines the behavior of the process after it is delivered.” [1].

-       Signal set: All of the signal blocking functions are defined by a data sigset_t() to specify what signals are affected. [2]. It is in fact an array of integer.

-       Catching a signal: a process can wait for a signal by sigsuspend().

-       Sending a signal to another process: a process can send signal to another process, defined by PID by kill().

-       Masking a signal: each process that has a single thread has a signal mask, which masks a set of signals which can affect the process. Sigpromask() can be used to manipulate the signal mask. [6].

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
·       int how
int how
  • int how: defines how the signal mask should be.

If SIG_SETMASK, the mask should be the same as the signals in sigset_set *set.

If SIG_UNBLOCK, the mask should be the signals in sigset_set *oldset, minus the signals in *set.

If SIG_BLOCK, the mask should be the union of *set, and *oldset. In other word, the mask should include all the signals in both signal sets.

  • Const sigset_t *set: the set of new blocked signals.
  • Sigset_t *oldest : the set of old blocked signals that affects the process

-       Actions that a process can do upon receiving a signal: “A process can change the disposition of a signal with function” sigaction() [3] such as:

  • Performs default action
  • Ignore the signal
  • Catch the signal and do something by a signal handler.

-       A struct signaction is defined to allow signaction() function to call other functions sa_handler() or sa_sigaction(), which in turns allows a process to change the disposition of a signal.

struct sigaction {

     void    (*sa_handler)(int); 

     void    (*sa_sigaction)(int, siginfo_t *, void *); 

     sigset_t  sa_mask;

     int       sa_flags;

     void    (*sa_restorer)(void);

};
  • * sa_sigaction() specifies the action to be taken when a signal sa_mask is received, and
  • *sa_handler() specifies the action to be taken when a signal sa_mask is received, or default action (performed by kernel)

- signaction() performs actions, defined by the functions inside struct sigaciton *act, when a signum is received. The actions are performed either by sa_handler() or sa_sigaction()

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • *act: the structure containing the actions to be taken
  • *oldact: the structure containing the previous actions to be saved.

2.  Code Implementation

2.1. Implementing caught Interrupting Signal (Ctrl+C) on a continuous running program

-       The following code will stop a while loop program upon Ctrl+C and call another function to handle exist action

#include <stdio.h>   /* to run prinft*/

#include <signal.h>  /* to run crtl C interruption handler*/

void onServiceStop_handler(mqd_t messageDescriptor){

        printf("Caught interrupting signal \n");

        //do something

        exit(1);

};




int main(int argc, char * argv[] ) {

        struct sigaction sigIntHandler;

        sigIntHandler.sa_handler = onServiceStop_handler;

        sigemptyset(&sigIntHandler.sa_mask);

        sigIntHandler.sa_flags = 0;

        // catch the signal Ctrl C

        sigaction(SIGINT, &sigIntHandler, NULL);

        while (true){

                //do something

        }

        return 0;


2.2. Implementing synchronization between parent and child process

-       Pseudo codes:

·       Step 1: create a signal set of type struct sigset_t

·       Step 2: add user defined signal to user set by sigaddset()

·       Step 3: create a signal mask containing user defined signal by using sigpromask(). If there is error, print error msg

·       Step 4: defines actions to take once the signal is catch by the process

·       Step 5: create a child process with fork().

·       

·       Step 6: runs child process and parent process in parallel or in any order depending on machine. Use switch() to run the 2 processes’ actions according to the return value of fork()

·       Step 6.1: If Parent process is running, suspend the process if the user-defined signal is caught by using sigsuspend(). (Child process is running meanwhile)

·       Step 6.2: Run Child process, send to parent process a user defined signal to trigger actions performed by Parent signal by kill().

-       The following code implements the synchronization between child and parent process

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

#include <bits/stdc++.h>

#include <signal.h>

  

#define SYNC_SIG SIGUSR1 /* Synchronization signal */

/* Signal handler - does nothing but return */

static void handler(int sig)

{

}

 

int main(int argc, char *argv[])

{

   setbuf(stdout, NULL); /* Disable buffering of stdout */

   struct sigaction sa;

 

   /* STEP 1: create a signal set of type struct sigset_t */

   //Sigset_t is a data structure to specify what signals are affected.

   sigset_t blockMask, origMask, emptyMask;

 

   /* STEP 2: add user defined signal to user set by sigaddset() */

   //This function removes every signal on blockMask signal set. It always returns 0.

   sigemptyset(&blockMask);

   //This function adds SYNC_SIG to the signal set blockMask. SYNC_SIG is SIGUSR1 or user-defined signal.

   sigaddset(&blockMask, SYNC_SIG); /* Block signal */

 

   /* STEP 3: create a signal mask containing user defined signal by using sigpromask(). If there is error, print error msg */

   //Sigprocmask sets the signal MASK of the process to be union of (&blockMask, &origMask), and saves current process' signal set to &original

   if (sigprocmask(SIG_BLOCK, &blockMask, &origMask) == -1) printf("sigprocmask error");

 

   /* STEP 4: defines actions to take once the signal is catch by the process */

   //set signal set "sa_mask" in sa structure to be empty

   sigemptyset(&sa.sa_mask);

   //sa_flags specifies a set of flags which modify the behavior of the signal

   sa.sa_flags = SA_RESTART;

   //sa_handler specifies the action to be associated with signum

   sa.sa_handler = handler;

   //sigaction is a system call, used to change the action taken by a process upon receipt of a specific signal.

   //upon SYNC_SIG is received, handler will run.

   if (sigaction(SYNC_SIG, &sa, NULL) == -1) printf("sigaction error");

 

   /* STEP 5: Creating process ID for child process. */

   // pid_t is a data type, same as int or long int, representing process ID

   pid_t childPid;

 

   /* STEP 6: runs child process and parent process in parallel */

   switch (childPid = fork()) {

      case -1:

         printf("fork");

      /* STEP 6.2: Run Child process, send to parent process a user defined signal to trigger actions performed by Parent signal by kill().*/

      case 0:

         /* Child does some required action here... */

         printf("Child process PID: %ld started  - doing some work\n", (long) getpid());

         sleep(2); /* Simulate time spent doing some work */

         /* And then signals parent that it's done */

         printf("Child process PID: %ld about to signal Parent PID %ld \n", (long) getpid(), (long) getppid() );

         if (kill(getppid(), SYNC_SIG) == -1) printf("kill caught error");

         /* Now child can do other things... */

         _exit(EXIT_SUCCESS);

      /* STEP 6.1: If Parent process is running, suspend the process if the user-defined signal is caught by using sigsuspend().*/

      default:

         /* Parent may do some work here, and then waits for child to

         complete the required action */

         printf("Parent PID: %ld about to wait for signal from Child Process \n", (long) getpid());

 

         // set signal set &emptyMask for Parent process to be empty????????????????

         sigemptyset(&emptyMask);

         printf("emptyMask is %ld \n",emptyMask.__val[0]);

         if (sigsuspend(&emptyMask) == -1 && errno != EINTR)

            printf("sigsuspend caught error");

         printf("Parent PID: %ld got signal from Child Process \n", (long) getpid());

         /* If required, return signal mask to its original state */

         if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1)

            printf("sigprocmask");

         /* Parent carries on to do other things... */

         exit(EXIT_SUCCESS);

   }

}

3.  References:

[1] Understanding Linux Process. https://medium.com/100-days-of-linux/understanding-linux-process-signals-53d44c85c706

[2] https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

[3] https://man7.org/linux/man-pages/man2/sigaction.2.html

[4] Micheal Kerrisk. The Linux Programming Interface a Linux and Unix System Programming Handbook. Chapter 24.2 Creating a New Process: fork(). p516

[5] Micheal Kerrisk. The Linux Programming Interface a Linux and Unix System Programming Handbook. Chapter 24.1 Overview of fork(), exit() wait() and execve(). p516

[6] https://man7.org/linux/man-pages/man7/signal.7.html

要查看或添加评论,请登录

Minh Ph?m的更多文章

社区洞察

其他会员也浏览了