HP C
Run-Time Library Reference Manual for OpenVMS Systems


Previous Contents Index

4.3 Program Example

Example 4-1 shows how the signal , alarm , and pause functions operate. It also shows how to establish a signal handler to catch a signal, which prevents program termination.

Example 4-1 Suspending and Resuming Programs

/*    CHAP_4_SUSPEND_RESUME.C                                   */ 
 
/* This program shows how to alternately suspend and resume a   */ 
/* program using the signal, alarm, and pause functions.        */ 
 
#define SECONDS 5 
 
#include <stdio.h> 
#include <signal.h> 
 
int number_of_alarms = 5;       /*  Set alarm counter.            */ 
 
void alarm_action(int); 
 
main() 
{ 
    signal(SIGALRM, alarm_action); /*  Establish a signal handler. */ 
                                   /*  to catch the SIGALRM signal.*/ 
 
    alarm(SECONDS);     /* Set alarm clock for 5 seconds. */ 
 
    pause();    /*  Suspend the process until     * 
                 *  the signal is received.       */ 
} 
 
void alarm_action(int x) 
{ 
    printf("\t<%d\007>", number_of_alarms); /*  Print the value of  */ 
                                            /*  the alarm counter.  */ 
 
    signal(SIGALRM, alarm_action);      /*  Reset the signal.       */ 
 
    alarm(SECONDS);     /*  Set the alarm clock.      */ 
 
    if (--number_of_alarms)     /*  Decrement alarm counter.  */ 
        pause(); 
} 

Here is the sample output from Example 4-1:


$ RUN  EXAMPLE
       <5>   <4>   <3>   <2>   <1>


Chapter 5
Subprocess Functions

The HP C Run-Time Library (RTL) provides functions that allow you to create subprocesses from a HP C program. The creating process is called the parent and the created subprocess is called the child.

To create a child process within the parent process, use the exec functions ( execl , execle , execv , execve , execlp , and execvp ) and the vfork function. Other functions are available to allow the parent and child to read and write data across processes ( pipe ) and to allow for synchronization of the two processes ( wait ). This chapter describes how to implement and use these functions.

The parent process can execute HP C programs in its children, either synchronously or asynchronously. The number of children that can run simultaneously is determined by the /PRCLM user authorization quota established for each user on your system. Other quotas that may affect the use of subprocesses are /ENQLM (Queue Entry Limit), /ASTLM (AST Waits Limit), and /FILLM (Open File Limit).

This chapter discusses the subprocess functions. Table 5-1 lists and describes all the subprocess functions found in the HP C RTL. For more detailed information on each function, see the Reference Section.

Table 5-1 Subprocess Functions
Function Description
Implementation of Child Processes
system Passes a given string to the host environment to be executed by a command processor.
vfork Creates an independent child process.
The exec Functions
execl, execle, execlp
execv, execve, execvp
Passes the name of the image to be activated in a child process.
Synchronizing Process
wait , wait3 , wait4 , waitpid , Suspends the parent process until a value is returned from a child.
Interprocess Communication
pipe Allows for communication between a parent and child.

5.1 Implementing Child Processes in HP C

Child processes are created by HP C functions with the OpenVMS LIB$SPAWN RTL routine. (See the VMS Run-Time Library Routines Volume for information on LIB$SPAWN.) Using LIB$SPAWN allows you to create multiple levels of child processes; the parent's children can also spawn children, and so on, up to the limits allowed by the user authorization quotas discussed in the introduction to this chapter.

Child processes can only execute other HP C programs. Other native-mode OpenVMS languages do not share the ability of HP C to communicate between processes; if they do, they do not use the same mechanisms. The parent process must be run under an HP supported command-language interpreter (CLI), such as DCL. You cannot run the parent as a detached process or under control of a user-supplied CLI.

Enabling the DECC$DETACHED_CHILD_PROCESS feature logical allows child processes to be created as detached processes instead of subprocesses. This feature has only limited support. In some cases, the console cannot be shared between the parent process and the detached process, which can cause exec to fail.

