1. Overview

In this tutorial, we will discuss signals in Linux processes, the parent-child process hierarchy, and the signal behavior among processes.

We’ll understand why the SIGINT signal is not propagated to the child process. To do that, we need to first understand how Linux handles processes and signals.

2. Signals

We can send signals to a process from the terminal using the kill command. To use this command, we simply call kill in this manner:

kill [-signal|-s signal|-p] [-q value] [-a] [--timeout milliseconds signal] [--] pid|name... 

Even though it looks complicated, it is quite simple and straightforward to use if we just want to interrupt one process:

$ kill -SIGINT 1234 

In short, the kill command sends a signal to a specific process, and the only required parameters are the signal and the process ID.

3. Signals in Parent-Child Process Hierarchy

Before we dive into the discussion, let’s focus on the fundamentals of the process hierarchy and the signals.

Linux does not support creating processes from scratch, but it provides a method fork() to help create child processes during programming. Typically, when we want to create a child process, we do something like this in the code:

int pid = fork();
if (pid == 0) {
    // child process
    exec();
}
else {
    // parent process
}

// block parent until the child is finished
waitpid(pid); 

When we invoke fork(), it creates a child process with a unique process ID and a copy of the signal dispositions from its parent process. However, when we call the exec() family of methods, it replaces the process image and resets the signal dispositions to default.

This means that the signal behavior in the child process after exec() does not share with that of the parent process anymore. Therefore, if we send the signal to the parent process after calling exec(), it will naturally not propagate.

Sometimes, when sending SIGINT to the parent process, we might notice that we can also “interrupt” some child processes. In this case, we are not interrupting the child processes by SIGINT itself. Instead, it is the operating system that “blocks” them. This happens when the child processes are waiting for some resources held by the parent process. The child process gets blocked and waits until the parent process releases the resource. This blocking behavior doesn’t happen when the child process is independent of the parent process.

4. How to Interrupt All the Processes in the Hierarchy?

Fortunately, when we create a child process in a hierarchy, every process within the hierarchy gets a new property named Process Group ID (PGID).

To find the PGID of a process, we can use the ps command:

$ ps ax -O pgid 
PID       PGID      S      TTY      TIME      COMMAND
  1          1      S      ?    00:00:00      /usr/lib/...
...

The second column tagged with PGID is what we are looking for.

This command gives us a list of all of the processes with their group ID attached. We can find our target process within this list. If we know the name of the process (e.g., a.out), we can simply pipe the output into a grep command:

$ ps ax -O pgid | grep a.out 
1234    1234    S    pts/0    00:00:00    ./a.out

Again, the second number is the PGID we are looking for.

Once we obtain this ID, we can use the kill command to send the SIGINT signal to the group. To do so requires us to add the negation symbol “-” to both the signal and the process group ID. This tells the kill command to treat the given ID as a group. Also, we recommend adding a double dash “- -” between the signal and the process group so that kill can send the signal to all the processes within it:

$ kill -SIGINT -- -1234 

This command guarantees to send the SIGINT signal to all the processes within a hierarchy. Also, the negation symbols make sure to interrupt the child processes without a system block.

5. Conclusion

In this article, we have briefly covered the relationship between signals and the parent-child process hierarchy.

As long as we understand how Linux creates child processes, we can see why the signal behavior of the child process is independent of that of the parent process. Even though we cannot directly gain access to the child processes through their parents,  they are still accessible in the process group if we use the process group ID.

If we want to send signals to either a parent or a child process, just use kill with a specific process id and a signal. If we want to send signals to all of them, we can use the ps command to find the process group ID,  and then use the kill command with the negation symbol to send a signal to all of the processes within the hierarchy.