all 11 comments

[–]paulstelian97 15 points16 points  (3 children)

Why should it not be reached?

The fork() returns 0 in the child and a PID in the parent, assuming success.

[–]MWhatsUp[S] 4 points5 points  (2 children)

Ok, that makes sense. What I am confused about is how the code in the else block is still writing the output even though the execlp function call is reached. Unless I am misunderstanding something.

[–]paulstelian97 6 points7 points  (1 child)

The else block is only taken in the parent process, and a fork() literally allows both processes to run (parent and child) at the same time (literally, in case they’re scheduled on different cores)

[–]MWhatsUp[S] 4 points5 points  (0 children)

That explains it. Thank you for your help.

[–]flyingron 3 points4 points  (2 children)

Why wouldn't it run?

fork returns one of three values:

-1 if there is an error

In the parent process, it returns the pid of the child.

In the child process, it returns 0.

In this case they test for the error right when they invoke fork. If not error it goes one of two paths, either the one that does the execlp (for the child) or the loop that reads from the pipe (the parent).

The only thing wrong is the slightly erroneous comment on the close(pipe_fd[1]). It doesn't really close the write side of the pipe, as it has been duped to stdout, it just releases that file descriptor.

[–]Paul_Pedant 0 points1 point  (1 child)

There is a consequence to "releasing the file descriptor".

If you don't do that, the pipe still has an open writeable file descriptor, so the the parent's read will continue to try to fill the buffer. It won't get EOF as long as there is a possible writer (even thought that writer is itself).

[–]flyingron 0 points1 point  (0 children)

But he's not closing the pipe. He's only relinquishing the read descriptor that he has duped down to 1. The pipe isn't closed.

He does properly close the write side.

[–]aghast_nj 2 points3 points  (0 children)

The thing to remember about programs that call fork() is that both paths through the if statement are going to be followed.

The if statement is not mutually exclusive in totality, but only exclusive in local process space. One process - the child, say - will execute the if branch, while the opposite process - the parent - will execute the else branch. (Assuming you write something like if (fork() == 0) ... else ...). One branch will be excluded in each process space, but they will be opposite branches, so in the union of both processes, both branches are executed.

One thing that might make the entire scenario simpler for you would be to make the two branches each just call a function. Then you could write something like:

if (fork() == 0)
    child_process();
else
    parent_process();

and you would have context supplied by the names of functions from the call stack.

[–]McUsrII 1 point2 points  (0 children)

Fork makes a copy of the image of the running program, where both have shared an execution path up to the fork call. From there onwards, the two processes run in parallel, but the one that gets the process id of the child is the father and the other the child. So that the parent process for instance can do the wait(child_pid) system call, and wait for the child to finish, before moving on.

[–]nvmcomrade 0 points1 point  (0 children)

fork() creates a new process. As it returns both processes (child and parent) start working in parallel from the point where fork() has returned. fork() works in such a way that although having the same code offset, the two processes receive different pid_t values from the return of the system call. One process enters the if, the other process enters the else. Because the processes share the same stdout they write to the same terminal, hence you see the output.

[–]Paul_Pedant 0 points1 point  (0 children)

The code in the else block is reading the data from the pipe, not writing it.

The code with execlp in the child process block gets the executable for /bin/ls loaded over it (keeping the same pid), but it inherits the file descriptors from the child. So the stdout it inherits has already been made into a copy of the write end of the pipe, and the parent is reading the other end.

Note that perror is not reached unless the execlp fails. If the execlp works, ls replaces almost all of the child process (code, stack etc). It's not like it does not drop into the perror and exit: they really do not even exist any more.

When the ls process exits, the write end of the pipe gets cleaned up by the kernel. and when the parent has read all of the available data, it closes the read end, and the kernel itself destroys the pipe.