This feature enables you to implement a SAS/C program as several
cooperative processes, or coprocesses. Each coprocess represents
a single thread of sequential execution. Only one thread is allowed to
execute at any particular time. Control is transferred from one
coprocess to another using the cocall
and coreturn
functions;
these functions also provide for the transfer of data between
coprocesses. At the start of execution, a main coprocess is created
automatically by the library. Additional coprocesses are created
during execution using the costart
function and are terminated via
the coexit
function so that the coprocess structure of a program
is completely dynamic. Most of the data structures of a SAS/C program are
shared among all coprocesses, including extern
variables, storage
allocated by malloc
, and open files. (The structures that are not
shared are listed later in this section.)
Note that coprocessing does not implement multitasking. That is, only a single coprocess can execute at a time. Furthermore, transfer of control is completely under program control. If the executing coprocess is suspended by the operating system to wait for an event, such as a signal or I/O completion, no other coprocess is allowed to execute. Finally, note that the implementation of coprocessing does not use any multitasking features of the host operating system.
Coprocesses are very similar to coroutines, as included in some other
languages. The name coprocess was used rather than coroutine because
many SAS/C functions can be called during the execution of a single
coprocess.
cocall and coreturn
The cocall
and coreturn
functions are used to transfer
control and information from one coprocess to another. Normally, the
cocall
function is used to request a service of a coprocess, and
the coreturn
function is used to return control (and information
about the requested service) to a requestor. The transfer of control
from one coprocess to another as the result of a call to cocall
or
coreturn
is called a coprocess switch.
Use of cocall
is very similar to the use of a normal SAS/C function
call. They both pass control to another body of code, they both allow
data to be passed to the called code, and they both allow the called
code to return information. Whether you are using a function call or a
cocall
, after completion of the call the next SAS/C statement is
executed.
In contrast, the SAS/C return
statement and the coreturn
function
behave differently, even though both allow information to be returned
to another body of code. Statements following a return
statement
are not executed because execution of the returning function is
terminated. However, when coreturn
is called, execution of the
current coprocess is suspended rather than terminated. When another
coprocess issues a cocall
to the suspended coprocess, the
suspended coprocess is resumed, and the statement following the call to
coreturn
begins execution.
The following example illustrates the way in which cocall
and coreturn
work. The two columns in the example show the statements to
be executed by two coprocesses, labeled A and B. For simplicity, in
this example no data are transferred between the two coprocesses.
Coprocess A Coprocess B A_func() B_func() { { . . . /* other statements */ . /* other statements */ . . begin: coreturn(NULL); puts("A1"); puts("B1"); cocall(B, NULL); bsub(); puts("A2"); puts("B4"); cocall(B, NULL); coreturn(NULL); puts("A3"); . . . /* other statements */ . /* other statements */ . . void bsub() } { puts("B2"); coreturn(NULL); puts("B3"); return; }If coprocess A's execution has reached the label
begin
, and
coprocess B is suspended at the first coreturn
call, then the
following sequence of lines is written to stdout
:
A1 B1 B2 A2 B3 B4 A3
costat
enables you to determine the state
of a coprocess.)
You may find it helpful to trace through one of the examples in this section for concrete illustration of the state transitions described here.
A coprocess is created by a call to the costart
function. After
its creation by costart
, a coprocess is in the starting
state until its execution begins as the result of a cocall
.
(Because the main coprocess is active when program execution begins, it
is never in the starting state.) Each coprocess has an initial
function, which is specified as an argument to costart
when the coprocess
is created. This is the function that is given control when the coprocess
begins execution.
A coprocess is active when its statements are being executed. When a coprocess is cocalled, it becomes active if the cocall was legal. An active process cannot be cocalled; an attempt by a coprocess to cocall itself returns an error indication. At the start of execution, the main coprocess is in the active state.
A coprocess is busy when it has issued a cocall
to some other
coprocess and that coprocess has not yet performed a coreturn
or
terminated. A busy coprocess cannot be cocalled, and an attempt to do
so returns an error indication. This means that a coprocess cannot
cocall itself, directly or indirectly. One implication of this rule is
that it is not possible to cocall the main coprocess.
A coprocess becomes idle (or suspended ) when it has called
coreturn
, if the call was legal. An idle coprocess remains idle
until another coprocess cocalls it, at which point it becomes active.
The main coprocess is not permitted to call coreturn
and,
therefore, cannot become idle.
A coprocess is ended after its execution has terminated.
Execution of a coprocess is terminated if it calls the coexit
function, if its initial function executes a return
statement, or
if any coprocess calls the exit
function. In the last case, all
coprocesses are ended. In the other two cases, the coprocess that
cocalled the terminating coprocess is resumed, as if the terminated
coprocess had issued a coreturn
. You cannot cocall a coprocess
after it has ended.
The effect of the various routines that cause coprocess switching can be summarized as follows:
cocall
coreturn
coexit
Passing Data between Coprocesses
The cocall
, coreturn
, and coexit
functions all provide
an argument that can be used to pass data from one coprocess to
another. Because the type of data that coprocesses may want to
exchange cannot be predicted in advance, values are communicated by
address. The arguments and returned values from these functions are
all defined as the generic pointer type void *
.
Except when a coprocess is beginning or ending execution, a call to
either cocall
or coreturn
by one coprocess causes the calling
coprocess to be suspended and a call to the other function in another
coprocess to be resumed. In either case, the argument to the first
function becomes the return value from the function that is resumed.
To illustrate, here is a version of the previous example that demonstrates how data can be passed from coprocess to coprocess:
Coprocess A Coprocess B A_init() char * B_init(arg) { char *arg; char *ret; { B = costart(&B_init,NULL); puts(arg); ret = cocall(B, "B0"); arg = coreturn("A1"); puts(ret); puts(arg); ret = cocall(B, "B1"); puts(bsub()); puts(ret); return "A3"; /* or coexit("A3"); */ ret = cocall(B, "B2"); } puts(ret); } char *bsub() { char *arg; arg = coreturn("A2"); return arg; }When the function
A_init
is called, the following sequence of
lines is written to stdout
:
B0 A1 B1 A2 B2 A3Each line is written by the coprocess indicated by its first letter.
cocall
causes execution of a coprocess to
begin or where a return from the initial function of a coprocess causes
it to terminate, the simple rule that the argument to one function
becomes the return value from another does not apply. In the first
case, the argument to cocall
becomes the argument to the initial
function of the newly started coprocess. In the second case, the
return value from the initial function becomes the value returned by
the cocall
in the resumed coprocess. Both of these situations are
illustrated in the preceding example.
Coprocess Data Types and Constants
The header file <coproc.h>
must be included (via a #include
statement) in any compilation that uses the coprocessing feature. In
addition to declaring the coprocessing functions, the header file also
defines certain data types and constants that are needed when
coprocesses are used. These definitions from <coproc.h>
are as
follows:
typedef unsigned coproc_t; /* cocall/coreturn error code */ #define CO_ERR (char *) -2 /* coproc argument values */ #define MAIN 0 #define SELF 1 #define CALLER 2 /* costat return values -- coprocess states */ #define ENDED 0 #define ACTIVE 1 #define BUSY 2 #define IDLE 4 #define STARTING 8The data type
coproc_t
defines the type of a
coprocess identifier.
Coprocess IDs are described in more detail in Coprocess Identifiers .
The value CO_ERR
is returned by cocall
and coreturn
when
the requested function cannot be performed. You should avoid passing
this value as an argument to cocall
or coreturn
, because it can
be misinterpreted by the paired function as indicating a program error
rather than a correct return value. Note that NULL
can be used as
a cocall
or coreturn
argument. This is the recommended way
to signify no information.
<coproc.h>
also defines the type struct costart_parms
, which
is a structure containing start-up information for a coprocess, such as
an estimate of the required stack size. The address of a structure of
this type is passed to costart
when a new coprocess is created.
See costart
for more information on the uses for this structure.
Coprocess Identifiers
Coprocess identifiers are values of type coproc_t
. They are
returned by the costart
function when a coprocess is created and
passed to cocall
to indicate the coprocess to be resumed.
0
is
not a valid coprocess ID; this value is returned by costart
when a
new coprocess cannot be created.
Each coprocess has a unique ID value; the IDs of ended coprocesses are not reused.
The IDs of specific coprocesses can be found by calling the coproc
function. See coproc for details.
Restrictions
You should be aware of the following restrictions and special
considerations when coprocesses are used. In addition, certain
advanced topics that are important to a few complex applications are
described here, after the example. You may want to skip these sections
if they are not relevant to your application.
costart
. However, each coprocess' stack space is expanded dynamically as
necessary.
=minimal
run-time option or its compile-time equivalent) in an application
that uses coprocesses.
quiet
function in one coprocess has no effect on diagnostic messages
generated by another.
exit
function, the entire program is
terminated. The details of program termination are described in
Advanced Topics: Program Termination .
A call to coexit
in the main coprocess is treated
as a call to exit
.
longjmp
cannot be used to transfer control between coprocesses.
In particular, you cannot call setjmp
in one coprocess and call
longjmp
using the same jmp_buf
in another. Any attempt to do
this causes abnormal program termination.
malloc
is shared by all coprocesses. Memory
allocated by one coprocess can be freed by another. Similarly, load
modules loaded by one process can be used or unloaded by another, and
files opened by one coprocess can be used or closed by another.
A convenient data structure for many applications is the sorted binary
tree, which is a set of items connected by pointers so that it is easy
to walk the tree and return the items in sorted order. A node of such
a tree can be declared as struct node
, defined by the following:
struct node { struct node *before; struct node *after; char *value; };
The following is an example of a sorted binary tree, with each node
represented as a point and the before and after pointers represented as
downward lines (omitting NULL
pointers):
It is easy to write a C function to print the nodes of such a tree in sorted order using recursion, as follows:
prttree(struct node *head) { /* Print left subtree. */ if (head->before) prttree(head->before); /* Print node value. */ puts(head->value); /* Print right subtree. */ if (head->after) prttree(head->after); }Now, suppose it is necessary to compare two sorted binary trees to see if they contain the same values in the same order (but not necessarily the exact same arrangement of branches). Without coprocesses, this cannot easily be solved recursively because the recursion necessary to walk one tree interferes with walking the other tree. However, use of coprocesses allows an elegant solution, which starts a coprocess to walk each tree and then compares the results the coprocesses return. (Some error checking is omitted from the example for simplicity.) The example assumes that all values in the compared trees are strings and that
NULL
does not appear as a value.
This example was adapted from S. Krogdahl and K. A. Olsen. "Ada, As Seen From Simula." Software - Practice and Experience 16, no. 8 (August 1986).
#include <stddef.h> #include <coproc.h> #include <string.h> int treecmp(struct node *tree1, struct node *tree2) { coproc_t walk1, walk2; char equal = 1, continu = 1; char *val1, *val2;
walk1 = costart(&treewalk,NULL); /* Start a coprocess for */ walk2 = costart(&treewalk,NULL); /* each tree. */ cocall(walk1, (char *) tree1); /* Tell them what to walk. */ cocall(walk2, (char *) tree2); while(continu) { /* Get a node from each tree. */ val1 = cocall(walk1, &continu); val2 = cocall(walk2, &continu); /* See if either tree has run out of data. */ continu = val1 && val2; /* Compare the nodes. */ equal = (val1? strcmp(val1, val2) == 0: val2 == NULL); /* Leave loop on inequality. */ if (!equal) continu = 0; } /* Terminate unfinished coprocesses. */ if (val1) cocall(walk1, &continu); if (val2) cocall(walk2, &continu); /* Return the result. */ return equal; } char *treewalk(char *head) { struct node *tree; tree = (struct node *) head; /* Get tree to walk. */ coreturn(NULL); /* Say that we're ready. */ walk(tree); /* Start walking. */ return NULL; /* Return 0 when done. */ } void walk(struct node *tree) { if (tree->before) walk(tree->before); /* Walk left subtree. */ if (!*coreturn(tree->value)) /* Return value, check for */ coexit(NULL); /* termination. */ if (tree->after) walk(tree->after); /* Walk right subtree. */ }
Advanced Topics: Program Termination
When a program that uses coprocesses terminates, either by a call to
exit
or by a call to return
from the main function, all
coprocesses must be terminated. The process by which termination
occurs is a complicated one whose details may be relevant to some
applications.
By using the blkjmp
function, you can intercept coprocess
termination when it occurs as the result of coexit
or exit
,
allowing the coprocess to perform any necessary cleanup. Both
coexit
and exit
are implemented by the library as a longjmp
to a jump buffer defined by the library; if you intercept an exit
or coexit
, you can allow coprocess termination to continue by
issuing longjmp
using the data stored in the jump buffer by
blkjmp
. (See the blkjmp
function description in Chapter 8,
"Program Control Functions," in SAS/C Library Reference, Volume 1 for details of this process.)
The atcoexit
function also can be used to establish a cleanup
routine for a coprocess. Note that this function allows one coprocess
to define a cleanup routine for another or even for every terminating
coprocess.
Exit Processing for Secondary Coprocesses
When exit
is called by a coprocess other than the main one, the
library effects a coexit
for each non-idle coprocess. More
specifically, the following steps are performed:
longjmp
is performed using a library jump buffer to terminate
the calling coprocess, allowing the termination to be intercepted by
blkjmp
. This means that the exit
call is treated at first
as a coexit
.
atcoexit
routines for the coprocess are called.
cocall
function, the exit
function is called
again. If this coprocess is not the main coprocess, steps 1 and 2 are
performed for that coprocess. If this coprocess is the main coprocess,
the normal function of exit is performed and the entire program is
terminated, as described in Exit Processing for the Main Coprocess .
exit
is
called by the main coprocess, all remaining coprocesses are idle.
exit
, the following steps are
performed:
longjmp
is performed using a library jump buffer to terminate
the program, allowing termination to be intercepted by blkjmp
.
coexit
routines and any atcoexit
routines for the main
coprocess are called.
coreturn
function. This coprocess becomes active, but the call to
coreturn
is not completed. Instead, a coexit
is performed. (If, on the
other hand, all coprocesses are ended, the remainder of program
termination processing is performed.)
blkjmp
, it will intercept
the coexit
and can perform cleanup processing. Note that since
there is no calling process to return to, an attempt to call
coreturn
is treated as an error. It is permissible to call
cocall
to communicate with other unterminated coprocesses or
even to use costart
to create new ones at this time.
coexit
completes, the current coprocess is terminated, and
control is transferred to step 3.
Signal handling for programs that use coprocesses is complex because
program requirements differ depending on the situation and the signals
involved. For instance, for computational signals such as SIGFPE
,
you may prefer a separate signal handler for each coprocess. On the
other hand, for a signal such as SIGINT
that can occur at any
time, independent of coprocess switches, you may prefer a single
handler, which is called regardless of the coprocess active at the time
of the signal. The coprocess implementation enables you to define
local and global handlers for each signal independently in whatever way
is most convenient for your application.
In addition, the library maintains a separate signal-blocking mask for
each coprocess. This allows coprocesses to block signals that they are
not equipped to handle, while assuring that signals are recognized
whenever an appropriate coprocess becomes active. By default, all
coprocesses except the main coprocess are created with all signals
blocked. For this reason, unless a new coprocess issues
sigsetmask
or sigprocmask
,
no asynchronous signals are detected during its execution.
Therefore, you can write subroutines that create coprocesses, and you
do not need any special code to avoid interference with the
asynchronous signal handling of the rest of the program. Note that you
can use the second argument to costart
to specify a different
signal mask for the new coprocess.
When you use coprocesses, there are two functions available to define
signal handlers: signal
and cosignal
. signal
defines a
local handler, a function that is called only for signals
discovered while the coprocess that defined the handler is active.
cosignal
defines a global handler, a function that is called
for signals discovered during the execution of any coprocess. If, at
the time of signal discovery, both a local and global handler are
defined, the local handler is called unless the local handler is
SIG_DFL
, in which case the global handler is called.
Usually, you want to define local handlers (using the signal
function) for synchronous signals and global handlers (using the
cosignal
function) for asynchronous signals. For instance, a
SIGFPE
handler normally is defined with signal
because
SIGFPE
cannot occur while a coprocess is active except as the result of code
executed by that coprocess. On the other hand, a SIGINT
handler
normally is defined with cosignal
because the time at which a
SIGINT
signal is generated has no relationship to coprocess
activity.
The local and global signal handlers are maintained independently. A
call to signal
returns the previous local handler for the calling
coprocess; a call to cosignal
returns the previous global
handler.
You can also use sigaction
to establish local or global
handlers. Ordinarily, sigaction
defines a local handler.
You can
set the sa_flags
bit SA_GLOBAL
to define a global
handler instead.
Note that when a signal handler is called, no coprocess switch takes
place; the handler executes as part of the interrupted coprocess. If
you want a signal to be handled by a particular coprocess, you either
can have all other coprocesses block the signal or you can define a
global handler that cocalls the relevant coprocess, if that coprocess
was not the one interrupted. An example of the latter technique is
shown in the cosignal
function description.
Note that global handlers generally should not use longjmp
unless
they first verify that the active coprocess is the one that issued the
corresponding call to setjmp
.
Function Descriptions
Descriptions of each coprocessing function follow. Each description
includes a synopsis, description, discussions of return values and
portability issues, and an example. Also, errors, cautions,
diagnostics, implementation details, and usage notes are included if
appropriate.
#include <coproc.h> int atcoexit(void (*func)(void), coproc_t procid);
atcoexit
function defines a function called during termination
of a particular coprocess or of all coprocesses either as the result
of a call to coexit
or a return from the coprocess' initial
function. The func
argument should be a function with no
arguments returning void
. The procid
argument specifies the
ID of the coprocess whose termination is to be intercepted or 0
to
intercept termination of all coprocesses.
atcoexit
routines free resources associated with particular
coprocesses. These can be library-managed resources, such as load
modules or FILEs, because these normally are cleaned up only when the
entire program terminates. atcoexit
can be called any number of
times, and the same routine can be registered more than once, in which
case it is called once for each registration.
atcoexit
cleanup routines are called in the opposite order of
their registration, and they execute as part of the terminating
coprocess. They are called after termination of the coprocess' active
functions. (Thus, a cleanup routine cannot cause coprocess execution
to resume by issuing longjmp
.) A cleanup routine can call
coexit
, which has no effect other than possibly changing the
information returned to the cocalling coprocess. In this case, no
cleanup routine previously called is called again during termination of
the same coprocess.
It is not possible to deregister a function once registered. However,
when a load module containing a registered cleanup routine is unloaded
using unloadm
, the cleanup routine is deregistered
automatically.
You can call atcoexit
during the termination of a coprocess, but
the corresponding function is not called during termination of this
coprocess. (Normally, it is called during the termination of other
coprocesses to which it applies.)
Note that atexit(func)
is equivalent to
atcoexit(func, coproc(MAIN))
.
atcoexit
returns 0
if successful or a nonzero value if
unsuccessful.
atcoexit
is not portable.
coalloc
that can be called to
allocate memory belonging to a particular coprocess. An atcoexit
cleanup routine is defined to free the memory allocated by each
coprocess when it terminates.
#include <stdlib.h> #include <coproc.h> struct memelt { /* header for each block allocated */ struct memelt *fwd; coproc_t owner; double pad [0]; /* force correct alignment */ }; static int first = 1; static struct memelt *queue; static void cleanup(void); void *coalloc(int amt) { struct memelt *newmem; if (first){ if (atcoexit(cleanup, 0)) abort(); else first = 0; } newmem = (struct memelt *) malloc(amt + sizeof(struct memelt)); if (!newmem) return 0; newmem->owner = coproc(SELF); newmem->fwd = queue; queue = newmem; return ((char *) newmem) + sizeof(struct memelt); } void cleanup() { coproc_t ending = coproc(SELF); struct memelt **prev, *cur; for (prev = &queue; cur = *prev;) { if (cur->owner == ending) { *prev = cur->fwd; free(cur); } else prev = &cur->fwd; } }
blkjmp
, atexit
#include <coproc.h> void *cocall(coproc_t procid, void *arg);
cocall
function gives control to a coprocess and passes it a
pointer value. Execution of the calling coprocess is suspended until
the called coprocess calls coreturn
or is terminated. The procid
argument identifies the coprocess to be called. The arg
value is a value passed to the called coprocess. arg
must not
have the value CO_ERR
.
The arg
value is passed to the called coprocess as follows:
(*f)(arg)
that begins executing the coprocess
created by the costart
call:
id = costart(f,NULL); cocall(id,arg);
coreturn
function. In
this case, the suspended call to coreturn
is resumed, and
coreturn
returns the value specified by arg
.
cocall
returns a pointer specified by the called coprocess.
Depending on the circumstances, any of the following can occur. If the
called coprocess suspends itself by calling coreturn(val)
, the
value val
is returned by cocall
. If the called coprocess
terminates by calling coexit(val)
, the value val
is returned
by cocall
. If the called coprocess terminates as a result of
execution of return val;
by its initial function, the value
val
is returned by cocall
.
If the procid
argument is invalid (that is, if it identifies an invalid or
terminated coprocess), the calling coprocess is not suspended, and cocall
immediately returns the constant CO_ERR
.
cocall
s are not allowed. While a coprocess is
suspended as the result of a call to cocall
, it cannot be
cocalled again. In particular, the main coprocess can never be
cocalled. Any attempt to perform a recursive cocall returns
CO_ERR
.
#include <stddef.h> #include <coproc.h> #include <stdlib.h> coproc_t inp_proc; /* input process process ID */ void *input(void *); char *file_name = "ddn:input"; char *input_ln; /* a line of input, returned by inp_proc */ /* Create and call a coprocess that reads a line of data from a */ /* file. The "input" function, which is the initial function of */ /* the new coprocess, is given in the EXAMPLE for coreturn. */ inp_proc = costart(&input,NULL); /* Create the coprocess. */ /* Pass the input file name and check for error. */ if (!cocall(inp_proc, file_name)) exit(16); for (;;) { input_ln = cocall(inp_proc, NULL); /* Ask for an input line; stop if no data left. */ if (!input_ln) break; . . /* Process input data. */ . }
#include <coproc.h> void coexit(void *val);
coexit
terminates execution of the current coprocess and returns a
pointer value to its cocaller. The call to cocall
that called the
terminating coprocess is resumed, and it returns the value specified by
val
.
If coexit
is called by the main coprocess, it is treated as a call
to exit. Specifically, coexit(val)
is treated as
exit(val? *(int *) val: 0)
.
coexit
is implemented as a longjmp
to a location within the
library coprocess initialization routine. This means that the
blkjmp
function can be used within a coprocess to intercept calls to
coexit
.
If program execution is terminated by a call to the exit
function
while there are still coprocesses active, each active coprocess is
terminated by an implicit call to coexit
. These calls also can
be intercepted by blkjmp
; however, note that in this context any
use of coreturn
fails, because no calling coprocess is defined.
coexit
.
coreturn
.
blkjmp
, exit
#include <coproc.h> coproc_t coproc(int name);
coproc
returns the coprocess identifier of a particular coprocess
as specified by its integer argument. The argument should be specified
as one of these three symbolic values: MAIN
, SELF
, or
CALLER
.
coproc(MAIN)
returns the coprocess ID of the main coprocess, which
is always created during program initialization. coproc(SELF)
returns the ID of the coprocess currently running. coproc(CALLER)
returns the ID of the coprocess that cocalled the current coprocess.
coproc
returns the ID of the specified coprocess or 0
if the
argument does not specify a valid coprocess. coproc(CALLER)
returns 0
if called from the main coprocess. Also note that
coproc(MAIN)
returns 0
if called after a call
to exit
, causing program termination.
#include <coproc.h> #include <stdio.h> /* Test whether the main coprocess or some */ /* other coprocess is running. */ if (coproc(SELF) == coproc(MAIN)) printf("main coprocess currently active.\n");
#include <coproc.h> void *coreturn(void *arg);
coreturn
returns control to the current cocaller of a coprocess
and returns a pointer value. Execution of the returning coprocess is
suspended until it is cocalled again. The arg
value is a value to
be returned to the cocaller of the current coprocess. arg
must
not have the value CO_ERR
.
The arg
value is passed to the resumed coprocess as follows. By
necessity, the caller of the current process is suspended by executing the
cocall
function. This call is resumed and returns the value
specified by arg
to its caller.
coreturn
returns only when the current coprocess is cocalled again
by some other coprocess. The return value is the arg
value
specified by the corresponding call to cocall
.
An invalid call to coreturn
immediately returns the value
CO_ERR
.
coreturn
cannot be called from the main coprocess because it has
no cocaller to return to.
cocall
example. It
illustrates a coprocess whose purpose is to read a file and
coreturn a line at a time.
#include <stddef.h> #include <coproc.h> #include <stdio.h> void *input(void *filename) { FILE *f; char buf [100]; f = fopen(filename, "r"); /* if open failed, quit, pass NULL to calling coprocess */ if (!f) coexit(NULL); /* else acknowledge valid name */ coreturn(filename); for (;;) { fgets(buf, 100, f); /* Read the next line. */ if (feof(f)) { /* if reached end-of-file */ fclose(f); coexit(NULL); /* Terminate coprocess and return NULL. */ } /* else pass back input line address */ else coreturn(buf); } }
#include <coproc.h> #include <signal.h> /* This typedef is in <coproc.h>. */ typedef __remote void(*_COHANDLER)(int); _COHANDLER cosignal(int signum, _COHANDLER handler);
cosignal
function defines a global handler for a signal. A
global handler can be called during the execution of any coprocess
except one that has used the signal
or sigaction
function to define a local
handler for the same signal. The signum
argument is the number
of
the signal, which should be specified as a symbolic signal name.
The handler
argument specifies the function to be called when the
signal occurs. The handler
argument can be specified as one of
the symbolic values SIG_IGN
or SIG_DFL
to specify that the
signal should be ignored or that the default action should be taken,
respectively.
The handler always executes as part of the coprocess interrupted by the signal.
cosignal
returns the address of the handler for the signal
established by the previous call to cosignal
or SIG_DFL
if
cosignal
has not been previously called. cosignal
returns
the special value SIG_ERR
if the request cannot be honored.
cosignal
has no effect on the handling of a signal that is
discovered during execution of a coprocess that has defined a local
handler (other than SIG_DFL
) using the signal
function.
However, when SIG_DFL
is defined as the local handler, any global
handler will be called.
Handlers established with cosignal
should not call longjmp
without verifying that the coprocess executing is the one that called
setjmp
to define the target. Attempting to use longjmp
to
transfer control in one coprocess to a target established by another
causes the program to terminate abnormally.
cosignal
is more appropriate than signal
for handling
asynchronous signals in a multiple coprocess program because
asynchronous signals can occur at any time, regardless of transfers of
control between processes. Alternately, because each coprocess has its
own mask of blocked signals, you can use sigblock
and
sigsetmask
to prevent signals from being discovered except during the
execution of a process set up to handle them.
cosignal
to
define a global handler for the SIGALRM
signal. This handler then
uses cocall
to give control to the coprocess.
#include <stddef.h> #include <coproc.h> #include <signal.h> #include <lclib.h> coproc_t tmr_proc; /* timer coprocess ID */ char *tmr_wait(char *); int delay = 10; /* delay time in seconds */ main() { /* Create the timer coprocess. */ tmr_proc = costart(&tmr_wait,NULL); /* Allow timer coprocess to initialize. */ cocall(tmr_proc, NULL); /* Set time until first signal. */ alarm(delay); . . . } void tmr_hndl(int signum) { /* This is the global SIGALRM handler, which uses cocall to */ /* activate the timer coprocess. After the timer coprocess has */ /* handled the signal, alarm is called to establish the next */ /* interval. */ cocall(tmr_proc, NULL); alarm(delay); /* Start new timer interval. */ } char *tmr_wait(char *unused) { /* This function is a coprocess that establishes a global */ /* signal handler with cosignal to gain control every time */ /* SIGALRM is raised. The handler is responsible for calling */ /* alarm to re-establish the interval after handling is */ /* complete. Note that this technique assures that SIGALRM will */ /* not be raised while this coprocess is active. */ for (;;) { /* Do until program exit. */ cosignal(SIGALRM, &tmr_hndl); /* Define global handler. */ coreturn(NULL); /* Let rest of program run. */ . . /* Alarm has gone off -- do periodic cleanup. */ . } }
sigaction
, signal
costart -- Create a New Coprocess
#include <coproc.h> coproc_t costart(void *(*func)(void *), struct costart_parms *p);
costart
function creates a new coprocess. Its argument is
the
address of the initial function to be executed. The initial function
should accept a void *
argument and return a void *
value.
This function is not called until the first time the new coprocess is
cocalled.
The p
argument can be NULL
or a pointer to a
struct costart_parms
, which has the following
definition (in <coproc.h>
):
struct costart_parms { unsigned stksize; unsigned sigmask; sigset_t *blocked_set; unsigned _[3]; };If
p
is a non-NULL
pointer, then the values
of each of the members at the struct costart_parms
it points to is used when the coprocess is
created. If p
is NULL
, then default values are used.
The value in stksize
is used as the size of the initial stack
allocation for the coprocess. This value must be at least 680 bytes.
All values are rounded up to an even fraction of 4096. The default
initial stack allocation is 4096. Use of stksize
does not prevent
the stack from being extended automatically if a stack overflow
occurs.
The sigmask
and blocked_set
arguments are used to
specify the signal mask for the coprocess.
The sigmask
field can be used to mask signals that are managed
by SAS/C, and the blocked_set
argument can be used to mask signals
that are managed by either SAS/C or OpenEdition.
sigmask
is provided primarily for backwards
compatibility, and it is recommended that you use the sigprocmask
function to establish a sigset_t
object that contains the
mask information for both SAS/C and OpenEdition managed signals.
The blocked_set
argument is then used to point to the
sigset_t
object.
If blocked_set
is set to 0
, the value of sigmask
is used to establish the signal mask for the coprocess.
The default value of sigmask
is 0xffffffff
; that is,
all signals
are blocked.
This blocks interruptions until the new coprocess is
ready. Refer to "Blocking Signals" in Chapter 5 of SAS/C Library Reference, Volume 1 for more
information about the signal mask.
The _[3]
field is reserved and must be set to
0
s.
costart
returns the ID of the new coprocess or 0
if a new
coprocess could not be created.
costart_parms
structure must be used.
If you want to change only one of the fields, you must set the other
fields to their default value.
At most, 65,536 coprocesses (including the main coprocess created at program start-up) can exist simultaneously. In practice, because each coprocess needs stack space beow the 16-megabyte line, it will not be possible to create more than roughly 10,000 coprocesses. The exact limit depends on the operating system and region or virtual machine size.
sigaddset
#include <coproc.h> int costat(coproc_t id);
costat
function returns information about the status of a
coprocess. The id
argument is the ID of the coprocess whose
status is desired.
costat
is one of the symbolic constants STARTING
, ACTIVE
, BUSY
, IDLE
, or ENDED
.
These constants have the following significance:
STARTING
costart
but has never been cocalled.
ACTIVE
BUSY
cocall
function. A BUSY
coprocess cannot be cocalled itself.
IDLE
coreturn
.
The coprocess will not execute again until it is cocalled.
ENDED
costat
are
unpredictable. Usually, this causes the value ENDED
to be
returned.
#include <stddef.h> #include <coproc.h> #include <stdlib.h> coproc_t err_proc; /* Cocall the coprocess whose ID is in the variable err_proc. But */ /* abort instead if that coprocess cannot be legally cocalled. */ switch (costat(err_proc)) { case STARTED: case IDLE: cocall(err_proc,NULL); /* Call the error coprocess if */ break; /* this is legal. */ default: abort(); /* Abort if error coprocess not */ } /* available for cocall. */