typdef'ing from sig_atomic_t valid?

M

Mark Piffer

Does a typedef like
typedef sig_atomic_t atomic_int;
produce an atomically write/readable type? From what I read in the
standard I would guess yes, as sig_atomic_t itself is produced by a
typedef and typedefs don't really introduce new types. For another
question though, how to define an unsigned atomic type? I can't find
anything in the standard that would justify it, if sig_atomic_t is
signed, not even looking into signal.h and using the unsigned type
from which sig_atomic_t is constructed, as it could well have a
different width/object representation.

Mark
 
I

Ian Woods

(e-mail address removed) (Mark Piffer) wrote in
Does a typedef like
typedef sig_atomic_t atomic_int;
produce an atomically write/readable type? From what I read in the
standard I would guess yes, as sig_atomic_t itself is produced by a
typedef and typedefs don't really introduce new types. For another
question though, how to define an unsigned atomic type? I can't find
anything in the standard that would justify it, if sig_atomic_t is
signed, not even looking into signal.h and using the unsigned type
from which sig_atomic_t is constructed, as it could well have a
different width/object representation.

Mark

sig_atomic_t has a range of SIG_ATOMIC_MIN to SIG_ATOMIC_MAX and may be
either signed or unsigned. Signed, the minimum range is -127 to 127.
Unsigned, the minimum range is 0 to 255. Or, in other words... you know for
definate that sig_atomic_t can hold values from 0 to 127 whether it's
signed or unsigned. (All from C99). Either way, I can't see any way of
making a "signed" or "unsigned" version of sig_atomic_t.

sig_atomic_t is best treated IMO as a completely seperate type from all the
other integer types rather than thinking of it as "typedef"ed from some
other type. The fact that the compiler has to make operations on it atomic
means that the compiler at some level has to know that isn't just a plain
old integer type.

Ian Woods
 
D

Dan Pop

In said:
sig_atomic_t is best treated IMO as a completely seperate type from all the
other integer types rather than thinking of it as "typedef"ed from some
other type. The fact that the compiler has to make operations on it atomic
means that the compiler at some level has to know that isn't just a plain
old integer type.

If the compiler *naturally* handles integer type T atomically, then
it couldn't care less whether the object was defined as having type
T directly or via sig_atomic_t.

It would take a less than 8-bit processor so that none of the integer
types can be handled atomically, requiring the additional information
you're talking about. But I have yet to hear about standard C
implementations for such processors...

I agree that maximally portable code can only rely on the 0..127 range
for sig_atomic_t, but this range should be more than enough for most
applications. Keep in mind that only *accesses* to sig_atomic_t are
guaranteed to be performed atomically, so the only meaningful operations
on this type are assigning a value and reading the current value.

Dan
 
K

Kevin D. Quitt

Keep in mind that only *accesses* to sig_atomic_t are
guaranteed to be performed atomically, so the only meaningful operations
on this type are assigning a value and reading the current value.

Does this mean that atomic read-modify-write is not supported? If not, of
what use is sig_atomic_t?
 
T

those who know me have no need of my name

in comp.lang.c i read:
On 22 Apr 2004 16:28:18 GMT, (e-mail address removed) (Dan Pop) wrote:

Does this mean that atomic read-modify-write is not supported? If not, of
what use is sig_atomic_t?

correct, read-modify-write is not required to be atomic. it's typical use
is as a flag, not as a counter, e.g.,

#include <stdio.h>
#include <signal.h>

volatile sig_atomic_t attention;

void attention_handler(int sig)
{
attention = 1;
if (SIG_ERR == signal(sig, SIG_DFL)) abort();
}

int main(void)
{
if (SIG_ERR == signal(SIGINT, attention_handler)) abort();
for (long i=0; i<LONG_MAX; i++)
{
/* ... */
if (attention)
{
fprintf(stderr, "interrupted at %ld\n", i);
break;
}
/* ... */
}
}

signal handling and atomic types and operations in standard c are very
limited, else it would be too hard to have hosted implementations on a
significant number of diverse platforms. typically there are other
standards or extensions which enhance an environment so much more can be
done, but you lose some portability (perhaps quite a lot) in the bargain.
 
K

Kevin D. Quitt

correct, read-modify-write is not required to be atomic. it's typical use
is as a flag, not as a counter, e.g.,

