Will Ctrl+C Send Sigint Signals to Both Parent and Child Processes in Linux

Will ctrl+c send SIGINT signals to both parent and child processes in Linux?


In both the parent and child processes I implemented a SIGINT signal
handler. So when I press "ctrl+c", will both the handlers be called at
the same time?

Yes, they both will receive SIGINT.

Or do I need to call the child process's signal handler explicitly in
the parent process's handler?

"Calling" another process' signal handler doesn't make sense. If the both the process have a handler installed then they will be called once they receive the signal SIGINT.

I just didn't quite understand what does "foreground process group"
means.

Typically, a process associated with a controlling terminal is foreground process and its process group is called foreground process group. When you start a process from the command line, it's a foreground process:

E.g.

$ ./script.sh # foreground process
$ ./script & # background process

I suggest you read about tty and The TTY demystified for a detailed explanation.

How does Ctrl-C terminate a child process?

Signals by default are handled by the kernel. Old Unix systems had 15 signals; now they have more. You can check </usr/include/signal.h> (or kill -l). CTRL+C is the signal with name SIGINT.

The default action for handling each signal is defined in the kernel too, and usually it terminates the process that received the signal.

All signals (but SIGKILL) can be handled by program.

And this is what the shell does:

  • When the shell running in interactive mode, it has a special signal handling for this mode.
  • When you run a program, for example find, the shell:

    • forks itself
    • and for the child set the default signal handling
    • replace the child with the given command (e.g. with find)
    • when you press CTRL+C, parent shell handle this signal but the child will receive it - with the default action - terminate. (the child can implement signal handling too)

You can trap signals in your shell script too...

And you can set signal handling for your interactive shell too, try enter this at the top of you ~/.profile. (Ensure than you're a already logged in and test it with another terminal - you can lock out yourself)

trap 'echo "Dont do this"' 2

Now, every time you press CTRL+C in your shell, it will print a message. Don't forget to remove the line!

If interested, you can check the plain old /bin/sh signal handling in the source code here.

At the above there were some misinformations in the comments (now deleted), so if someone interested here is a very nice link - how the signal handling works.

Signals Behavior in C/C++ - Linux

raise(SIGINT) sends SIGINT signal only to the calling process - which is the parent process in this case. On the other hand, when you send the SIGINT signal using CTRL + C you send it to all of the foreground processes, i.e. the child process also receives it and therefore terminates. So there's no more a child process to reply to the SIGUSR1 signal of the parent process. Try ignoring the signal in the child process.

Child process receives parent's SIGINT

If you are generating the SIGINT with Ctrl+C on a Unix system, then the signal is being sent to the entire process group.

You need to use setpgid or setsid to put the child process into a different process group so that it will not receive the signals generated by the controlling terminal.


[Edit:]

Be sure to read the RATIONALE section of the setpgid page carefully. It is a little tricky to plug all of the potential race conditions here.

To guarantee 100% that no SIGINT will be delivered to your child process, you need to do something like this:

#define CHECK(x) if(!(x)) { perror(#x " failed"); abort(); /* or whatever */ }
/* Block SIGINT. */
sigset_t mask, omask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
CHECK(sigprocmask(SIG_BLOCK, &mask, &omask) == 0);

/* Spawn child. */
pid_t child_pid = fork();
CHECK(child_pid >= 0);
if (child_pid == 0) {
/* Child */
CHECK(setpgid(0, 0) == 0);
execl(...);
abort();
}
/* Parent */
if (setpgid(child_pid, child_pid) < 0 && errno != EACCES)
abort(); /* or whatever */
/* Unblock SIGINT */
CHECK(sigprocmask(SIG_SETMASK, &omask, NULL) == 0);

Strictly speaking, every one of these steps is necessary. You have to block the signal in case the user hits Ctrl+C right after the call to fork. You have to call setpgid in the child in case the execl happens before the parent has time to do anything. You have to call setpgid in the parent in case the parent runs and someone hits Ctrl+C before the child has time to do anything.

The sequence above is clumsy, but it does handle 100% of the race conditions.

If I register a custom signal handler on a process before forking, will the subsequent child processes also have the custom signal handler registered?

When you do this:

for (int count = 0; count < 10; count++) {
int pid = fork();
if (pid == 0) {
return 0;
}
}

You're telling the child processes to exit immediately, so they never get a chance to get the signal. Instead, have the children break out of the loop:

for (int count = 0; count < 10; count++) {
int pid = fork();
if (pid == 0) {
break;
}
}

Then the child processes will be active when CTRL-C is pressed.



Related Topics



Leave a reply



Submit