Laboratory Exercises For Computer Science 195

Laboratory Exercise on Concurrency in [Unix-based/GNU] C

Goals: This laboratory exercise provides some practice with beginning elements of process control in Unix-based C programming, using the GNU C compiler on MathLAN's PC/Linux computers.

Preparation

  1. Review sample programs 1-3 in An Introduction to Concurrency in Unix-based [GNU] C Through Annotated Examples, as discussed in class.

Experiments with fork

  1. Copy ~walker/c/concurrency-linux/fork-1.c to your account, compile it with gcc and run it a few times -- waiting a few moments between each run. How does the output differ for each run?

  2. Add the statement sleep(10) between the two printf statements for both the parent and the child process. (This will make each process pause 10 seconds after the first printf before executing the second printf.)
    1. Again, compile and run the program in a terminal window.
    2. Open a second terminal window. Now run the program in the first terminal window. Then while it is running, type ps -ef in the second terminal window. This second command gives a listing of all processes running on your workstation. Toward the bottom of the listing, you should see entries for both of the fork-1 processes.

  3. In turn, change each sleep(10) statement to exit(1), and repeat step 2. In other words, observe the process listing in each case when one process exits abnormally while the other executes naturally.
    1. If one process halts abnormally, does the other continue?
    2. Does it matter in each case if exit(1) is replaced by exit(0) -- for normal termination.

  4. Insert the statement sleep(50) in only the child process, between the two printf statements. Be sure no other sleep or exit statements occur in the program (except the exit(0) at the very end of the program).

    Now, recompile and run the program.

    1. After a few seconds, again use ps -ef in the second terminal window to check the status of both processes.
    2. When the prompt reappears in the original terminal window, try executing some other commands (eg., ps or ls -l or cat fork-1.c). Describe the results from these other commands. What happened/happens to the child process fork-1 ?
    3. What happens if the sleep(50) statement is placed only in the parent process? How is this different from the sleep(50) in the child process?
    4. Does this experiment suggest any potential risks of spawning arbitrary processes with fork? Explain.

  5. Run the program containing sleep(50) (either for the parent or the child) again. Now use ps -ef in the second terminal window to find the process id's of a long-standing process. Then use the statement
    kill <process-id>
    to terminate the execution of this process.

    Note that the normal termination of a process may be accomplished with
    kill <process-id>.
    In this case, a signal is sent to the process to stop the process. However, in some cases, more drastic action is needed, and the statement
    kill -KILL <process-id>
    directs the operating system to do whatever is necessary to make the process terminate.

Experiments with pipe

  1. Copy ~walker/c/concurrency-linux/fork-2.c to your account, compile it with gcc and run it a few times -- waiting a few moments between each run.

  2. In the statement write (fd[1], ...) change the 22 to 10. Then recompile and rerun. How does this affect the running of the program? Briefly explain what you see.

    Now change the 10 (formerly 22) to 30 and rerun. Again, describe the effect and explain why this happens.

  3. In #define change MAX to 15, recompile and rerun. Again describe and explain the result.

  4. With MAX set to 15, change the reading/printing section of code for the child to:
    
    read(fd[0], line, MAX);
    printf ("The string received is '%s'\n", line);
    read(fd[0], line, MAX);
    printf ("The string received is '%s'\n", line);
    
    That is, perform the reading and printing twice. Rerun, describe, and explain what happens.

  5. With MAX set to 40, change the writing section of code for the parent to:
    
    write (fd[1], "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26);
    write (fd[1], "abcdefghijklmnopqrstuvwxyz", 26);
    
    At the same time, restore the code for the child to contain just one read of line with a single corresponding printf. Rerun, describe, and explain the result.

Local and Global Variables

  1. Copy ~walker/c/concurrency-linux/fork-3.c to your account, compile it, and run it. Why do you think the variable value is used in the write statement rather than giving the number 20 directly?

  2. Move the assignments
    
    data_g = 10;         /* change data elements */
    data_l = 12;
    
    from the beginning of the code for the parent process to the beginning of the child's code segment. Recompile and rerun. Compare this output with that from the previous step (step 12), and explain any differences.

  3. Remove the wait statement from the parent. Then recompile and rerun several times. Does the order of the output ever change? Explain why or why not.
Work to be turned in: Note: For most steps, it is expected that the answer will be short.


This document is available on the World Wide Web as

     http://www.math.grin.edu/~walker/courses/195.fa01/lab-concurrency.html

created September 20, 1998
last revised October 11, 2001
Valid HTML 3.2!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.