Yesterday I had some problems trying to get rid of the SIGINT signal (when you press C-c on your terminal). Imagine you have the following script :
# a.sh b & sleep
Let’s suppose that b is a sleep or another simple command or a shell script. In that case a SIGINT on the terminal will kill a.sh but b will be rattached to init. This is strange because:
- b effectively receives the signal (seen with strace)
- the default action should terminate the process
if(!fork()) { /* child */ signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); execve(...cmd...); }
The execve manpage has this to say about signals:
* POSIX.1-2001 specifies that the dispositions of any signals that are
ignored or set to the default are left unchanged.
i.e. the process will inherit SIG_IGN signals and set all the others to default. So with our shell the SIGINT is ignored for the background processes. However if the process defines its own handler and catch the signal, it will override the inherited SIG_IGN value. This is the case for example with tcpdump which will catch this signal to terminate properly. This is also the case for the following code:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> void sigint(int n) { printf("child int; exitn"); exit(0); } int main() { signal(SIGINT, sigint); usleep(-1); }
In this case the process will catch SIGINT and terminate. So if we cannot prevent the process to catch the signal the question now is: How can we prevent b to receive the signal?
When this signal is generated, it is sent to the foreground process group on the session associated to the tty. If you have a shell open on your tty, it will be the currently running command if any. In this case the current command is a non-interactive shell script. In this non-interactive shell, all commands even those sent to background with “&” are attached to the same process group. So here the background commands of the shell script are associated to the foreground process group. This is why they still receive SIGINT even when the parent process die and they are rattached to init.
The solution is to create the processes which we want to make unaware of SIGINT in a new process group, different from the foreground process group and therefore a background process group. However there is no command to start something in a new process group. Instead we have a command which can start something in a new session and therefore a new process group too. Better still, your process is detached from your tty which makes it (nearly) a real daemon. So the solution is:
# a.sh setsid b & sleep
Another solution forces the shell to turn on job control in non-interactive mode with set -m. This way each background process will be created in a new process group. This could be embarrassing though because you cannot background processes in the same process group anymore. Basically we want processes in the same process group to make sure that a SIGINT will terminate the entire script, background commands included. Otherwise you have to save the pid, trap the SIGINT signal and take care of terminating everything by yourself.
Moreover using setsid is a part of the REAL answer to this question:
“How do we fully detach a process from its tty?”
For which we almost see:
“Use nohup…”
Which is wrong! According to man nohup justs: “run a command immune to hangups, with output to a non-tty”. That is, it will just adjust input/output, ignore SIGHUP and exec your command. However if you do something like signal(SIGHUP, SIG_DFL); then nohup has no effect anymore and the process will be terminated when the terminal hang up.
Thanks, this is exactly what I needed, using a Makefile rule to start avarice (a program providing a debug interface for atmel microchips) which must run in background, starting gdb and using CTRL+C for interrupting the program while not killing avarice at the same time.
Why they cannot provide a “noint”, or better yet, extend nohup to include blocking arbitrary signals like SIGINT is beyond me.