Clearly this is a meaning of atomic with which I'm not familiar (i.e.,
bus-atomic). There is insufficient information in the Standard to define
what it means by atomic. 7.14 p2 says:

The type defined is
sig_atomic_t
which is the (possibly volatile-qualified) integer type of an object that
can be accessed as an atomic entity, even in the presence of asynchronous
interrupts.

At first that sounds interesting, since that would be very useful for
structs, especially linked lists. But that's clearly not the case; 7.18.3
says:

If sig_atomic_t (see 7.14) is defined as a signed integer type, the value
of SIG_ATOMIC_MIN shall be no greater than -127 and the value of
SIG_ATOMIC_MAX shall be no less than 127; otherwise, sig_atomic_t is
defined as an unsigned integer type, and the value of SIG_ATOMIC_MIN shall
be 0 and the value of SIG_ATOMIC_MAX shall be no less than 255.

So it isn't necessarily more than a byte. How can access to a byte not be
atomic? With an 8-bit processor and bus, it's conceivable that 16- or
32-bit fetches might not be atomic, but 8-bit must be.

And if it must also be declared as volatile, I can't see *any* use for
sig_atomic_t. Please help me cure my ignorance. Point me at the detailed
definition of 'atomic' according to C, or show me code where an object of
type volatile sig_atomic_t acts differently that an object of type
volatile.
 
E

Eric Sosman

Kevin D. Quitt said:
[...]
So it isn't necessarily more than a byte. How can access to a byte not be
atomic? With an 8-bit processor and bus, it's conceivable that 16- or
32-bit fetches might not be atomic, but 8-bit must be.

On the original Alpha processors, the memory subsystem
supported only 64- and 32-bit accesses. If you wanted to
store a byte, you had to fetch the entire containing region,
do some shifting and masking, and store the whole thing
back again. So much for the atomicity of bytes.
 
B

Ben Pfaff

Kevin D. Quitt said:
So it isn't necessarily more than a byte. How can access to a byte not be
atomic? With an 8-bit processor and bus, it's conceivable that 16- or
32-bit fetches might not be atomic, but 8-bit must be.

What about using a 1-bit bus? Or a 32-bit bus? Access to a byte
can easily be non-atomic.
 
D

Dan Pop

In said:
Does this mean that atomic read-modify-write is not supported?

The type defined is

sig_atomic_t

which is the integral type of an object that can be accessed as an
atomic entity, even in the presence of asynchronous interrupts.
....
If the signal occurs other than as the result of calling the abort
or raise function, the behavior is undefined if the signal handler
^^^^^^^^^^^^^^^^^^^^^^^^^
calls any function in the standard library other than the signal
function itself or refers to any object with static storage duration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
other than by assigning a value to a static storage duration variable
^^^^^^^^^^^====================^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
of type volatile sig_atomic_t.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If not, of what use is sig_atomic_t?

A volatile sig_atomic_t object can be used as a flag that the signal
handler has been executed.

Typical example:

#include <stdio.h>
#include <signal.h>

volatile sig_atomic_t gotsig = 0;

void handler(int signo)
{
gotsig = signo;
}

int main()
{
signal(SIGINT, handler);
puts("Press the interrupt key to exit.");
while (gotsig == 0) ;
printf ("The program received signal %d.\n", (int)gotsig);
return 0;
}

Some people object to the fact that the signal number cannot be
represented by a sig_atomic_t object. I ignore them.

Dan
 
K

Kevin D. Quitt

