I am trying to implement a shell program and I want the shell program to ignore SIG_INT(ctrl + c). But in my program the child also ignores the SIG_INT signal, which it should not because exec should take the child process to another program and that program should handle the SIG_INT signal by default. What should I do so that the child process terminates when ctrl + c is pressed.
Newly edited: after I put signal(certain_signal, SIG_DFL) in the block my child process, my code works fine. But I am still confused about how this work. Does this mean that signals as well as signal disposition can both propagate through execute command?
int main(void){
signal(SIG_INT, SIG_IGN);
int result = fork();
if(result == 0){
//child:
//exec some programs
}
else{
waitpid(result);
//do something
}
}
I believe you have misunderstood slightly how exec
modifies signal dispositions. In the Linux exec
man page, for example(1), it states that (my emphasis):
All process attributes are preserved during an execve()
, except the following:
The dispositions of any signals that are being caught are reset to the default (signal(7)
).
<Lots of other irrelevant stuff, in the context of this question>
Signals that are being caught are not the same as signals that are being ignored, as evidenced by the signal
man page:
Using these system calls, a process can elect one of the following behaviors to occur on delivery of the signal:
- perform the default action;
- ignore the signal; or
- catch the signal with a signal handler, a programmer-defined function that is automatically invoked when the signal is delivered.
That actually makes some sense because, while ignoring a signal can propagate through the exec
call, a signal handler cannot - the function that is meant to handle the signal has been replaced by the exec
call, so attempting to call it would most likely be catastrophic.
You can see this behaviour of inheriting the "ignore" disposition by compiling the following two programs, qqp.c
:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int main (void) {
signal (SIGINT, SIG_IGN);
puts("Parent start");
if (fork() == 0)
execl ("./qqc", 0);
wait(0);
sleep (1);
puts("Parent end");
return 0;
}
and qqc.c
:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main (void) {
//signal (SIGINT, SIG_DFL);
puts("Child start");
sleep (60);
puts("Child end");
return 0;
}
Note that you could also change the disposition in the first code sample, between the fork
and the exec
. This would be preferable in cases where you don't actually control what the second code sample will do (such as if you're calling an executable you didn't compile).
Running qqp
, neither the parent nor child will exit prematurely no matter how many times you press CTRL-C. But, uncomment out the line that reverts to default behaviour and you can break out of the child easily.
So, if you want your child to revert to the default behaviour, you need to do that in the child itself, with something like:
signal (SIG_INT, SIG_DFL);
(1) POSIX has a little more detail on what happens:
Signals set to the default action (SIG_DFL
) in the calling process image shall be set to the default action in the new process image. Except for SIGCHLD
, signals set to be ignored (SIG_IGN
) by the calling process image shall be set to be ignored by the new process image. Signals set to be caught by the calling process image shall be set to the default action in the new process image (see <signal.h>
). If the SIGCHLD
signal is set to be ignored by the calling process image, it is unspecified whether the SIGCHLD
signal is set to be ignored or to the default action in the new process image.
And, just on your edit that my proposed solution works, but that it raises another question for you:
Does this mean that signals as well as signal disposition can both propagate through execute command?
Signals themselves do not propagate through the exec
call, a signal is actually the "interrupt" being generated. That's different to a signal handler (code to handle a signal) or the signal disposition (what to do when the signal occurs). As shown above, dispositions may survive the exec
call but handlers cannot. Signals also do not.
What you're seeing when you press CTRL-C and multiple processes are affected has nothing to do with inheriting signals across the exec
boundary, it's more to do with the terminal stuff.
A signal delivered to an individual process will not affect any of its child processes. However, pressing CTRL-C does not send a signal to an individual process. The POSIX terminal interface has a concept of controlling terminals and process groups:
Each process also is a member of a process group. Each terminal device records a process group that is termed its foreground process group. The process groups control terminal access and signal delivery. Signals generated at the terminal are sent to all processes that are members of the terminal's foreground process group.