Many students have reported problems with zombies in Lab 8.
I did not think it would be this hard to prevent zombies, so this is unintended, partly due to last minute change of platform (from Linux to Solaris), and confusion due to the skeleton code given.
I would rather you spend time handling the pipes properly (remember to close every unused ends!) than worrying about zombies.
So, here is a way to remove zombies in Lab 8.
replace the line
waitpid(pid, NULL, NULL)
for (i = 0; i < num_of_cmds; i++) waitpid(-1, NULL, NULL);
This line ensures that the parent will wait as many times as child processes forked before returning to the prompt.
Next, I will describe several other ways that does not work or tricky to get it to work.
One solution that you might have done is to move the waitpid() statement into the for loop. So, in effect, bush is calling fork(), wait(), fork(), wait().. The impact of this change is that, the second command in the pipeline will not start until after the first command finishes execution. This is problematic, because the first command might not exit or might wait for the second command to read from the pipe (e.g., when the pipe is full). Example of a pipe that can cause trouble for this solution is “yes | head”.
Another solution is to wait() asynchronously with signals like what you have done in Lab 4. Using signal is not strictly necessary in Lab 8 since we do not support background processes. But it is not wrong to use signal either. The problem, however, comes from the subtle different semantic of signal() on Linux and Solaris. While on Linux, once you install a signal, it is there to stay. On Solaris, however, after the signal handler is called, the signal handler is reset back to the default handler. To make the signal handler you installed persistent, you have to reinstall it again and again by calling signal() within the signal handler. So if you have used this approach, either use sigaction() or call signal() from within the signal handler to reinstall the handler.