tags:

views:

242

answers:

3

This code below is for executing ls -l | wc -l. In the code, if I comment close(p[1]) in parent then the program just hangs, waiting for some input. Why it is so? The child writes output of ls on p1 and parent should have taken that output from p0.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

main ()
{
  int i;
  int p[2];
  pid_t ret;
  pipe (p);
  ret = fork ();

  if (ret == 0)
    {
      close (1);
      dup (p[1]);
      close (p[0]);
      execlp ("ls", "ls", "-l", (char *) 0);
    }

  if (ret > 0)
    {
      close (0);
      dup (p[0]);
      //Doubt, Commenting the line below does not work WHy?
      close (p[1]);
      wait (NULL);
      execlp ("wc", "wc", "-l", (char *) 0);
    }
}
A: 

If the child doesn't close p[1], then that FD is open in two processes -- parent and child. The parent eventually closes it, but the child never does -- so the FD stays open. Therefore any reader of that FD (the child in this case) is going to wait forever just in case more writing it's gonna be done on it... it ain't, but the reader just doesn't KNOW!-)

Alex Martelli
+4  A: 

pipe + fork creates 4 file descriptors, two are inputs

Before the fork you have a single pipe with one input and one output.

After the fork you will have a single pipe with two inputs and two outputs.

If you have two inputs for the pipe (that a proc writes to) and two outputs (that a proc reads from), you need to close the other input or the reader will also have a pipe input which never gets closed.

In your case the parent is the reader, and in addition to the output end of the pipe, it has an open other end, or input end, of the pipe that stuff could, in theory, be written to. As a result, the pipe never sends an eof, because when the child exits the pipe is still open due to the parent's unused fd.

So the parent deadlocks, waiting forever for it to write to itself.

DigitalRoss
I understood ur answer. I have some more doubts.1) How can we generate EOF from keyboard?2) When we call dup, we are just aliasing the file descripter, but actually they share the same entry in the file table. SO if a process closes one copy of that file descripter, can it still use the other copy? Because that entry in file table must have been removed.3) When exec is called, do file descrpters remain with the new program? From the above code that I have given, it seems even after calling exec, it retains the copy.
avd
(1) The terminal recognizes a character (usually control-D) and arranges to send all the waiting characters to the reading program; if there are no characters waiting, it sends 0 bytes, and the reading program interprets 0 bytes read as EOF. (2) Yes, both copies of the file descriptor can be used - but that usually leads to confusion. (3) Unless you use fcntl() with the `FD_CLOEXEC` option, or you have a modern enough system that it supports `O_CLOEXEC` as a flag to `open()`, then file descriptors remain open across an `exec()` operation.
Jonathan Leffler
It remains accessible because Unix is carefully designed to ensure that it does. You can look at Stevens' 'Advanced Programming in the UNIX Environment' or Rochkind's 'Advanced UNIX Programming' for discussions of how file descriptors and file table entries are handled.
Jonathan Leffler
One more doubt, In the ans given above(@DigitalRoss), Am I understanding it correctly? "When child(ls) exits, its descripters are closed, so wc encounters EOF because it is trying to read a pipe whose writers end is closed. " Is that what u meant?
avd
I might have simplified (3) to "Yes, fd's are unchanged by exec", but strictly speaking JL is quite right about everything.
DigitalRoss
The answer to the new question is "yes". (As long as the extra copies are also all closed.)
DigitalRoss
+3  A: 

Note that 'dup(p[1])' means you have two file descriptors pointing to the same file. It does not close p[1]; you should do that explicitly. Likewise with 'dup(p[0])'. Note that a file descriptor reading from a pipe only returns zero bytes (EOF) when there are no open write file descriptors for the pipe; until the last write descriptor is closed, the reading process will hang indefinitely. If you dup() the write end, there are two open file descriptors to the write end, and both must be closed before the reading process gets EOF.

You also do not need or want the wait() call in your code. If the ls listing is bigger than a pipe can hold, your processes will deadlock, with the child waiting for ls to complete and ls waiting for the child to get on with reading the data it has written.

When the redundant material is stripped out, the working code becomes:

#include <unistd.h>

int main(void)
{
    int p[2];
    pid_t ret;
    pipe(p);
    ret = fork();

    if (ret == 0)
    {
        close(1);
        dup(p[1]);
        close(p[0]);
        close(p[1]);
        execlp("ls", "ls", "-l", (char *) 0);
    } 
    else if (ret > 0)
    {
        close(0);
        dup(p[0]);
        close(p[0]);
        close(p[1]);
        execlp("wc", "wc", "-l", (char *) 0);
    }
    return(-1);
}

On Solaris 10, this compiles without warning with:

Black JL: gcc -Wall -Werror -Wmissing-prototypes -Wstrict-prototypes -o x x.c
Black JL: ./x
      77
Black JL:
Jonathan Leffler
I got it. I have one more doubt. When 2 file descripters are pointing to the same file and I call close() on one. Does the other remain accesible? Beacuse they share the same entry in the file table. So after calling close on Ist , that entry must be removed.
avd
Yes, close() only closes the file descriptor; it doesn't clean up the file table entry if there's another file descriptor also referring to it. The file table entry is only removed when all file descriptors referring to it are closed.
Jonathan Leffler
Thanks a lot, a lot of doubts have been cleares now.
avd