Parent and child processes communicate through a mailbox as shown in Figure 5-1. This mailbox transfers the context in which the child will run. This context mailbox passes information to the child that it inherits from the parent, such as the names and file descriptors of all the files opened by the parent and the current location within those files. The mailbox is deleted by the parent when the child image exits.

Figure 5-1 Communications Links Between Parent and Child Processes


Note

The mailbox created by the vfork and exec functions is temporary. The logical name of this mailbox is VAXC$EXECMBX and is reserved for use by the HP C RTL.

The mailbox is created with a maximum message size and a buffer quota of 512 bytes each, unless the buffer size and quota are explicitly specified with the DECC$PIPE_BUFFER_SIZE or DECC$PIPE_BUFFER_QUOTA feature logicals, or with the bufsize or bufquota parameters of the pipe function. See the pipe function for more information.

You need the TMPMBX privilege to create a mailbox with these RTL functions. Since TMPMBX is the privilege required by the DCL commands PRINT and SUBMIT, most users on a system have this privilege. To see what system privileges you have, enter a SHOW PROCESS/PRIVILEGES command.

You cannot change the characteristics of these mailboxes. For more information on mailboxes, see the VMS I/O User's Reference Volume.

5.2 The exec Functions

There are six exec functions that you can call to execute an HP C image in the child process. These functions expect that vfork has been called to set up a return address. The exec functions will call vfork if the parent process did not.

When vfork is called by the parent, the exec function returns to the parent process. When vfork was called by an exec function, the exec function returns to itself, waits for the child to exit, and then exits the parent process. The exec function does not return to the parent process unless the parent calls vfork to save the return address.

In OpenVMS Version 7.2, the exec functions were enhanced to activate either executable images or DCL command procedures. If no file extension is specified in the file_name argument, the functions first search for the file with the .EXE file extension and then for the file with the .COM file extension. If both the executable image and the command procedure with the same name exist, you must explicitly specify the .COM file extension to force activating the command procedure.

For a DCL command procedure, the exec functions pass the first eight arg0, arg1, ..., arguments specified in the exec call to the command procedure as P1, P2, ... parameters, preserving the case.

Unlike UNIX based systems, the exec functions in the HP C RTL cannot always determine if the specified executable image or command procedure exists and can be activated and executed. Therefore, the exec functions might appear to succeed even though the specified file cannot be executed by the child process.

The status of the child process, returned to the parent process, indicates that the error occurred. You can retrieve this error code by using one of the functions from the wait family of functions.

Note

The vfork and exec functions in the HP C RTL on OpenVMS systems work differently than on UNIX systems:
  • On UNIX systems, vfork creates a child process, suspends the parent, and starts the child running where the parent left off.
  • On OpenVMS systems, vfork establishes context later used by an exec function, but it is the exec function, not vfork , that starts a process running the specified program.


For a programmer, the key differences are:
  • On OpenVMS systems, code between the call to vfork and the call to an exec function runs in the parent process.

    On UNIX systems, this code runs in the child process.
  • On OpenVMS systems, the child inherits open file descriptors and so on, at the point where the exec function is called.

    On UNIX systems, this occurs at the point where vfork is called.

5.2.1 exec Processing

The exec functions use the LIB$SPAWN routine to create the subprocess and activate the child image within the subprocess. This child process inherits the parent's environment, including all defined logical names and command-line interpreter symbols.

By default, child processes also inherit the default (working) directory of their parent process. However, you can use the decc$set_child_default_dir function to set the default directory for a child process as it begins execution. For more information about the decc$set_child_default_dir function, see the Reference Section.

The exec functions use the logical name VAXC$EXECMBX to communicate between parent and child; this logical name must not exist outside the context of the parent image.

The exec functions pass the following information to the child:

When everything is transmitted to the child, exec processing is complete. Control in the parent process then returns to the address saved by vfork and the child's process ID is returned to the parent.

See Section 4.2.4 for a discussion of signal actions and the SIGCHLD signal.

