8.2 The ACE_Process Class

I l @ ve RuBoard

Motivation

Process creation and management is a core part of most general-purpose operating systems (though Section 6.2 describes why some embedded operating systems don't provide process capabilities). Operating systems that do support multiple processes provide functionality to

  • Create a process to run a particular program image

  • End a process voluntarily or involuntarily

  • Synchronize with the exit of another process

Given the maturity of these OS process management capabilities, you might expect a unified standard to govern this area. For a variety of factors, unfortunately , that's not the case. For instance, the following table outlines the key differences between POSIX/UNIX and Win32 process management:

POSIX/UNIX Win32
fork() duplicates the calling process, including open I/O handles; a subsequent (optional) call to an exec *() function replaces the current program image with a new one. CreateProcess() both spawns a new process and runs a designated program image in a single system function call.
The kill() function sends a signal to a process to stop it. Processes can intercept some signals and interpret them as requests to shut down and exit gracefully. TerminateProcess() forces a process to exit without giving the process an opportunity to clean itself up.
The waitpid() function can be used to wait for child processes to exit. A parent process can also catch the SIGCHLD signal, which the OS delivers to the parent process when a child process exits. Either the WaitForMultipleObjects() or WaitForSingleObject() function can be used to wait on a process's handle, which is signaled when it exits.

Although a complete discussion of OS multiprocessing mechanisms is beyond the scope of this book (see [Sch94, Ste92, Ric97] for more information), this comparison shows key differences between two popular operating environments. As you can see, OS process management mechanisms differ syntactically and semantically. Addressing these platform variations in each application is difficult, tedious , error prone, and unnecessary because ACE provides the ACE_Process class.

Class Capabilities

The ACE_Process class encapsulates the variation among different OS multiprocessing APIs in accordance with the Wrapper Facade pattern. This class defines uniform and portable methods whose capabilities enable applications to

  • Spawn and terminate processes

  • Synchronize on process exit

  • Access process properties, such as process ID

The interface of the ACE_Process class is shown in Figure 8.2; its key platform-independent methods are outlined in the following table:

Method Description
prepare() Called by spawn() before the child process is spawned. Enables inspection and/or modification of the options to be used for the new process, and is a convenient place to set platform-specific options.
spawn() Creates a new process address space and runs a specified program image.
unmanage() Called by ACE_Process_Manager when a process it manages exits.
getpid() Returns the process id of the new child process.
exit_code() Returns the exit code of the child process.
wait() Waits for the process we created to exit.
terminate() Terminates the process abruptly, without giving the process a chance to clean up (so use with caution).
Figure 8.2. The ACE_Process Class Diagram

The ACE_Process class provides portable access to the common capability of spawning a new process and executing a new program image in the new process. Process management is one of the few functional areas, however, where wrapper facades can't encapsulate all the OS differences into one unified class that offers portability across the full range of OS platforms supported by ACE. For example, Sidebar 16 describes some key portability challenges associated with servers that use certain unique semantics of the POSIX fork() mechanism.

Since UNIX is a key platform for networked applications, the ACE_Process class offers a number of methods that only work in a POSIX/UNIX environment. These methods are useful (indeed, necessary) in many design situations. To avoid confusion, they are listed in the table below, separate from the portable methods that ACE_Process offers.

Method Description
parent() Can be overridden to supply application-specific behavior; called in the parent's process context after the fork() call if the child process was successfully forked.
child() Can be overridden to supply application-specific behavior; called in the child's process context after the fork() and before exec() .
kill() Sends the process a signal. This is only portable to UNIX/POSIX operating systems that can send signals to designated processes.

Sidebar 16: POSIX Portability Challenges

Many UNIX applications use the POSIX fork() system function to create concurrent process-based servers. It can be hard to port these servers if they rely on the ability of fork() to both

  1. Have a parent process fork() a child process with a duplicate of its parent's address space and copies of all the parent's I/O handles (Including sockets) and

  2. Have both parent and child process return from fork() at the same location but with different return values, which then run different parts of the server processing within the same program Image.

There's no facility for duplicating an address space on platforms without the fork() function, However, most popular OS platforms, Including WIn32, support the more important capability of obtaining the needed I/O handles in the child process. The ACE_Process_Options class provides a portable way to use this capability, as shown in logging server example in Section 8.4.

Example

This example illustrates how to use ACE_Process to portably spawn a new process that runs a specified program image. The program we implement computes factorials "recursively" by

  1. Having a parent process spawn a child process that runs the program image itself to compute the factorial of a number passed in the FACTORIAL environment variable, defaulting to factorial of 10 (10!) if no environment variable is set.

  2. Each parent process then waits for its child process to finish, uses its child's exit code to compute the factorial for n, and returns this as the exit code for the process.

Although this is clearly not an efficient way to compute factorials, it illustrates the ACE_Process features concisely, as shown below.

 #include "ace/OS.h" #include "ace/Process.h" int main (int argc, char *argv[]) {   ACE_Process_Options options;   char *n_env = 0;   int n;   if (argc == 1) { // Top-level process.     n_env = ACE_OS::getenv ("FACTORIAL");     n = n_env == 0 ? 0 : atoi (n_env);     options.command_line ("%s %d", argv[0], n == 0 ? 10 : n);   }   else if (atoi (argv[l]) == 1) return 1; // Base case.   else {     n = atoi (argv[1]);     options.command_line ("%s %d", argv[0], n - 1);   }   ACE_Process child;   child.spawn (options); // Make the ''recursive'' call.   child.wait ();   return n * child.exit_code (); // Compute n factorial. } 

The call to ACE_Process::wait() will work regardless of whether the spawned process has finished before the call or not. If it has finished, the call completes immediately; if not, the method waits until the child process exits. This method is portable to all operating systems that support multiple processes.

The example above presents the most basic and portable use of ACE_Process_Options : to specify the command line for a spawned program. The next section discusses why we need a separate options class and shows its range of capabilities.

I l @ ve RuBoard


C++ Network Programming
C++ Network Programming, Volume I: Mastering Complexity with ACE and Patterns
ISBN: 0201604647
EAN: 2147483647
Year: 2001
Pages: 101

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net