OK, thanks to the both of your for removing one set of my (many, I'm sure)
blinders.

(Except, of course, you invoked a read-modify-write, which would have been
a really useful thing to have been in the standard. But never mind.)
 
K

Kevin D. Quitt

function itself or refers to any object with static storage duration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
other than by assigning a value to a static storage duration variable
^^^^^^^^^^^====================^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
of type volatile sig_atomic_t.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

OK, so by definition, the only thing a signal handler can write to that's
available to the rest of the world is a sig_atomic_t. And it has to be
volatile to the rest of the world to make access correct. So the only
thing that makes sig_atomic_t useful is the fact that it's defined to be
useful.

A volatile sig_atomic_t object can be used as a flag that the signal
handler has been executed.

Simply declaring that byte volatile would have the same effect under any
set of circumstances I can imagine. Clearly I'm having trouble with my
imagination.

Some people object to the fact that the signal number cannot be
represented by a sig_atomic_t object. I ignore them.

Me, too. It's meaningless in light of the definition of sig_atomic_t.
 
T

those who know me have no need of my name

in comp.lang.c i read:
On 23 Apr 2004 18:19:04 GMT, (e-mail address removed) (Dan Pop) wrote:

Me, too. It's meaningless in light of the definition of sig_atomic_t.

i think the point is that it is possible for SIGINT to have a value outside
the range of sig_atomic_t. i've never seen it in the wild, but if one is
usually pedantic about the standard, especially having just quoted it, it's
not entirely misplaced to expect mention of the possibility -- even a left-
handed mention.
 
K

Kevin D. Quitt

i think the point is that it is possible for SIGINT to have a value outside
the range of sig_atomic_t.

I was only saying that *that* problem is a small one compared to the
problematic utility of sig_atomic_t as defined.

i've never seen it in the wild, but if one is
usually pedantic about the standard, especially having just quoted it, it's
not entirely misplaced to expect mention of the possibility -- even a left-
handed mention.

Completely fair of course. I'm really just trying to cure my ignorance,
but I'm beginning to think that I do understand C's idea of "atomic" - and
that it isn't what "atomic" means to most everybody else.
 
M

Michael Wojcik

... I'm beginning to think that I do understand C's idea of "atomic" - and
that it isn't what "atomic" means to most everybody else.

I can't speak for "most everybody else", but I've never thought
"atomic" implied "atomic read-and-update" in the context of the C
standard. Obviously "atomic" in computer science usually means that
some operation will either be performed completely or not at all, but
what that operation is can vary widely depending on context.

In the case of sig_atomic_t ISTM that the intent of the standard is
clear: assigning to a sig_atomic_t variable, or referencing one,
will be an atomic operation. The assignment, or reference, will
occur completely or not at all.

Consider a hypothetical conforming implementation on a processor with
an 8-bit bus, where a 16-bit store requires two instructions. An
asynchronous signal could interrupt that sequence between the two
instructions and leave a 16-bit value partially updated. On such an
implementation, sig_atomic_t would have to be an 8-bit integer
(signed or unsigned char, where CHAR_BIT was 8) so that no
sig_atomic_t variable could ever have a partially-updated value.

That said, sig_atomic_t as defined in the standard adds relatively
little value, because there are few cases where strictly-conforming
code could read a partially-updated value from any object. In
particular, a signal handler for an asynchronous signal cannot read
such a value, since it cannot refer to any object with static storage
duration except to assign to a volatile sig_atomic_t.

Footnote 109 (C90) states that another signal while in a handler for
an asynchronous signal causes undefined behavior. Footnotes aren't
normative, but clearly the standard doesn't intend to cover this case
(where a handler's assignment to a variable is interrupted by another
signal) with sig_atomic_t either.

The only other case I can think of is one where:

- Function A establishes a jmp_buf with setjmp.
- Function B assigns a value to a file-scope object X.
- Signal handler H longjmps back to A.
- A, when returning from setjmp via longjmp, refers to X.
- An asynchronous signal occurs while B is assigning to X.

However, it appears to me that this is not guaranteed to work by
the standard (I'm looking at C90), because while it says that a
signal handler may exit by calling longjmp, it also says it's not
allowed to "refer[] to any object with static storage duration"
except for the aforementioned assignment to a volatile sig_atomic_t.
Does calling longjmp constitute referring to the jmp_buf? Also,
in the case of asynchronous signals (ie ones not caused by abort or
raise), handlers are prohibited from calling any library function
other than signal. So as far as I can tell, there are no circum-
stances where a partial assignment could be detected (by strictly-
conforming code) anyway.

I suspect sig_atomic_t is one of those things which has semantics
that are not actually useful for strictly-conforming code, but are
plausibly useful in a variety of implementations as a common
extension. Specifically, I suspect that in most implementations
an asynchronous handler can safely refer to the value of a volatile
sig_atomic_t (as well as assigning to it), and in this case its
atomicity is important (as it guarantees that the handler will not
see a partial value).
 
D

Dan Pop

In said:
Simply declaring that byte volatile would have the same effect under any
set of circumstances I can imagine. Clearly I'm having trouble with my
imagination.

Clearly. Imagine a traditional Cray processor, that can only access
64-bit words, but whose C implementation uses 8-bit bytes. How do you
write something to one *byte* atomically?

At the other range of the spectre, imagine a 4-bit processor.
This is a pathological case, where sig_atomic_t is different
from normal C types: the compiler has to disable interrupts (errr,
delivery of asynchronous signals) while writing or reading all the 8+
bits composing the sig_atomic_t type, while such precautions are not
necessary for ordinary byte accesses.

Dan
 
D

Dan Pop

That said, sig_atomic_t as defined in the standard adds relatively
little value, because there are few cases where strictly-conforming
code could read a partially-updated value from any object. In
particular, a signal handler for an asynchronous signal cannot read
such a value, since it cannot refer to any object with static storage
duration except to assign to a volatile sig_atomic_t.

It's the ordinary code that is exposed to this problem, not the signal
handler. Imagine that this code has read half of the flag, the signal
handler has been executed, than the interrupted code resumed its
execution by reading the other half of the flag, that has been updated
in the meantine, ending up with half of the old value and half of the
new value. It is precisely this problem that sig_atomic_t solves.

Dan
 
K

Kevin D. Quitt

Clearly. Imagine a traditional Cray processor, that can only access
64-bit words, but whose C implementation uses 8-bit bytes. How do you
write something to one *byte* atomically?

Trivially. One either uses a single instruction that is read-modify-write
bus atomic, or three instructions to do the same. No danger of a
partial-read by anybody.

On any machine that can write (at least) a byte in a single
uninterruptable instruction, there is no meaning to, or protection
provided by sig_atomic_t. sig_atomic_t specifically does *not* provide
protection against the following sequence:

Client task reads sig_atomic_t flag and starts to clear that flag.

Signal handler activates and writes new (different) value to sig_atomic_t
flag.

Client task completes clearing the flag.

Information has now been lost. There is no more protection using volatile
sig_atomic_t than there is just using a volatile. In this case,
sig_atomic_t is meaningless.

Compare this to the operation of a truly bus-atomic test-and-set
instruction, coupled with a bus-atomic test-and-clear, where one can be
guaranteed that information can never be lost. Even more powerful are
instructions like those on the 68K family that can link and unlink
doubly-linked list entries atomically.

At the other range of the spectre, imagine a 4-bit processor.

This is another case completely, but only because the size of sig_atomic_t
is defined so as to force the loss of information by requiring a data size
that requires multiple instructions for access. On the C compiler I wrote
for the 6502, all accesses to volatile variables of any size were
protected by blocking int^W asynchronous signals; again, sig_atomic_t
would have made no difference.


It seems to me that this is one of those *very( rare cases where somebody
had an idea and an agenda, and forced it through the committee, without
anybody really thinking about it overly much.

Truly, I'm very surprised it got through - I have a great deal of respect
for the members. People around me consider me to be a C guru; I consider
myself (just barely) an expert and certainly not in the league of those on
the committee (those with whom I am familiar).

So basically, it boils down to: sig_atomic_t is useful because the use on
anything else is forbidden.
 
D

Dan Pop

You seem to be MUCH denser than usual.

In said:
Trivially. One either uses a single instruction that is read-modify-write
bus atomic,

It's not enough, even if such an instruction actually exist. Think about
the complete sequence of operations needed for the job:

read word
AND operation to clear old byte
OR operation to insert new byte
write word
or three instructions to do the same. No danger of a
partial-read by anybody.

Huh?!? What is preventing an interrupt from occurring in the middle of
the sequence?
On any machine that can write (at least) a byte in a single
uninterruptable instruction, there is no meaning to, or protection
provided by sig_atomic_t.

The point is that you don't know what is the type that can be atomically
written to, even if it exists. On the Cray it is int, on an 8-bit micro
it is char. Hence the need for sig_atomic_t.
sig_atomic_t specifically does *not* provide
protection against the following sequence:

Client task reads sig_atomic_t flag and starts to clear that flag.

Signal handler activates and writes new (different) value to sig_atomic_t
flag.

Client task completes clearing the flag.

You're forgetting that clearing the sig_atomic_t flag is an atomic
operation *by definition*, therefore your scenario cannot happen.

2 The type defined is

sig_atomic_t

which is the (possibly volatile-qualified) integer type of an
object that can be accessed as an atomic entity, even in the
presence of asynchronous interrupts. ^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Information has now been lost.

No information has been lost.
There is no more protection using volatile
sig_atomic_t than there is just using a volatile. In this case,
sig_atomic_t is meaningless.
Bullshit!

Compare this to the operation of a truly bus-atomic test-and-set
instruction, coupled with a bus-atomic test-and-clear, where one can be
guaranteed that information can never be lost. Even more powerful are
instructions like those on the 68K family that can link and unlink
doubly-linked list entries atomically.

They are entirely irrelevant in the context of sig_atomic_t and
signal handlers, as specified by the C standard.
It seems to me that this is one of those *very( rare cases where somebody
had an idea and an agenda, and forced it through the committee, without
anybody really thinking about it overly much.

You're the only who failed to really engage his brain on this issue,
despite the detailed explanations you have already gotten.
Truly, I'm very surprised it got through - I have a great deal of respect
for the members. People around me consider me to be a C guru; I consider
myself (just barely) an expert and certainly not in the league of those on
the committee (those with whom I am familiar).

You're behaving like an idiot in this thread.
So basically, it boils down to: sig_atomic_t is useful because the use on
anything else is forbidden.

And there is a *good* reason for forbidding the use of anything else,
as already explained: char doesn't work on the Cray (and the 21064 Alpha),
anything wider than a char doesn't work on the 8-bit micro. No standard
integer type at all works on the less than 8-bit micro.

Dan
 
T

those who know me have no need of my name

in comp.lang.c i read:
On 26 Apr 2004 18:14:03 GMT, (e-mail address removed) (Dan Pop) wrote:

Trivially. One either uses a single instruction that is read-modify-write
bus atomic, or three instructions to do the same. No danger of a
partial-read by anybody.

volatile sig_atomic_t is the clue to the compiler that this effort is
needed. a vast speed improvement is typically available when a `bus
atomic' transaction is avoided.
On any machine that can write (at least) a byte in a single
uninterruptable instruction, there is no meaning to, or protection
provided by sig_atomic_t.

portability. one cannot know that any particular system will have this
property.
Client task reads sig_atomic_t flag and starts to clear that flag.

Signal handler activates and writes new (different) value to sig_atomic_t
flag.

Client task completes clearing the flag.

indeed, which is why it must also be qualified volatile as well. the
combination (causes the compiler to emit code which) prevents this from
occurring.
Information has now been lost. There is no more protection using volatile
sig_atomic_t than there is just using a volatile.

only on specific platforms -- hence non-portable code.
 
M

Mark Piffer

Kevin D. Quitt said:
Trivially. One either uses a single instruction that is read-modify-write
bus atomic, or three instructions to do the same. No danger of a
partial-read by anybody.

But there is no such thing as a read-modify-write access in the
semantics of C. Not in the Standard and I highly doubt in any real
implementation either. Read-modify-write is what happens to come out
of an optimizing stage.

On any machine that can write (at least) a byte in a single
uninterruptable instruction, there is no meaning to, or protection
provided by sig_atomic_t.

Exactly. Thats just restating the standard's definiton of sig_atomic_t
in another way: it is a typedef from an existing type, so the compiler
gains _no_ information whatsoever about integers of that type - it
just can be assumed to always generate atomic writes or reads to them.
You could use the implementation defined underlying type and produce
equivalent code (on one platform), with the same semantics of atomic
access, this time just hidden in the compiler handbooks.

This is another case completely, but only because the size of sig_atomic_t
is defined so as to force the loss of information by requiring a data size
that requires multiple instructions for access. On the C compiler I wrote
for the 6502, all accesses to volatile variables of any size were
protected by blocking int^W asynchronous signals; again, sig_atomic_t
would have made no difference.

IOW you implemented your special flavour of C, which in the case of
volatiles takes another (safer, I admit) approach than standard C.
Unluckily I don't expect to become a user of the 6502 or your compiler
any time soon, so my question was directed towards the standard atomic
types. Keep in mind also, that with this policy you prohibit certain
optimizations which contradicts the C design principles.

Truly, I'm very surprised it got through - I have a great deal of respect
for the members. People around me consider me to be a C guru; I consider
myself (just barely) an expert and certainly not in the league of those on
the committee (those with whom I am familiar).

So basically, it boils down to: sig_atomic_t is useful because the use on
anything else is forbidden.

Yes, also IMHO sig_atomic_t is one of the dirty corners where your
only lucky escape is to leave the paths of portability to do anything
real-world-ish, with the risk of adapting to this bad practice and
being beaten up on c.l.c ;)

Mark
 

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

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,178
Latest member
Crypto Tax Software
Top