Pipes...

Mark Bartelt sysmark at aurora.physics.utoronto.ca
Mon May 13 23:30:20 AEST 1991


Michael Sweet writes:

| In a project I am working on, I want to fork and exec another process with
| pipes to *both* the standard input and output of the new process.
|               [ ... ]                                                my test
| program seems to create the 2 sets of pipes and fork the new program fine,
| but when it comes time to actually *write* anything to the standard input of
| the second process, the first process sits there waiting (presumably blocked.)

A couple days ago rec.humor.funny reprinted something from (I think) The
Guardian, containing some entries from a "programming is like sex because"
(or was it the reverse?) contest.  Somehow, the problem of communicating
bidirectionally with a child process seems like a good candidate:  It's
both a lot easier and a lot more fun once you've done it a few times :-)

This sort of thing, while not terribly difficult, isn't exactly trivial,
either.  A couple of caveats follow, and a (working!) example is appended,
but first a comment on Paul Jackson's followup:

| Only call pipe once, not twice.  The pipe(2) system call provides
| a pair of file descriptors, one good for reading and one for writing.
| And after duping them in the child to its stdin/stdout, close the
| file descriptors that you got from pipe.

I suspect that Paul didn't read Michael's original posting closely.  Note
that Michael wants to send stuff to the child's stdin *and* receive stuff
from the child's stdout.  I can't conceive of any way to do this without
using two pipes:  One for the parent to send stuff to the child, another
for the child to send stuff to the parent.  If this can be accomplished
by using a single pipe, I'd be interested in seeing how it's done.

Anyway, several things to watch out for ...

(1)  Be certain that all unnecessary file descriptors are closed.  Michael's
example doesn't close stdin_pipes[0] and stdout_pipes[1] in the child.  Note
that they're not needed, since the child has dup2()ed them to file descriptors
0 and 1.  It's not clear that failing to close them is actually the source of
the problem here, but it can't hurt.

(2)  If the child is producing output in response to input that it receives
from the parent, the parent had better read it.  If it doesn't, the "upward"
(child-to-parent) pipe will eventually constipate, and the child will block.
Because the child is blocked waiting for that pipe to partially drain, the
"downward" (parent-to-child) pipe will also constipate as the parent writes
more stuff into that pipe, so the parent will block as well, and the whole
business will hang.

(3)  If you're using stdio routines instead of read()/write() to move data
through the pipes, be sure that the streams are unbuffered (line buffered
may be good enough), or else one process will block waiting for data that
the other has written, but which hasn't actually gotten shoved into the back
end of the pipe since it's sitting instead in a partially-filled stdio buffer.

(4)  If the child is one which won't produce any output until it's seen all
its input (consider, for example, the child doing an exec() of /usr/bin/sort
just after the fork() and dup2()s), the parent should be sure to call close
(or fclose(), whichever is appropriate) on the back end of the "downward" pipe,
so that the child will see EOF on stdin.

(5)  If the child produces output on stdout, but perhaps not necessarily in
a predictable fashion (the example below produces exactly one line of output
for every line of input; spawning "sort" means that the child will produce no
output until it's seen all its input), the parent will have to sniff at the
"upward" pipe in a non-blocking way, in order to see whether there's anything
there that needs to be read (to keep the "upward" pipe from constipating).
You can do this sort of thing in a BSD environment by using FIONREAD; I'm not
certain of the best way to do this under IRIX.

Anyway, the example follows.  Note that these are really only templates, and
shouldn't be construed as bullet-proof an any sense:  No checking for error
returns from pipe() and fork(), for example; or for line-too-long situations
when calling fgets(); or several other things that really ought to be done.
Nonetheless, they should be useful as a vague outline of how to do it ...

Mark Bartelt                                                      416/978-5619
Canadian Institute for                                   mark at cita.toronto.edu
Theoretical Astrophysics                                 mark at cita.utoronto.ca

------------------------------------------------------------------------------

/*
 *	Parent process -- reads a line from stdin, sends it to
 *	child, reads a line back from child, sends it to stdout
 */

#include <stdio.h>

#define PCR	p_to_c[0]	/* parent-to-child pipe, read fd */
#define PCW	p_to_c[1]	/* parent-to-child pipe, write fd */
#define CPR	c_to_p[0]	/* child-to-parent pipe, read fd */
#define CPW	c_to_p[1]	/* child-to-parent pipe, write fd */

#define BUFSIZE 200

main()
{
	int	p_to_c[2];
	int	c_to_p[2];
	int	fk;
	FILE *	to_child;
	FILE *	from_child;
	char	buf[BUFSIZE];

	pipe(p_to_c);
	pipe(c_to_p);

	if ( (fk=fork()) == 0 ) {	/* Child process only */
		dup2(PCR,0);
		dup2(CPW,1);
	}

	close(PCR);			/* Both processes */
	close(CPW);

	if ( fk == 0 ) {		/* Child process only */
		close(PCW);
		close(CPR);
		execl("./sqrt","sqrt",0);
		fprintf(stderr,"sqrt exec failure\\n");
		exit(-1);
	}

	to_child   = fdopen(PCW,"w");
	from_child = fdopen(CPR,"r");
	setlinebuf(to_child);
	while ( fgets(buf,BUFSIZE,stdin) != NULL ) {
		fputs(buf,to_child);
		fgets(buf,BUFSIZE,from_child);
		fputs(buf,stdout);
	}

	close(PCW);
	close(CPR);
}

------------------------------------------------------------------------------

/*
 *	Child process -- reads a line from stdin, writes a line to
 *	stdout reporting square root of first numeric arg on input
 */

#include <stdio.h>
#include <math.h>

#define	BUFSIZE	200

char	buf[BUFSIZE];

main()
{
	double	val;

	setlinebuf(stdout);
	while ( fgets(buf,BUFSIZE,stdin) != NULL ) {
		sscanf(buf,"%lf",&val);
		printf("%.4f\n",sqrt(val));
	}
}



More information about the Comp.sys.sgi mailing list