CDS LAB 7 2007: FORK, ZOMBIES AND PIPE FORK ======================================================================== * Modify the 1s program to obtain and printout the PID of both the parent and child process after the fork() call. Replace: printf("After fork %d\n", fork_pid); with: printf("After fork, fork_pid=%d, PID=%d\n", fork_pid, getpid()); * Modify the above code to print out different messages from the parent and child after the fork. Replace the above with something like: if (fork_pid==0) printf("child: After fork, fork_pid=%d, pid=%d, ppid=%d\n", fork_pid, getpid(), getppid()); else printf("parent: After fork, fork_pid=%d, pid=%d\n", fork_pid, * first program writing to MY_FORK_FILE.txt, produces output like: Hello from main After fork 0 Hello from main After fork 28951 The problem is that fprintf() does not write immediately into the file; it gets stored in an IO buffer. This buffer as well as the file descriptors is replicated during fork(). Hence both the parent and child write out "Hello from main", even though only the parent actually executed the first fprintf(). After inserting a fflush() before the fork(), you obtain correct output, as the buffer is flushed. ======================================================================== WAIT ======================================================================== * Using the wait() system call, force the master to wait until the child process has completed until it prints out 2nd Hello from Parent. add (as `man wait' suggests): #include add the local variables: int status; int wpid; replace: elp_time = get_elapsed_time(); printf("2nd Hello from Parent %14.6lf\n",elp_time); with: wpid = wait(&status); elp_time = get_elapsed_time(); printf("2nd Hello from Parent wpid=%d exited=%d %14.6lf\n", wpid, WIFEXITED(status), elp_time); Note: WIFEXITED() is also mentioned in `man wait'. If you are not interested in finding out about the pid of the waited-on processor or status information, you can just use the call: wait(NULL); You should see output verifying the parent has waited, e.g. ... Good bye from Child 5.014453 2nd Hello from Parent wpid=29026 exited=1 5.014589 * Modify the above code so that the master creates 2 child processes and waits for both children to complete before exiting itself (still use wait() and not wait_pid()). Program up until `fork_pid = fork();' is unchanged. From there, the parent must fork a second child and do 2 wait calls. if (fork_pid == 0) { elp_time = get_elapsed_time(); printf("1st Hello from Child1 %14.6lf\n", elp_time); sleep(SLEEP_TIME); elp_time = get_elapsed_time(); printf("2nd Hello from Child1 %14.6lf\n", elp_time); } else { fork_pid = fork(); if (fork_pid == 0) { elp_time = get_elapsed_time(); printf("1st Hello from Child2 %14.6lf\n", elp_time); sleep(SLEEP_TIME); elp_time = get_elapsed_time(); printf("2nd Hello from Child2 %14.6lf\n", elp_time); } else { int status; int wpid; elp_time = get_elapsed_time(); printf("1st Hello from Parent %14.6lf\n", elp_time); wpid = wait(&status); elp_time = get_elapsed_time(); printf("2nd Hello from Parent wpid=%d exited=%d %14.6lf\n", wpid, WIFEXITED(status), elp_time); wpid = wait(&status); elp_time = get_elapsed_time(); printf("3nd Hello from Parent wpid=%d exited=%d %14.6lf\n", wpid, WIFEXITED(status), elp_time); } } elp_time = get_elapsed_time(); printf("Good bye from %s %14.6lf\n", fork_pid==0? "Child": "Parent", elp_time); return 0; } ======================================================================== ZOMBIES ======================================================================== * (with the child sleeping for 4 secs, and the parent sleeping for only 2) Explain why the values printed out by the child process before and after the sleep(4) statement are different. The output will be something like: Hello from main Parent PID of Child before sleep 29121 Hello from Parent Parent PID of Child after sleep 1 When the parent dies, the child processes parent becomes the init process (PID=1). * A parent who does not care about the termination status of a child process can avoid the creation of a zombied process by using fork() twice. Explain how this is done. Solution (a). The child process forks a 'grandchild', which performs the task of the original child. If we then let the child terminate, init becomes the parent of the grandchild and we have broken the tie between parent and grandchild (problem: the first child is still a `zombie', so we haven't saved our process tables!) Solution (b). The parent forks two children, the first doing the task of the original parent, the second doing the task of the original parent child. The parent then exits. Both task now have init as their parent, and hence will not become zombies. i.e. #include #include int main(void) { printf("Hello from main PID=%d\n", getpid()); if (fork() == 0){ /* this is the 1ST CHILD, acting as PARENT */ printf("Hello from `Parent' \n"); printf("Parent PID of `Parent' = %d\n", getppid()); sleep(2); } else if (fork() == 0) { /* this is THE 2ND CHILD, acting as CHILD */ printf("Parent PID of Child before sleep %d\n", getppid()); sleep(4); printf("Parent PID of Child after sleep %d\n", getppid()); } return 0; } ======================================================================== EXEC ======================================================================== * Modify the [1st example] so that it works when "/bin/ls" is replaced with "ls". Add the "-r" option to the argument list. From `man exec': execlp() ... will duplicate the actions of the shell in searching for an executable file ... Hence we replace the exec() call with: status = execlp("ls", "ls", "-l", "-t", "-r", (char *) NULL); * Modify the [1st example] so that instead of executing ls the child process picks up an executable (residing in your current working directory) from [a program reading n from argv[1] and printing out the sum of 1..n]. Compile the program to named executable - eg "sumn"; then replace the exec() call in the above program to be: status = execl("./sumn", "sumn", "5", (char *) NULL); This executes "./sumn" with argv[0] = "sumn" and argv[1] = "5". ======================================================================== PIPE ======================================================================== * Compile and run the [1st program]. In both cases what is the value reported by nbytes? Modify the code to investigate what happens if we read less data from the pipe than was initially written. * Compile and run the [3rd program]. You will find that it works correctly for an input value of N = 1000, but there appears to be a problem when N = 20000. What is the problem and how can you fix it? The program hangs when N>20000. This is because the pipe has an internal buffer of somewhere around sizeof(int)*20000. You cannot put more data into the pipe that this without first removing some. The problem is that both parent and child are writing to their pipes at the same time! Reverse the order for read() and write() one then it works fine for all N. * Write a program that establishes a ring of N processes linked via pipes.... Keep working on it, surely seeing a solution now would spoil the fun! * The [4th program] illustrates how pipes can be used with fork() and exec(): Explain what the code is doing. The code does "ls -l -r -t | wc -l" dup2() duplicates a file descriptor. On the parent the standard output becomes the write pipe, i.e. writing to stdout has the same effect of writing to the write pipe. On the child standard in becomes the read pipe. These file descriptors are maintained after the execlp() call (unlike the pile). Note: this is how the Unix pipe operator ("|') is implemented.