Discussion:
[Help-bash] What the signals cause EXIT?
Peng Yu
2018-11-16 21:55:14 UTC
Permalink
Hi,

I made the following test case to show what signals will cause EXIT.

It seems that
- SIGKILL directly kill the process without triggering EXIT.
- SIGCHLD does not trigger EXIT.
- SIGSTOP and SIGCONT do not trigger EXIT. (Test cases not shown as
the two signals are kind of special.)
- SIGINT and SIGQUIT are ignored as they are not sent from the same terminal.

So, other than these signals, all the rest signals cause EXIT. Is my
conclusion correct? Some of the conclusion is counter-intuitive. For
example, what is the reason to EXIT when SIGWINCH is triggered?

Could anybody let me know what signals will result in EXIT? Thanks.

$ cat script.sh
#!/usr/bin/env bash
# vim: set noexpandtab tabstop=2:

mkdir -p /tmp/trapls/pid
mkdir -p /tmp/trapls/log
echo "$$" > "/tmp/trapls/pid/$1.txt"
while IFS= read -r s
do
"/tmp/trapls/log/$1.txt"
trap "echo $(echo '"$(date)"'):$s >> /tmp/trapls/log/$1.txt" "$s"
done < <(trap -l | awk -v OFS=$'\t' -e '{ for(i=2;i<=NF;i+=2) { print
$i } }'; echo EXIT)

sleep 10 &
wait "$!"
$ cat main.sh
#!/usr/bin/env bash
# vim: set noexpandtab tabstop=2:

set -v

rm -rf /tmp/trapls/pid
rm -rf /tmp/trapls/log

for s in $(trap -l | awk -v OFS=$'\t' -e '{ for(i=2;i<=NF;i+=2) {
print $i } }' | grep -v SIGSTOP | grep -v SIGCONT; echo EXIT)
do
./script.sh "$s" &
done
wait
$ cat main_kill.sh
#!/usr/bin/env bash
# vim: set noexpandtab tabstop=2:

