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