
Interprocess communication essentials in Perl
Connections
Anyone who has written a fairly complex script has had to deal with interprocess communication (IPC) at one time or another. In this article, I talk about the use and structure of IPC by reading and writing from parent to child and from child to parent processes, and I'll look at bi-directional communication to achieve both at the same time.
A process is a task given to the operating system that has its own execution resources, including its own allocation of CPU time. A process can interact with other processes in a number of different ways, as you'll see in this article.
The support for IPC in Perl is considerable. At its most basic is the use of backticks to perform system commands (e.g., unlink
). When you submit a command with backticks, a new child process is created. When the task the child process has been given is complete, the child process reports back to the parent process and the parent script continues.
Perl supplies the fork
function to start a new process after setting up a pipe with the pipe
command. Although you can use the open
function in lieu of the pipe
function, I'll stick to piping at first.
It should be noted that the use of fork
is entirely operating system dependant. It is readily available on Unix or Linux machines, but you'll run into problems on a Windows system because the command is not supported on that platform.
Signal Handling
On Linux, processes can communicate with each other with signals. Perl implements an easy mechanism for catching signals sent to your processes. All you do is connect a signal-handling subroutine to the signal's entry in a predefined %SIG hash. To set up a machination to catch an INT
signal, use:
$SIG{INT} = \&handler_sub;
Note that a hard reference to the handler_sub
subroutine was used, but you could also use symbolic references, if required. The handler_sub
subroutine simply gets the name of the signal passed as an argument:
sub handler_sub { $forced_scalar = shift; die "The signal was $forced_scalar"; }
A signal handler like this gives you an idea of the ease with which Perl handles IPC. The die
and print make for a clean exit from the subroutine.
You should note here that Perl is currently not re-entrant. When a function is re-entrant, you can interrupt processing while still inside the function and call it again at any time, because the original state of the data is stored and is restored when the second call to the function exits. If a function is not re-entrant, an interrupt and subsequent call will overwrite the original data.
Functions
Another easy way to play with processes is with the exec
function, which executes your commands and doesn't return if the command is executed without error. For example, the exec
function will fail and return a false value if the program you called isn't found. That is, exec
returns only when an error is encountered and is the only time exec
will return a value. If you want a return value, you would use system
instead of exec
. I'll explore system
thoroughly later.
Parameters passed to exec
are treated in a very interesting way. The list of parameters passed is parsed, and the first element in the list is treated as the program to run. The next value is a function of the program being executed. For example, if you were to use the copy
command, you would specify the file name to be copied as a parameter. In the code
@thisarray = ("copy", "thisfile.txt"); if (exec(@thisarray)) { if ($?) { die qq{Error Encountered: $?}; } else { die qq{One File Copied. File Name Is: $thisarray[1]}; } }
the first element of the array passed is the program to run, and the next item within the array is the file name. The exec
is executed if @thisarray
exists, and the two parameters within @thisarray
are used to perform the copy
system command. If no errors are encountered, the program prints and exits. The program also reports whether an error has been placed in the $?
status variable, which then prints an error message and exits the program without copying the file.
This simple and intuitive way of creating processes isn't all that is available. To really get an idea of how to use processes, you must explore the system
function. When you call a program with exec
, it replaces the current program with the one called by exec
. If you were to use the system
command, however, you'd end up with a program that forks and creates a child process. The program is then executed, and any values you have need for can be returned to the parent.
Pipes
The means to send data to and from different processes is a necessarily simple task. To open a program with a pipe, I use:
open(THISFILEHANDLE, "thisfile.cgi |");
The pipe (|
) character sets the file to send its output to the calling program. To make the operation go the other way and send data to the program, simply move the pipe:
open(THISFILEHANDLE, "| thisfile.cgi");
The child process, then, can be piped to or from, depending on what is required. Pipes are fundamental to IPC. The entire read operation would be,
open(THISFILEHANDLE, "thisfile.cgi |"; while (<THISFILEHANDLE>) { print; } close(THISFILEHANDLE);
where the file that is opened and executed simply has a print
statement. This ease of reading and writing to different processes is a big reason why Perl has stayed alive all these years. To send data would be just a little different:
open(THISFILEHANDLE, "| thisfile.cgi"; print THISFILEHANDLE "Hey There!"; close(THISFILEHANDLE);
The example sends the string Hey There! to the program that was opened. What the program that was opened does with the string is entirely up to you.
Standard File Handles
In the previous examples I used named pipes and file handles. Now I'll explore STDOUT and STDERR. When you use one of the various IPC methods, the list passed is parsed for Unix metacharacters that can be used to redirect standard file handles, such as the aforementioned STDOUT and STDERR.
To redirect a standard file handle, you would refer to them with Unix file descriptors. The STDIN file descriptor is 0
, STDOUT is 1
, and STDERR is 2
. You may use notation such as 1>thisfilename
to send STDOUT to a file or you can use the notation
$this_value = `this_program 1>&2`;
to catch a program's STDOUT and STDERR. This example shows the backtick method, but you can also use the open
function to acquire the same result:
open(THISPIPEHANDLE, "this_program 1>&2 |") or die "Could Not Open Pipe"; while (<THISPIPEHANDLE>) { print; }
The this_program
parameter has a simple print
statement in it. You can see at a glance the amazing flexibility Perl gives you. You can also write to a child process with the open
function:
if (open(THISCHILDHANDLE, "|-")) { print THISCHILDHANDLE "This is a text message"; close (THISCHILDHANDLE); }
The open
statement creates a child process of the current process and reads from that process with the |-
after the process name. The statement creates a new process in THISCHILDHANDLE
and causes the program to fork. The statements return false in the child process and the process is destroyed with the close
function.
Parent and Child
In the next example, I read from and write to a child process created with fork
, the pipe
function, and a couple of functions from IO::Handle (Listing 1). The autoflush()
function from IO::Handle makes sure the pipe is unbuffered. You want it to be unbuffered so the data flows through, rather than getting stuck in, the pipe. It also uses the waitpid()
function to wait for the child process to terminate.
Listing 1: Child Read and Write
01 Use IO::Handle; 02 03 pipe(READHANDLE, WRITEHANDLE); 04 05 WRITEHANDLE->autoflush(1); 06 READHANDLE->autoflush(1); 07 08 if ($processed = fork) { 09 close(READHANDLE); 10 print WRITEHANDLE "Here is some text"; 11 close(WRITEHANDLE); 12 waitpid($processed, 0); 13 } else { 14 close(WRITEHANDLE); 15 while (defined($text = <READHANDLE>)) { 16 print $text; 17 }; 18 exit; 19 }
You can also write to a parent process from a child process with the open
function and a pipe:
if (open(THISCHILDHANDLE, "-|")) { print <THISCHILDHANDLE>; close(THISCHILDHANDLE); } else { print "This is a text message"; exit; }
The above example prints This is a text message to the parent process from the child process. Creating a new process with fork
also lets you write to a parent process from a child process (Listing 2).
Listing 2: Child to Parent Write
01 Use IO::Handle; 02 03 pipe(THISREADHANDLE, THISWRITEHANDLE); 04 05 THISWRITEHANDLE->autoflush(1); 06 THISREADHANDLE->autoflush(1); 07 08 if ($processed = fork) { 09 close THISWRITEHANDLE; 10 while (defined($text = <THISREADHANDLE>)) { 11 print $text; 12 } 13 close THISREADHANDLE; 14 waitpid($processed, 0); 15 } else { 16 close THISREADHANDLE; 17 print THISWRITEHANDLE "This is a text message"; 18 exit; 19 }
Asynchronous Communication
At this point, I've given you a lot of IPCs to mull over, but now I'll get a little more complicated by discussing the use of bi-directional (asynchronous) communication. I'll use two pipe
commands to communicate with the processes I create, write both ways, and then kill everything, leaving no lingering processes (Listing 3).
Listing 3: Asynchronous Communication
01 Use IO:Handle; 02 03 pipe(READFROMCHILD, WRITETOPARENT); 04 pipe(READFROMPARENT, WRITETOCHILD); 05 06 READFROMCHILD->autoflush(1); 07 READFROMPARENT->autoflush(1); 08 WRITETOCHILD->autoflush(1); 09 WRITETOPARENT->autoflush(1); 10 11 if ($this = fork) { 12 close READFROMPARENT; 13 close WRITETOPARENT; 14 print WRITETOCHILD "The parent process says hello"; 15 $thisdata = <READFROMCHILD>; 16 print "The parent process read $thisdata"; 17 close READFROMCHILD; 18 close WRITETOCHILD; 19 waitpid(-1, 0); 20 } else { 21 close READFROMCHILD; 22 close WRITETOCHILD; 23 $thisdata = <READFROMPARENT>; 24 print "The child process read $thisdata"; 25 print WRITETOPARENT "The child process says hello"; 26 close READFROMPARENT; 27 close WRITETOPARENT; 28 exit; 29 }
This example is the culmination of everything I've talked about so far: I open two sets of linked pipes and both write to and read from one process to another. The waitpid(-1, 0)
tells the program to wait for any process. You can also have waitpid()
return a value of 0
immediately if no dead child processes are found, as shown here with the use of POSIX
:
use POSIX "sys_wait_h"; $this_id = waitpid(-1, &WNOHANG);
With these handy machinations, you'll be up and running and using processes to get your work done in no time. I hope you enjoyed reading as much as I did writing. Happy programming!