Re: signals: Bug or manpage inconsistency?
From: Michael Kerrisk (man-pages)
Date: Wed May 31 2017 - 02:51:23 EST
Hi Thomas,
On 05/30/2017 03:21 PM, Thomas Gleixner wrote:
> While trying to address the longstanding FIXME in the posix timer code
> related to ignored signals, I stumbled over the following issue:
>
> I blocked the signal of the timer, then installed the SIG_IGN handler,
> created and started the timer. After a short sleep the timer has fired
> several times, but it's still ignored AND blocked.
>
> Calling sigpending() after that has the timer signal set. See test case
> below.
>
> But 'man sigpending' says:
>
> "If a signal is both blocked and has a disposition of "ignored", it is _not_
> added to the mask of pending signals when generated."
>
> So something is clearly wrong here.
Yes. I confirm the behavior you saw with your test program. And, I'm a little
surprised by the man page text (mainly because I wrote it :-}).
Here's what I understand as longstanding UNIX behavior: if a blocked signal
is pending, and then we set the disposition to SIG_IGN, the signal is
removed the pending set.
The reason I'm surprised about the man page text is because I think I wrote
it (in 2013) while thinking about that case. I don't recall ever much thinking
about the case "signal is generated while it is blocked and its disposition
has been explicitly set to SIG_IGN" (and the commit message that goes with
the man page change isn't really enlightening as to my thinking either...)
> The same happens with sigwait() while the signal is still blocked and
> ignored, it returns with that signal number and has the signal dequeued.
Yes. And one can also see that the signal is pending in the ShdPnd field
of /proc/PID/status.
> The whole blocked vs. ignored handling is inconsistent both in the posix
> spec and in the kernel.
>
> The only thing vs. ignored signals what the spec mandates is:
>
> SIG_IGN:
>
> Delivery of the signal shall have no effect on the process.
>
> ...
>
> Setting a signal action to SIG_IGN for a signal that is pending shall
> cause the pending signal to be discarded, whether or not it is blocked.
Yes, that's the bit I knew.
> ...
>
> Any queued values pending shall be discarded and the resources used to
> queue them shall be released and made available to queue other signals.
>
> That's exactly what the kernel does in do_sigaction().
>
> And for everything else the spec is blurry:
>
> If the action associated with a blocked signal is to ignore the signal
> and if that signal is generated for the process, it is unspecified
> whether the signal is discarded immediately upon generation or remains
> pending.
>
> So the kernel has chosen to keep them pending for whatever reasons, which
> does not make any sense to me, but there is probably a historic reason.
>
> The commit which added the queuing of blocked and ignored signals is in the
> history tree with a pretty useless changelog.
>
> https://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git
>
> commit 98fc8ab9e74389e0c7001052597f61336dc62833
> Author: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxxx>
> Date: Tue Feb 11 20:49:03 2003 -0800
>
> Don't wake up processes unnecessarily for ignored signals
>
> It rewrites sig_ignored() and adds the following to it:
>
> + /*
> + * Blocked signals are never ignored, since the
> + * signal handler may change by the time it is
> + * unblocked.
> + */
> + if (sigismember(&t->blocked, sig))
> + return 0;
>
> I have no idea how that is related to $subject of the commit and why this
> decision was made.
>
> Linus, any recollection?
>
> IMO, it's perfectly reasonable to discard ignored signals even when the
> signal is in the blocked mask. When its unblocked and SIG_IGN is replaced
> then the next signal will be delivered. But hell knows, how much user space
> depends on this weird behaviour by now.
Clearly, something needs to be fixed in the man page. I want to do
a little more investigation first though.
Cheers,
Michael
>
> Thanks,
>
> tglx
>
> 8<-------------
>
> #define _GNU_SOURCE
> #include <stdint.h>
> #include <stdio.h>
> #include <unistd.h>
> #include <signal.h>
> #include <string.h>
> #include <time.h>
>
> #include <sys/types.h>
> #include <sys/time.h>
> #include <sys/syscall.h>
>
> int main(void)
> {
> struct itimerspec tspec;
> struct sigevent sigev;
> struct sigaction action;
> int signal, sig = SIGALRM;
> sigset_t set, pend;
> timer_t timerid;
>
> sigemptyset(&set);
> sigaddset(&set, sig);
> sigprocmask(SIG_BLOCK, &set, NULL);
>
> memset(&action, 0, sizeof(action));
> action.sa_handler = SIG_IGN;
> sigaction(sig, &action, NULL);
>
> memset(&sigev, 0, sizeof(sigev));
> sigev.sigev_notify = SIGEV_SIGNAL;
> sigev.sigev_signo = sig;
> sigev.sigev_value.sival_ptr = &timerid;
> timer_create(CLOCK_REALTIME, &sigev, &timerid);
>
> tspec.it_interval.tv_sec = 0;
> tspec.it_interval.tv_nsec = 100 * 1e6;
>
> tspec.it_value.tv_sec = 0;
> tspec.it_value.tv_nsec = 100 * 1e6;
>
> timer_settime(timerid, 0, &tspec, NULL);
>
> sleep(1);
>
> sigpending(&pend);
> if (sigismember(&pend, sig)) {
> /* This is reached */
> printf("Timer signal pending 1\n");
> sigwait(&set, &signal);
> printf("sigwait signal: %d\n", signal);
> }
>
> sleep(1);
>
> printf("Unblock\n");
> sigprocmask(SIG_UNBLOCK, &set, NULL);
>
> sigpending(&pend);
> if (sigismember(&pend, sig)) {
> /* This is not reached */
> printf("Timer signal pending 2\n");
> sigwait(&set, &signal);
> printf("sigwait signal: %d\n", signal);
> }
>
> sleep(1);
>
> sigpending(&pend);
> if (sigismember(&pend, sig)) {
> /* This is not reached */
> printf("Timer signal pending 3\n");
> sigwait(&set, &signal);
> printf("sigwait signal: %d\n", signal);
> }
>
> timer_delete(timerid);
> return 0;
> }
>
>
>
--
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/