2 questions about signals

S

Spiros Bousbouras

In 7.14.1.1 par.5 we read

If the signal occurs other than as the result
of calling the abort or raise function, the behavior
is undefined if the signal handler refers to any
object with static storage duration other than by
assigning a value to an object declared as volatile
sig_atomic_t,

So that means that you are not allowed to read the value of such
an object. So for example

static volatile sig_atomic_t a ;
void handler(int i) {
a = 2 ; /* OK */
a++ ; /* UB */
if ( a == 3 ) { /* UB */
...
}
}

Have I got it right ? If yes can anyone give me some insight on
why writing is allowed but reading is not ?

Par. 3:

If and when the function returns, if the value of sig is
SIGFPE, SIGILL, SIGSEGV, or any other implementation-defined
value corresponding to a computational exception, the
behavior is undefined; otherwise the program will resume
execution at the point it was interrupted.

If the signal was generated by raise() why is it not ok for the
handler to return since no actual computational exception
occurred ?
 
J

James Kuyper

Spiros said:
In 7.14.1.1 par.5 we read

If the signal occurs other than as the result
of calling the abort or raise function, the behavior
is undefined if the signal handler refers to any
object with static storage duration other than by
assigning a value to an object declared as volatile
sig_atomic_t,

So that means that you are not allowed to read the value of such
an object. ...

No, it means that a program is allowed to read the value, but that the
behavior of the program is undefined if such an attempt is made.

The standard disallows nothing. It only tells you what is, or is not,
guaranteed to happen if a program attempts something. For code such as
this, what the standard says is that nothing is guaranteed (not even the
failure of such a program).
... So for example

static volatile sig_atomic_t a ;
void handler(int i) {
a = 2 ; /* OK */
a++ ; /* UB */
if ( a == 3 ) { /* UB */
...
}
}

Have I got it right ? If yes can anyone give me some insight on
why writing is allowed but reading is not ?

The only answer I can give you is from the C rationale (n897). The
section corresponding to 7.14 says nothing directly about this,
referring you to comments on section 5.2.3. Those comments say: "Signals
are difficult to specify in a system-independent way. The C89 Committee
concluded that about the only thing a strictly conforming program can do
in a signal handler is to assign a value to a volatile static variable
which can be written uninterruptedly and promptly return. (The header
<signal.h> specifies a type sig_atomic_t which can be so written.)"

This implies that there was at least one system, probably more than one,
that was considered by the Committee where it would be problematic to
allow reading of such a variable. However, I have no idea which systems
those would be, nor why it was problematic.
Par. 3:

If and when the function returns, if the value of sig is
SIGFPE, SIGILL, SIGSEGV, or any other implementation-defined
value corresponding to a computational exception, the
behavior is undefined; otherwise the program will resume
execution at the point it was interrupted.

If the signal was generated by raise() why is it not ok for the
handler to return since no actual computational exception
occurred ?

I'm afraid I can't answer that question at all. Sorry.
 
S

Spiros Bousbouras

Others have tried unsuccessfully to obtain an answer, so good luck with
that. You may hear some mumblings about "state", but you'd be unwise to
accept that uncritically.

And I can't accept it critically either since I don't know what
it means ;-)
You appear to be asking why handlers for raise()-generated SIGFPE,
SIGILL, SIGSEGV, etc., aren't allowed to return, given that
the programmer is merely "faking" a computational exception.
Yes.

raise()-generated signals have the same semantics as non-raise()-
generated signals (technically, you have to take into account
7.14.1.1{4-5}).

So they're not the same after all. If for example the signal
handler was entered because of raise() then you are allowed to
read the value of a static object.
Why would you want it otherwise?

I'm not particularly fussed but first it would make testing
easier. Say for example your application has different handlers
for different signals and you have written a test programme
which you will use to determine if the handlers behave as they
should. If each handler is allowed to return then you can test
all of them in one go without having to restart the programme.

Second , I find it more intuitive.

Third , I was curious why par. 5 treats specially the cases where
the handler was entered because of abort() or raise() but par. 3
doesn't. Perhaps a better question to ask would be why the
special treatment in par. 5 ?
 
S

Spiros Bousbouras

The only answer I can give you is from the C rationale (n897). The
section corresponding to 7.14 says nothing directly about this,
referring you to comments on section 5.2.3. Those comments say: "Signals
are difficult to specify in a system-independent way. The C89 Committee
concluded that about the only thing a strictly conforming program can do
in a signal handler is to assign a value to a volatile static variable
which can be written uninterruptedly and promptly return.

