/* signal.h
L. Stewart June 2, 1982 9:25 AM
From D. Swinehart's bcpl signaller package
*/
#define UNWIND 1
#define ANY 0
#define ERROR 2
#define minClientError 10
#define maxClientError 037777
/*
Clients may assign SIGNAL values via compiled-in constants
or any other scheme, if they fall within [minClientError..maxClientError].
If duplicate values result, caveat clientus. code() returns a new SIGNAL
value outside this interval. To be sure each signal is distinct, make
it a static and initialize it with sig = code().
*/
#define REJECT 1
#define RESUME 2
#define RETRY 3
#define CONTINUE 4
/*
CatchProc: TYPE = PROC[sig: SIGNAL, arg: UNSPECIFIED, seal: SEAL]
RETURNS [control: ControlValue ]
Catch phrases in this implementation are provided by actual
procedures. sig is the signal value that was raised, and arg is
another value supplied in the SIGNAL call. seal is described later.
If the catch procedure returns RESUME, execution will continue after the
SIGNAL call (there is no enforced analog of ERROR.) Returning REJECT
causes the next catch phrase to be sought and invoked. Returning RETRY or
CONTINUE causes the stack to be unwound, then a true (CONTINUE) or false
(RETRY) return from the ENABLE for this seal (see below.)
When the stack is unwound, catch procedures for UNWIND are
called, as in Mesa.
The control codes returned by UNWIND catch procedures are ignored.
ANY will catch any signal, including an UNWIND.
A catch phrase for ANY will not catch its own unwind -- same as Mesa.
I don't know what happens when signals are raised within catch
procedures, etc.
*/
struct Seal {
int slauth;
int slid;
};
struct Seal1 {
int slauth;
int slid;
int data[1];
};
#define lenSeal1 (sizeof(struct Seal1)/2)
struct Seal2 {
int slauth;
int slid;
int data[2];
};
#define lenSeal2 (sizeof(struct Seal2)/2)
#define lenseal 4
/*
The client must supply a pointer to a two-word (or larger) block
as a seal. The signaller stores values in the seal that allows it
to verify the seal's validity and identify the specific catch phrase
instance later, when the signal is raised. This is done so that
the client does not have to explicitly disable each ENABLE when
leaving the scope of the ENABLE. Roughly speaking, the ENABLE
is in effect until:
The client calls DISABLE, specifying the same seal.
The client calles ENABLE again, specifying the same seal.
The client procedure containing the ENABLE returns.
The seal is supplied to the catch procedure so that, if the client
knows something about the seal that the signaller doesn't know
(like what's in words 2..n, or where the seal is located within
the local frame), the catch proc can gain some sort of access to
the ENABLE's context. Oh, for true nested scopes! Catch
phrase procs are called, until accepted, in reverse temporal order
of ENABLE within still-valid frames. There can be several ENABLEs
in effect for each local frame -- the frame orders must not be
rearranged while the ENABLES are valid. When the client calls
ENABLE, it returns false. Later, when a catch procedure corresponding
to that ENABLE's seal returns CONTINUE, it appears to the client
as if the same ENABLE has just returned true. If the catch
procedure specifies RETRY, the client sees another false return
from the ENABLE. So a typical use might be:
struct seal myseal;
int catcher(sig, code, seal)
int sig, code;
struct seal *sl;
{
if (code==unbound) return (CONTINUE);
else return (REJECT);
};
main() {
if (enable(callfailed, catcher, &myseal)) return;
xxx;
};
Two situations to avoid:
1) Scope lasts too long. Suppose that procedure P enabled signal
S using seal L, then called Q, which returned normally. Suppose
that subsequently P is unprepared to handle S using that catch
procedure. P must explicitly DISABLE L.
2) Invalid scope appears valid: Suppose that procedure P enabled
signal S using seal L, did some work, then returned. Suppose
further that procedure P is then called again, and its local
frame ends up in the same place (likely, if P is called again
by its previous caller.) Finally, suppose that P or one of its
called procedures raises S before P reaches the enable of S
sealed by L. The seal will still appear valid, and the catch
procedure will be called, possibly at a time that P cannot deal
with. So if you enable a signal after some point in your procedure
that could cause it to be raised, DISABLE the seal on entry to the
procedure. (Setting either word of the seal to zero will also
disable it -- oh, for inlines!)
*/
/* procedures
sigginit()
Initialize the signaller package
siginit(svec, sveclen, gvproc)
svec is a block of storage of length sveclen bytes, it
will be used for saving enable information.
There should be a separate call on siginit from each
process which uses signals. gvproc should be tghe same in
each case. It is a procedure that the signaller will use
to aquire the apropriate svec for the running process.
int enable(s, p, sl)
sl is a block of length lenseal for the signallers use.
p is the procedure to be called when the signal is raised
s is the signal value, aquired from code() perhaps.
enable returns false on the initial call and on RETRY
enable returns true on CONTINUE
disable(sl)
disable the signal corresponding to seal sl
signal(s, c)
raise signal s with argument c
int code()
acquire a unique signal value
*/