while IFS= read -r s
do
pid=$(< "/tmp/trapls/pid/$s.txt")
echo "$(date):$s"
kill -s "$s" "$pid"
done < <(trap -l | awk -v OFS=$'\t' -e '{ for(i=2;i<=NF;i+=2) { print
$i } }' | grep -v SIGSTOP | grep -v SIGCONT; echo EXIT)
$ ./main.sh
$ ./main_kill.sh # run in a terminal different from the terminal in
which .main.sh runs.
Fri Nov 16 15:29:40 CST 2018:EXIT
Fri Nov 16 15:29:40 CST 2018:SIGHUP
Fri Nov 16 15:29:40 CST 2018:SIGINT
Fri Nov 16 15:29:40 CST 2018:SIGQUIT
Fri Nov 16 15:29:40 CST 2018:SIGILL
Fri Nov 16 15:29:40 CST 2018:SIGTRAP
Fri Nov 16 15:29:40 CST 2018:SIGABRT
Fri Nov 16 15:29:40 CST 2018:SIGEMT
Fri Nov 16 15:29:40 CST 2018:SIGFPE
Fri Nov 16 15:29:40 CST 2018:SIGKILL
Fri Nov 16 15:29:40 CST 2018:SIGBUS
Fri Nov 16 15:29:40 CST 2018:SIGSEGV
Fri Nov 16 15:29:40 CST 2018:SIGSYS
Fri Nov 16 15:29:40 CST 2018:SIGPIPE
Fri Nov 16 15:29:40 CST 2018:SIGALRM
Fri Nov 16 15:29:40 CST 2018:SIGTERM
Fri Nov 16 15:29:40 CST 2018:SIGURG
Fri Nov 16 15:29:40 CST 2018:SIGTSTP
Fri Nov 16 15:29:40 CST 2018:SIGCHLD
Fri Nov 16 15:29:40 CST 2018:SIGTTIN
Fri Nov 16 15:29:40 CST 2018:SIGTTOU
Fri Nov 16 15:29:40 CST 2018:SIGIO
Fri Nov 16 15:29:41 CST 2018:SIGXCPU
Fri Nov 16 15:29:41 CST 2018:SIGXFSZ
Fri Nov 16 15:29:41 CST 2018:SIGVTALRM
Fri Nov 16 15:29:41 CST 2018:SIGPROF
Fri Nov 16 15:29:41 CST 2018:SIGWINCH
Fri Nov 16 15:29:41 CST 2018:SIGINFO
Fri Nov 16 15:29:41 CST 2018:SIGUSR1
Fri Nov 16 15:29:41 CST 2018:SIGUSR2
$ for f in /tmp/trapls/log/*.txt; do echo "==> $f <=="; cat "$f"; done
==> /tmp/trapls/log/EXIT.txt <==
Fri Nov 16 15:29:46 CST 2018:EXIT
==> /tmp/trapls/log/SIGABRT.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGABRT
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGALRM.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGALRM
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGBUS.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGBUS
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGCHLD.txt <==
Fri Nov 16 15:29:46 CST 2018:EXIT
==> /tmp/trapls/log/SIGEMT.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGEMT
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGFPE.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGFPE
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGHUP.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGHUP
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGILL.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGILL
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGINFO.txt <==
Fri Nov 16 15:29:41 CST 2018:SIGINFO
Fri Nov 16 15:29:41 CST 2018:EXIT
==> /tmp/trapls/log/SIGINT.txt <==
Fri Nov 16 15:29:46 CST 2018:EXIT
==> /tmp/trapls/log/SIGIO.txt <==
Fri Nov 16 15:29:41 CST 2018:SIGIO
Fri Nov 16 15:29:41 CST 2018:EXIT
==> /tmp/trapls/log/SIGKILL.txt <==
==> /tmp/trapls/log/SIGPIPE.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGPIPE
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGPROF.txt <==
Fri Nov 16 15:29:41 CST 2018:SIGPROF
Fri Nov 16 15:29:41 CST 2018:EXIT
==> /tmp/trapls/log/SIGQUIT.txt <==
Fri Nov 16 15:29:46 CST 2018:EXIT
==> /tmp/trapls/log/SIGSEGV.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGSEGV
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGSYS.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGSYS
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGTERM.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGTERM
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGTRAP.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGTRAP
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGTSTP.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGTSTP
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGTTIN.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGTTIN
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGTTOU.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGTTOU
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGURG.txt <==
Fri Nov 16 15:29:40 CST 2018:SIGURG
Fri Nov 16 15:29:40 CST 2018:EXIT
==> /tmp/trapls/log/SIGUSR1.txt <==
Fri Nov 16 15:29:41 CST 2018:SIGUSR1
Fri Nov 16 15:29:41 CST 2018:EXIT
==> /tmp/trapls/log/SIGUSR2.txt <==
Fri Nov 16 15:29:41 CST 2018:SIGUSR2
Fri Nov 16 15:29:41 CST 2018:EXIT
==> /tmp/trapls/log/SIGVTALRM.txt <==
Fri Nov 16 15:29:41 CST 2018:SIGVTALRM
Fri Nov 16 15:29:41 CST 2018:EXIT
==> /tmp/trapls/log/SIGWINCH.txt <==
Fri Nov 16 15:29:41 CST 2018:SIGWINCH
Fri Nov 16 15:29:41 CST 2018:EXIT
==> /tmp/trapls/log/SIGXCPU.txt <==
Fri Nov 16 15:29:41 CST 2018:SIGXCPU
Fri Nov 16 15:29:41 CST 2018:EXIT
==> /tmp/trapls/log/SIGXFSZ.txt <==
Fri Nov 16 15:29:41 CST 2018:SIGXFSZ
Fri Nov 16 15:29:41 CST 2018:EXIT
--
Regards,
Peng
Greg Wooledge
2018-11-16 22:01:43 UTC
Permalink
Post by Peng Yu
Could anybody let me know what signals will result in EXIT? Thanks.
signal(7)

man 7 signal
Peng Yu
2018-11-17 12:52:33 UTC
Permalink
Post by Greg Wooledge
signal(7)
man 7 signal
I don't quite understand signal(7) with respect to bash. For example,

Why are there three values for SIGUSR1 in singal(7), whereas there are
only 30 for SIGUSR1 in bash?

"""
SIGUSR1 30,10,16 Term User-defined signal 1
"""

What the Core is in bash?

"""
SIGQUIT 3 Core Quit from keyboard
"""

Why SIGTTIN leads to EXIT?

"""
SIGTTIN 21,21,26 Stop Terminal input for background process
"""
--
Regards,
Peng
Bob Proulx
2018-11-18 00:26:05 UTC
Permalink
Post by Peng Yu
It seems that
- SIGKILL directly kill the process without triggering EXIT.
SIGKILL is a very special type of signal. It really isn't a signal at
all. What it does is cause the kernel to remove the process from the
run queue. Which means that the process does not receive a signal and
can never receive the signal and can never trap the signal. No signal
handling can be done by the process, ever. It can't log about it,
ever. The process simply stops running.

Which is why it is always bad to kill a process using SIGKILL that you
are not the developer of because you don't know if there are temporary
files or other state that needs to be cleaned up manually. It is
truly an action of last resort to be used only when all else fails
followed by rebooting the system because something must be terribly
broken to have gotten into that need for such a last resort.
Post by Peng Yu
- SIGCHLD does not trigger EXIT.
As documented the default action for SIGCHLD is to ignore it. As with
most things it has a purpose and it is not general but very specific.
Post by Peng Yu
- SIGINT and SIGQUIT are ignored as they are not sent from the same terminal.
There appears to be confusion surrounding foreground process groups.

When bash invokes sleep there are then two processes running in the
foreground process group. When the tty driver sees Control-C (see
'stty -a' for intr = ^C) then it sends the foreground process group an
interrupt with SIGINT. Both the shell and the sleep process are in
the foreground process group and receive the SIGINT. As per signal(7)
documentation the default action is to terminate therefore the sleep
process terminates. The bash process has trapped SIGINT and therefore
invokes the logical EXIT trap just before exiting.

If you are sending a SIGINT to the bash (via $$) process then *only*
the bash process is receiving the signal. The sleep process was not
sent the signal, does not receive it, and sleeps for the duration when
it exits normally.

For your testing I think you should be sending to the foreground
process group.

man kill

Negative PID values may be used to choose whole process groups; see
the PGID column in ps command output.

Sending 'kill -s SIGINT -$pid' will send to the process group.
(Note that dash's builtin kill does not handle this syntax.)
Post by Peng Yu
So, other than these signals, all the rest signals cause EXIT. Is my
conclusion correct? Some of the conclusion is counter-intuitive.
Bash automatically traps signals that cause the process to exit, see
man 7 signal, and therefore the EXIT trap runs. Other shells such as
POSIX shells such as dash do not do this. In POSIX shells if you want
to trap a signal such that the logical EXIT trap is invoked then this
must be coded up with a trap handler explicitly.

My recommendation is to only ever care about HUP, INT, QUIT, and TERM
for cleanup actions. Sometimes, rarely, there may be a special
purpose use of SIGUSR1 and/or SIGUSR2. (And in very, very, very rare
cases PIPE but special care must be taken when dealing with SIGPIPE to
avoid an infinite stack overflow condition. You have been warned.)

Trapping other signals is a sign of a program design problem.

Bash traps signals for you automatically and will run the logical EXIT
trap but POSIX shells do not. That is one reason I believe it is
always a good idea to code them explicitly so that they will work
portably across shells other than bash too. But regardless of that if
trapping signals then I like to see a diagnostic message printed in
the case of a signal and that requires explicitly trapping them.
Post by Peng Yu
For example, what is the reason to EXIT when SIGWINCH is triggered?
What makes you think bash exits when receiving SIGWINCH? Double check
your test case.
Post by Peng Yu
Could anybody let me know what signals will result in EXIT? Thanks.
Post by Greg Wooledge
signal(7)
man 7 signal
I don't quite understand signal(7) with respect to bash. For example,
Why are there three values for SIGUSR1 in singal(7), whereas there are
only 30 for SIGUSR1 in bash?
SIGUSR1 30,10,16 Term User-defined signal 1
In man 7 signal:

(Where three values are given, the first one is usually valid for alpha
and sparc, the middle one for x86, arm, and most other architectures,
and the last one for mips. (Values for parisc are not shown; see the
Linux kernel source for signal numbering on that architecture.) A dash
(-) denotes that a signal is absent on the corresponding architecture.
Post by Peng Yu
What the Core is in bash?
SIGQUIT 3 Core Quit from keyboard
The idea behind SIGQUIT invoked from the tty driver via Control-\ is
that a developer authoring a C program can cause a program in an
infinite loop to dump a core (memory used to be ferrite core at the
time) image to disk in a file named "core" and kill the program. Then
the developer can start the debugger on the source code referencing
the core file and see what the program counter is pointing at and dump
the value of variables and registers. This is usually enough to
determine why a program was in an infinite loop and fix it.

These days core dumps are almost always disabled (ulimit -c) by
default. That means that for most non-developers these days
triggering SIGQUIT with Control-\ is simply another signal that should
terminate the program. Bash itself shouldn't dump a core image
because that wouldn't help in debugging the shell script as it is only
indirectly associated. Your shell script program might choose to dump
some type of program state for debugging purposes when trapping
SIGQUIT however. I haven't seen that technique used much though and
can't recommend it as better debugging techniques exist.
Post by Peng Yu
Why SIGTTIN leads to EXIT?
SIGTTIN 21,21,26 Stop Terminal input for background process
SIGTTIN leads to job control stopping the program not exiting. If you
continue the program then eventually when it exists the logical EXIT
trap will be invoked.

This is a complex area with subtle interactions. I may have made a
mistake in the above description. It was written with the best of
intentions though.

Bob
Paul Wagner
2018-11-19 09:46:50 UTC
Permalink
Dear Bob,

thanks a lot for explaining all those signalling details. As someone
who is having troubles with a pipeline and SIGPIPE (as posted a few
weeks ago here) myself, could you point to resources explaining the
whole *nix signalling infrastructure?

Thanks for your help,

Paul
Post by Bob Proulx
Post by Peng Yu
It seems that
- SIGKILL directly kill the process without triggering EXIT.
SIGKILL is a very special type of signal. It really isn't a signal at
all. What it does is cause the kernel to remove the process from the
run queue. Which means that the process does not receive a signal and
can never receive the signal and can never trap the signal. No signal
handling can be done by the process, ever. It can't log about it,
ever. The process simply stops running.
Which is why it is always bad to kill a process using SIGKILL that you
are not the developer of because you don't know if there are temporary
files or other state that needs to be cleaned up manually. It is
truly an action of last resort to be used only when all else fails
followed by rebooting the system because something must be terribly
broken to have gotten into that need for such a last resort.
Post by Peng Yu
- SIGCHLD does not trigger EXIT.
As documented the default action for SIGCHLD is to ignore it. As with
most things it has a purpose and it is not general but very specific.
Post by Peng Yu
- SIGINT and SIGQUIT are ignored as they are not sent from the same terminal.
There appears to be confusion surrounding foreground process groups.
When bash invokes sleep there are then two processes running in the
foreground process group. When the tty driver sees Control-C (see
'stty -a' for intr = ^C) then it sends the foreground process group an
interrupt with SIGINT. Both the shell and the sleep process are in
the foreground process group and receive the SIGINT. As per signal(7)
documentation the default action is to terminate therefore the sleep
process terminates. The bash process has trapped SIGINT and therefore
invokes the logical EXIT trap just before exiting.
If you are sending a SIGINT to the bash (via $$) process then *only*
the bash process is receiving the signal. The sleep process was not
sent the signal, does not receive it, and sleeps for the duration when
it exits normally.
For your testing I think you should be sending to the foreground
process group.
man kill
Negative PID values may be used to choose whole process groups; see
the PGID column in ps command output.
Sending 'kill -s SIGINT -$pid' will send to the process group.
(Note that dash's builtin kill does not handle this syntax.)
Post by Peng Yu
So, other than these signals, all the rest signals cause EXIT. Is my
conclusion correct? Some of the conclusion is counter-intuitive.
Bash automatically traps signals that cause the process to exit, see
man 7 signal, and therefore the EXIT trap runs. Other shells such as
POSIX shells such as dash do not do this. In POSIX shells if you want
to trap a signal such that the logical EXIT trap is invoked then this
must be coded up with a trap handler explicitly.
My recommendation is to only ever care about HUP, INT, QUIT, and TERM
for cleanup actions. Sometimes, rarely, there may be a special
purpose use of SIGUSR1 and/or SIGUSR2. (And in very, very, very rare
cases PIPE but special care must be taken when dealing with SIGPIPE to
avoid an infinite stack overflow condition. You have been warned.)
Trapping other signals is a sign of a program design problem.
Bash traps signals for you automatically and will run the logical EXIT
trap but POSIX shells do not. That is one reason I believe it is
always a good idea to code them explicitly so that they will work
portably across shells other than bash too. But regardless of that if
trapping signals then I like to see a diagnostic message printed in
the case of a signal and that requires explicitly trapping them.
Post by Peng Yu
For example, what is the reason to EXIT when SIGWINCH is triggered?
What makes you think bash exits when receiving SIGWINCH? Double check
your test case.
Post by Peng Yu
Could anybody let me know what signals will result in EXIT? Thanks.
Post by Greg Wooledge
signal(7)
man 7 signal
I don't quite understand signal(7) with respect to bash. For example,
Why are there three values for SIGUSR1 in singal(7), whereas there are
only 30 for SIGUSR1 in bash?
SIGUSR1 30,10,16 Term User-defined signal 1
(Where three values are given, the first one is usually valid for alpha
and sparc, the middle one for x86, arm, and most other
architectures,
and the last one for mips. (Values for parisc are not shown; see the
Linux kernel source for signal numbering on that architecture.)
A dash
(-) denotes that a signal is absent on the corresponding architecture.
Post by Peng Yu
What the Core is in bash?
SIGQUIT 3 Core Quit from keyboard
The idea behind SIGQUIT invoked from the tty driver via Control-\ is
that a developer authoring a C program can cause a program in an
infinite loop to dump a core (memory used to be ferrite core at the
time) image to disk in a file named "core" and kill the program. Then
the developer can start the debugger on the source code referencing
the core file and see what the program counter is pointing at and dump
the value of variables and registers. This is usually enough to
determine why a program was in an infinite loop and fix it.
These days core dumps are almost always disabled (ulimit -c) by
default. That means that for most non-developers these days
triggering SIGQUIT with Control-\ is simply another signal that should
terminate the program. Bash itself shouldn't dump a core image
because that wouldn't help in debugging the shell script as it is only
indirectly associated. Your shell script program might choose to dump
some type of program state for debugging purposes when trapping
SIGQUIT however. I haven't seen that technique used much though and
can't recommend it as better debugging techniques exist.
Post by Peng Yu
Why SIGTTIN leads to EXIT?
SIGTTIN 21,21,26 Stop Terminal input for background process
SIGTTIN leads to job control stopping the program not exiting. If you
continue the program then eventually when it exists the logical EXIT
trap will be invoked.
This is a complex area with subtle interactions. I may have made a
mistake in the above description. It was written with the best of
intentions though.
Bob
Bob Proulx
2018-11-23 19:42:36 UTC
Permalink
Hi Paul,
thanks a lot for explaining all those signalling details. As someone who is
having troubles with a pipeline and SIGPIPE (as posted a few weeks ago here)
myself, could you point to resources explaining the whole *nix signalling
infrastructure?
By far the best and most comprehensive reference I know is what is now
the classic Advanced Programming in the UNIX Environment originally by
the late W. Richard Stevens and now updated by Steven Rago. Andy Chu
recommended this book in another thread just recently and I
wholeheartedly second that recommendation.

The topic you are asking about reaches into signal handling relating
to shells and Martin Cracauer's excellent page on this should also be
on the reading list too.

Proper handling of SIGINT/SIGQUIT
https://www.cons.org/cracauer/sigint.html

I don't know Android really at all and therefore I didn't have any
help for you in your previous message. But I will go look at it now
and see if there might be something I can think of there regardless.

Bob

Loading...