But the standard allows plenty of other stuff to be done in a
signal handler. So I don't understand what they mean.
 
J

jameskuyper

Spiros said:
But the standard allows plenty of other stuff to be done in a
signal handler. So I don't understand what they mean.

Does it? Keep in mind that "The functions in the standard library are
not guaranteed to be reentrant and may modify objects with static
storage duration." (7.1.4p4) and "Thus, a signal handler cannot, in
general, call standard library functions." (footnote 164). Also,
consider 7.14.1.1p5: "If the signal occurs other than as the result of
calling the abort or raise function, the behavior is undefined if the
signal handler refers to any object with static storage duration other
than by assigning a value to an object declared as volatile
sig_atomic_t, or the signal handler calls any function in the standard
library other than the abort function, the _Exit function, or the
signal function with the first argument equal to the signal number
corresponding to the signal that caused the invocation of the handler.
Furthermore, if such a call to the signal function results in a
SIG_ERR return, the
value of errno is indeterminate."

The net result of these restrictions is that the only inputs a
strictly-conforming signal handler can make any use of are it's
arguments, and the only information it can produce that will survive
after the signal handler returns is the values it writes to static
volatile objects of type sig_atomic_t. Keep in mind that signals can
be raised asynchronously, so that if a signal handler writes to
multiple objects of that type, there's no way to ensure that two
different such object were written to by the same invocation of the
signal handler, which substantially reduces the potential value of
doing so. Alternatively, the signal could pass an exit status to _Exit
(), but that's a pretty limited information channel too.

You can write some pretty complicated sophisticated code within those
restrictions - but most of that complexity and sophistication will go
to waste as soon as the signal handler returns or calls _Exit(). Give
it a try: what's the most complicated, USEFUL thing you can think of
putting in a signal handler, that fits within these restrictions?
 
B

blmblm

Does it? Keep in mind that "The functions in the standard library are
not guaranteed to be reentrant and may modify objects with static
storage duration." (7.1.4p4) and "Thus, a signal handler cannot, in
general, call standard library functions." (footnote 164). Also,
consider 7.14.1.1p5: "If the signal occurs other than as the result of
calling the abort or raise function, the behavior is undefined if the
signal handler refers to any object with static storage duration other
than by assigning a value to an object declared as volatile
sig_atomic_t, or the signal handler calls any function in the standard
library other than the abort function, the _Exit function, or the
signal function with the first argument equal to the signal number
corresponding to the signal that caused the invocation of the handler.
Furthermore, if such a call to the signal function results in a
SIG_ERR return, the
value of errno is indeterminate."

The net result of these restrictions is that the only inputs a
strictly-conforming signal handler can make any use of are it's
arguments, and the only information it can produce that will survive
after the signal handler returns is the values it writes to static
volatile objects of type sig_atomic_t. Keep in mind that signals can
be raised asynchronously, so that if a signal handler writes to
multiple objects of that type, there's no way to ensure that two
different such object were written to by the same invocation of the
signal handler, which substantially reduces the potential value of
doing so. Alternatively, the signal could pass an exit status to _Exit
(), but that's a pretty limited information channel too.

You can write some pretty complicated sophisticated code within those
restrictions - but most of that complexity and sophistication will go
to waste as soon as the signal handler returns or calls _Exit(). Give
it a try: what's the most complicated, USEFUL thing you can think of
putting in a signal handler, that fits within these restrictions?

Very interesting -- and relevant to a recent discussion/argument I've
been having about the interaction of signal handlers and fork().
Since this is UNIX-specific, I should probably set follow-ups to
comp.unix.programmer .... :

As I understand things, signal handlers established before a fork()
apply, after the fork(), to both parent and child processes.
I had been thinking that if one wanted to have the signal handler
do different things in parent and child, one could put the result
of getpid() in a global variable before the fork() and then, in
the signal handler, compare this global variable to the (current)
return value of getpid(). But it sounds like this is not guaranteed
to work, is it? I had hoped that perhaps things were a little
different in POSIX-land, but apparently not, since what seems to
be an official definition

http://www.opengroup.org/onlinepubs/000095399/functions/sigaction.html

repeats the restrictions mentioned upthread.

I did find another reference that says that reading from other
global variables is "safe", with some restrictions:

http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_21.html

("Merely reading from a memory object is safe provided that you can deal
with any of the values that might appear in the object at a time when
the signal can be delivered.")

But now I wonder.

*Is* there some way to reliably distinguish, in a signal handler,
between parent and child processes?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top