5.2.2 exec Error Conditions

The exec functions will fail if LIB$SPAWN cannot create the subprocess. Conditions that can cause a failure include exceeding the subprocess quota or finding the communications by the context mailbox between the parent and child to be broken. Exceeding some quotas will not cause LIB$SPAWN to fail, but will put LIB$SPAWN into a wait state that can cause the parent process to hang. An example of such a quota is the Open File Limit quota.

You will need an Open File Limit quota of at least 20 files, with an average of three times the number of concurrent processes that your program will run. If you use more than one open pipe at a time, or perform I/O on several files at one time, this quota may need to be even higher. See your system manager if this quota needs to be increased.

When an exec function fails, a value of - 1 is returned. After such a failure, the parent is expected to call either the exit or _exit function. Both functions then return to the parent's vfork call, which returns the child's process ID. In this case, the child process ID returned by the exec function is less than zero. For more information about the exit function, see the Reference Section.

5.3 Synchronizing Processes

A child process is terminated when the parent process terminates. Therefore, the parent process must check the status of its child processes before exiting. This is done using the HP C RTL function wait .

5.4 Interprocess Communication

A channel through which parent and child processes communicate is called a pipe. Use the pipe function to create a pipe.

5.5 Program Examples

Example 5-1 shows the basic procedures for executing an image in a child process. The child process in Example 5-1 prints a message 10 times.

Example 5-1 Creating the Child Process

/*      chap_5_exec_image.c     */ 
 
/* This example creates the child process.  The only    */ 
/* functionality given to the child is the ability to   */ 
/* print a message 10 times.                            */ 
 
#include <climsgdef.h>  /* CLI status values  */ 
#include <stdio.h> 
#include <perror.h> 
#include <processes.h> 
#include <stdlib.h> 
 
static const char *child_name = "chap_5_exec_image_child.exe" ; 
 
main() 
{ 
   int status, 
       cstatus; 
 
   /* NOTE:                                            */ 
   /*    Any local automatic variables, even those     */ 
   /*    having the volatile attribute, may have       */ 
   /*    indeterminant values if they are modified     */ 
   /*    between the vfork() call and the matching     */ 
   /*    exec() call.                                  */ 
 
(1)   if ((status = vfork()) != 0) {   
       /* This is either an error or                   */ 
       /* the "second" vfork return, taking us "back"  */ 
       /* to parent mode.                              */ 
(3)       if (status < 0)   
            printf("Parent - Child process failed\n"); 
       else { 
            printf("Parent - Waiting for Child\n"); 
(4)            if ((status = wait(&cstatus)) == -1)  
               perror("Parent - Wait failed"); 
(5)            else if (cstatus == CLI$_IMAGEFNF)    
               printf("Parent - Child does not exist\n"); 
            else 
               printf("Parent - Child final status: %d\n", cstatus); 
        } 
    } 
(2)    else {  /* The FIRST Vfork return is zero, do the exec */ 
           printf("Parent - Starting Child\n"); 
           if ((status = execl(child_name, 0)) == -1) { 
               perror("Parent - Execl failed"); 
               exit(EXIT_FAILURE); 
         } 
    } 
} 
 
---------------------------------------------------------- 

/*      CHAP_5_EXEC_IMAGE_CHILD.C                    */ 
 
/* This is the child program that writes a message   */ 
/* through the parent to "stdout"                    */ 
 
#include <stdio.h> 
 
main() 
{ 
    int i; 
 
    for (i = 0; i < 10; i++) 
        printf("Child - executing\n"); 
    return (255) ;    /* Set an unusual success stat */ 
} 

Key to Example 5-1:

  1. The vfork function is called to set up the return address for the exec call.
    The vfork function is normally used in the expression of an if statement. This construct allows you to take advantage of the double return aspect of vfork , since one return value is 0 and the other is nonzero.
  2. A 0 return value is returned the first time vfork is called and the parent executes the else clause associated with the vfork call, which calls execl .
  3. A negative child process ID is returned when an exec function fails. The return value is checked for these conditions.
  4. The wait function is used to synchronize the parent and child processes.
  5. Since the exec functions can indicate success up to this point even if the image to be activated in the child does not exist, the parent checks the child's return status for the predefined status, CLI$_IMAGEFNF (file not found).

