Coprocessing Functions

Introduction

Some large applications are most conveniently implemented as a set of communicating processes that run independently of each other except for occasional exchanges of messages or other information. The SAS/C coprocessing feature supports this sort of application in a natural, operating-system-independent way.

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
 

Coprocess States

During its execution, a coprocess may pass through up to five states: The effect of cocalling a coprocess depends on its state at the time of the call. (The function 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
can be used to resume a starting or idle coprocess. The active coprocess becomes busy and the cocalled coprocess becomes active.
coreturn
can be used to suspend the active coprocess and resume the one that cocalled it. The active coprocess becomes idle, and the one that cocalled it changes from busy to active.
coexit
can be used to terminate the active coprocess and resume the one that cocalled it. The active coprocess becomes ended, and the one that cocalled it changes from busy to active.

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
    A3
 
Each line is written by the coprocess indicated by its first letter.

Special Cases

In the case where a 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 8
 
The 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.

A Coprocessing Example

The following example shows a programming problem that is difficult to solve without coprocesses but is easy when coprocesses are used.

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:
  1. A 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.
  2. Any atcoexit routines for the coprocess are called.
  3. The active coprocess is terminated by the library. The coprocess that cocalled the terminated process is then made active. Instead of resuming the 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 .
Note that as a result of the above approach, by the time exit is called by the main coprocess, all remaining coprocesses are idle.

Exit Processing for the Main Coprocess

When the main coprocess calls exit, the following steps are performed:
  1. A longjmp is performed using a library jump buffer to terminate the program, allowing termination to be intercepted by blkjmp.
  2. Any coexit routines and any atcoexit routines for the main coprocess are called.
  3. The main coprocess becomes ended.
  4. If there are any idle coprocesses left, one of them is selected for termination. This coprocess is suspended in a call to the 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.)
  5. As usual, if the coprocess has issued 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.
  6. When coexit completes, the current coprocess is terminated, and control is transferred to step 3.
All coprocesses are terminated before files are closed by the library so that coprocess termination routines can flush buffers or write final messages.

Advanced Topics: Signal Handling

To understand this section, you should be familiar with the normal operation of signal handling. You may want to review Chapter 5, "Signal-Handling Functions," of SAS/C Library Reference, Volume 1 before proceeding.

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.

atcoexit -- Register Coprocess Cleanup Function

SYNOPSIS

 #include <coproc.h>

 int atcoexit(void (*func)(void), coproc_t procid);
 

DESCRIPTION

The 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)).

RETURN VALUE

atcoexit returns 0 if successful or a nonzero value if unsuccessful.

PORTABILITY

atcoexit is not portable.

EXAMPLE

This example defines a routine 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;
    }
 }
 

RELATED FUNCTIONS

blkjmp, atexit

cocall -- Pass Control to a Coprocess

SYNOPSIS

 #include <coproc.h>

 void *cocall(coproc_t procid, void *arg);
 

DESCRIPTION

The 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:

RETURN VALUE

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.

CAUTIONS

Recursive cocalls 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.

EXAMPLE

 #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.                                      */ 
    .
 }
 

coexit -- Terminate Coprocess Execution

SYNOPSIS

 #include <coproc.h>

 void coexit(void *val);
 

DESCRIPTION

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.

RETURN VALUE

Control does not return from coexit.

EXAMPLE

See the example for coreturn.

RELATED FUNCTIONS

blkjmp, exit

coproc -- Return the ID of a Coprocess

SYNOPSIS

 #include <coproc.h>

 coproc_t coproc(int name);
 

DESCRIPTION

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.

RETURN VALUE

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.

EXAMPLE

 #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");
 

coreturn -- Return Control to a Coprocess

SYNOPSIS

 #include <coproc.h>

 void *coreturn(void *arg);
 

DESCRIPTION

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.

RETURN VALUE

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.

CAUTION

coreturn cannot be called from the main coprocess because it has no cocaller to return to.

EXAMPLE

This example is a companion to the 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);
    }
 }
 

cosignal -- Define a Global Signal Handler

SYNOPSIS

 #include <coproc.h>
 #include <signal.h>

    /* This typedef is in <coproc.h>. */ 
 typedef __remote void(*_COHANDLER)(int);
 _COHANDLER cosignal(int signum, _COHANDLER handler);
 

DESCRIPTION

The 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.

RETURN VALUE

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.

CAUTIONS

Use of 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.

EXAMPLE

The following example shows how a coprocess can be defined to get control every n seconds. The coprocess uses 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.    */ 
       .

    }
 }
 

RELATED FUNCTIONS

sigaction, signal

SEE ALSO

See Chapter 5, "Signal-Handling Functions," in theSAS/C Library Reference, Volume 1.

costart -- Create a New Coprocess

SYNOPSIS

 #include <coproc.h>
 
 coproc_t costart(void *(*func)(void *), struct costart_parms *p);
 

DESCRIPTION

The 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 0s.

RETURN VALUE

costart returns the ID of the new coprocess or 0 if a new coprocess could not be created.

CAUTIONS

All of the members in the 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.

RELATED FUNCTIONS

sigaddset

SEE ALSO

See Chapter 5, "Signal-Handling Functions," in the SAS/C Library Reference, Volume 1.

costat -- Return Coprocess Status

SYNOPSIS

 #include <coproc.h>

 int costat(coproc_t id);
 

DESCRIPTION

The costat function returns information about the status of a coprocess. The id argument is the ID of the coprocess whose status is desired.

RETURN VALUE

The value returned by costat is one of the symbolic constants STARTING, ACTIVE, BUSY, IDLE, or ENDED. These constants have the following significance:
STARTING
indicates that the coprocess has been created by a call to costart but has never been cocalled.
ACTIVE
indicates that the coprocess is the one currently running.
BUSY
indicates that the coprocess has cocalled another coprocess and therefore has been suspended by the cocall function. A BUSY coprocess cannot be cocalled itself.
IDLE
indicates that the coprocess is suspended by a call to coreturn. The coprocess will not execute again until it is cocalled.
ENDED
indicates that the coprocess has terminated.

CAUTION

The effects of passing an invalid coprocess ID to costat are unpredictable. Usually, this causes the value ENDED to be returned.

EXAMPLE

 #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.         */ 
 


Copyright (c) 1998 SAS Institute Inc. Cary, NC, USA. All rights reserved.