In Example 5-2, the parent passes arguments to the child process.

Example 5-2 Passing Arguments to the Child Process

/*       CHAP_5_CHILDARG.C                                           */ 
 
/* In this example, the arguments are placed in an array, gargv,     */ 
/* but they can be passed to the child explicitly as a zero-         */ 
/* terminated series of character strings. The child program in this */ 
/* example writes the arguments that have been passed it to stdout.  */ 
 
#include <climsgdef.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <perror.h> 
#include <processes.h> 
 
const char *child_name = "chap_5_childarg_child.exe" ; 
 
main() 
{ 
    int status, 
        cstatus; 
    char *gargv[] = 
    {"Child", "ARGC1", "ARGC2", "Parent", 0}; 
 
    if ((status = vfork()) != 0) { 
        if (status < -1) 
            printf("Parent - Child process failed\n"); 
        else { 
            printf("Parent - waiting for Child\n"); 
            if ((status = wait(&cstatus)) == -1) 
                perror("Parent - Wait failed"); 
            else if (cstatus == CLI$_IMAGEFNF) 
                printf("Parent - Child does not exist\n"); 
            else 
                printf("Parent - Child final status: %x\n", 
                       cstatus); 
        } 
    } 
    else { 
        printf("Parent - Starting Child\n"); 
        if ((status = execv(child_name, gargv)) == -1) { 
            perror("Parent - Exec failed"); 
            exit(EXIT_FAILURE); 
        } 
    } 
} 
 
-------------------------------------------------------- 
/*       CHAP_5_CHILDARG_CHILD.C                      */ 
 
/* This is a child program that echo's its arguments   */ 
 
#include <stdio.h> 
 
main(argc, argv) 
    int argc; 
    char *argv[]; 
{ 
    int i; 
 
    printf("Program name: %s\n", argv[0]); 
 
    for (i = 1; i < argc; i++) 
        printf("Argument %d: %s\n", i, argv[i]); 
    return(255) ; 
} 

Example 5-3 shows how to use the wait function to check the final status of multiple children being run simultaneously.

Example 5-3 Checking the Status of Child Processes

/*        CHAP_5_CHECK_STAT.C                                   */ 
 
/* In this example 5 child processes are started.  The wait()   */ 
/* function is placed in a separate for loop so that it is      */ 
/* called once for each child. If wait() were called within     */ 
/* the first for loop, the parent would wait for one child to   */ 
/* terminate before executing the next child. If there were     */ 
/* only one wait request, any child still running when the      */ 
/* parent exits would terminate prematurely.                    */ 
 
#include <climsgdef.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <perror.h> 
#include <processes.h> 
 
const char *child_name = "chap_5_check_stat_child.exe" ; 
 
main() 
{ 
    int status, 
        cstatus, 
        i; 
 
    for (i = 0; i < 5; i++) { 
        if ((status = vfork()) == 0) { 
            printf("Parent - Starting Child %d\n", i); 
            if ((status = execl(child_name, 0)) == -1) { 
                perror("Parent - Exec failed"); 
                exit(EXIT_FAILURE); 
            } 
        } 
        else if (status < -1) 
            printf("Parent - Child process failed\n"); 
    } 
 
    printf("Parent - Waiting for children\n"); 
 
    for (i = 0; i < 5; i++) { 
        if ((status = wait(&cstatus)) == -1) 
            perror("Parent - Wait failed"); 
        else if (cstatus == CLI$_IMAGEFNF) 
            printf("Parent - Child does not exist\n"); 
        else 
            printf("Parent - Child %X final status: %d\n", 
                   status, cstatus); 
    } 
} 

Example 5-4 shows how to use the pipe and dup2 functions to communicate between a parent and child process through specific file descriptors. The #define preprocessor directive defines the preprocessor constants inpipe and outpipe as the names of file descriptors 11 and 12.

Example 5-4 Communicating Through a Pipe

/*        CHAP_5_PIPE.C                                         */ 
 
/* In this example, the parent writes a string to the pipe for  */ 
/* the child to read.  The child then writes the string back    */ 
/* to the pipe for the parent to read.  The wait function is    */ 
/* called before the parent reads the string that the child has */ 
/* passed back through the pipe.  Otherwise, the reads and      */ 
/* writes will not be synchronized.                             */ 
 
#include <perror.h> 
#include <climsgdef.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <processes.h> 
#include <unixio.h> 
 
#define inpipe 11 
#define outpipe 12 
 
const char *child_name = "chap_5_pipe_child.exe" ; 
 
main() 
{ 
    int pipes[2]; 
    int mode, 
        status, 
        cstatus, 
        len; 
    char *outbuf, 
        *inbuf; 
 
    if ((outbuf = malloc(512)) == 0) { 
        printf("Parent - Outbuf allocation failed\n"); 
        exit(EXIT_FAILURE); 
    } 
 
    if ((inbuf = malloc(512)) == 0) { 
        printf("Parent - Inbuf allocation failed\n"); 
        exit(EXIT_FAILURE); 
    } 
    if (pipe(pipes) == -1) { 
        printf("Parent - Pipe allocation failed\n"); 
        exit(EXIT_FAILURE); 
    } 
 
    dup2(pipes[0], inpipe); 
    dup2(pipes[1], outpipe); 
    strcpy(outbuf, "This is a test of two-way pipes.\n"); 
 
    status = vfork(); 
 
    switch (status) { 
    case 0: 
        printf("Parent - Starting child\n"); 
        if ((status = execl(child_name, 0)) == -1) { 
            printf("Parent - Exec failed"); 
            exit(EXIT_FAILURE); 
        } 
        break; 
 
    case -1: 
        printf("Parent - Child process failed\n"); 
        break; 
 
    default: 
        printf("Parent - Writing to child\n"); 
  
        if (write(outpipe, outbuf, strlen(outbuf) + 1) == -1) { 
            perror("Parent - Write failed"); 
            exit(EXIT_FAILURE); 
        } 
        else { 
            if ((status = wait(&cstatus)) == -1) 
                perror("Parent - Wait failed"); 
 
            if (cstatus == CLI$_IMAGEFNF) 
                printf("Parent - Child does not exist\n"); 
            else { 
                printf("Parent - Reading from child\n"); 
                if ((len = read(inpipe, inbuf, 512)) <= 0) { 
                    perror("Parent - Read failed"); 
                    exit(EXIT_FAILURE); 
                } 
                else { 
                    printf("Parent: %s\n", inbuf); 
                    printf("Parent - Child final status: %d\n", 
                            cstatus); 
                } 
            } 
        } 
        break; 
    } 
}  
 
------------------------------------------------------------------ 
/*        CHAP_5_PIPE_CHILD.C                                   */ 
 
/* This is a child program which reads from a pipe and writes   */ 
/* the received message back to its parent.                     */ 
 
#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <unistd.h> 
 
#define inpipe 11 
#define outpipe 12 
 
main() 
{ 
    char *buffer; 
    int len; 
 
    if ((buffer = malloc(512)) == 0) { 
        perror("Child - Buffer allocation failed\n"); 
        exit(EXIT_FAILURE); 
    } 
 
    printf("Child - Reading from parent\n"); 
    if ((len = read(inpipe, buffer, 512)) <= 0) { 
        perror("Child - Read failed"); 
        exit(EXIT_FAILURE); 
    } 
    else { 
        printf("Child: %s\n", buffer); 
        printf("Child - Writing to parent\n"); 
        if (write(outpipe, buffer, strlen(buffer) + 1) == -1) { 
            perror("Child - Write failed"); 
            exit(EXIT_FAILURE); 
        } 
    } 
    exit(EXIT_SUCCESS); 
} 


Previous Next Contents Index