Hello everyone, Here are the tutorial notes for this week. Remember, feel free to contact me if you have any questions about the material. (You don't have to email me after your tutorial this week, but if there are any problems please let me know about them.) Reminders ========= - Assignment 3 is available online and is due on July 28 at noon. - Please announce to your students that the tutorial examples from last week and this week will be posted to the course webpage (on the lectures and tutorials page) after tomorrow night's lecture. Tutorial 9 Notes -- July 20 =========================== (1) Assignment 3 Please read over the assignment specification and the clarifications. I expect that you'll get some questions about it. The due date for this assignment is next week so the class has less time for this assignment than the previous assignments. I have, however, given them some sample code to play with, and I have divided up the assignment into sections that should help them progressively write their programs. This week I'd like you to go over two more examples that use fork(), pipe(), and signals. (2) Process fan int main() { int i, t, status; int fd[5][2]; pid_t pid; srand(time(NULL)); /* seed randomizer */ printf("Parent creating %d child processes...\n", NUM_PROCS); for (i = 0 ; i < 5 ; i++) { pipe(fd[i]); pid = fork(); if (pid == 0) { /* child */ close(fd[i][1]); read(fd[i][0], &t, sizeof(int)); printf("PID=%d: sleeping for %d seconds\n", getpid(), t); sleep(t); printf("PID=%d: done\n", getpid()); exit(0); } else { /* parent */ close(fd[i][0]); t = rand() % 13 + 3; write(fd[i][1], &t, sizeof(int)); } } for (i = 0 ; i < 5 ; i++) /* wait for children to finish */ wait(&status); exit(0); } In this example the parent creates a "fan" of 5 child processes and establishes a pipe between the parent and each child. Once a child is created the parent generates a random number between 3 and 15, and sends this value to the child via the pipe. The child process, once created, identifies itself, reads the integer value from its pipe, and sleeps for this length of time (in seconds). The child process then displays a message that it has completed its work, and exits. For instance, we may have the following output: Parent creating 5 child processes... PID=6368: sleeping for 15 seconds PID=6369: sleeping for 8 seconds PID=6370: sleeping for 7 seconds PID=6371: sleeping for 13 seconds PID=6372: sleeping for 6 seconds PID=6372: done PID=6370: done PID=6369: done PID=6371: done PID=6368: done Here are some points you can make: - In the code I'm using an array fd[5][2] for the file descriptors of all the pipes, but since the parent only accesses a pipe right after it creates the child process I could have just used fd[2] and overwritten the values each time through the first for loop. - Mention that the sleep() function can be used to put a process to sleep for a specified length of time. After the time has elapsed, the process will be woken up. You can also mention that sleep() might be useful for testing the first half of A3, since it ensures that a process stays around for a given period of time before terminating. - There is a lack of error checking in this example (and many of the other ones we've presented). Stress to your students that they should always be testing the return values of calls like fork() and pipe(), and handling errors as appropriate. (3) Signals int root; void catch_signal(int signo) { fprintf(stderr, "Process PID=%d exiting...\n", getpid()); if (getpid() != root) kill(getppid(), SIGINT); exit(0); } int main() { int i; pid_t pid; struct sigaction act; act.sa_handler = catch_signal; sigemptyset(&act.sa_mask); act.sa_flags = 0; root = getpid(); if (sigaction(SIGINT, &act, NULL) == -1) exit(1); for (i = 0 ; i < 5 ; i++) { pid = fork(); if (pid != 0) break; } printf("Process PID=%d, PPID=%d...\n", getpid(), getppid()); for ( ; ; ); /* loop forever */ return(0); } In this example a chain of 6 processes is created: each process creates one child process until we have 6 processes in total. After a process has been created, it identifies itself (with its PID and PPID) and then enters an infinite loop. Before the process creation has started, however, the original parent process sets up a signal handler to catch the SIGINT signal. After each fork(), subsequent child processes also inherit this signal handler. When a SIGINT is sent to any process the function catch_signal() is executed. This function has the effect of causing the process to exit, but not before it sends a SIGINT signal to its immediate parent. Thus, this causes a cascade effect as each process will in turn also execute its signal handler, send a signal to its parent (up to the root), and exit. For instance, we might have the following output: Process PID=6475, PPID=6173... Process PID=6476, PPID=6475... Process PID=6477, PPID=6476... Process PID=6478, PPID=6477... Process PID=6480, PPID=6479... Process PID=6479, PPID=6478... If we send a SIGINT to the last process in the chain with the command: kill -INT 6480 then we might have the following output: Process PID=6480 exiting... Process PID=6479 exiting... Process PID=6478 exiting... Process PID=6477 exiting... Process PID=6476 exiting... Process PID=6475 exiting... Similarly, if we ran the program again and had the output: Process PID=6510, PPID=6509... Process PID=6509, PPID=6508... Process PID=6508, PPID=6507... Process PID=6507, PPID=6506... Process PID=6506, PPID=6505... Process PID=6505, PPID=6173... we could instead send a signal to a different process in the chain, e.g., kill -INT 6507 In this case, only those processes from that point to the "root" of the chain would terminate, e.g., Process PID=6507 exiting... Process PID=6506 exiting... Process PID=6505 exiting... At this point, we should still clean up the remaining processes, for instance by sending a SIGINT signal to the process at the end of the chain, or by using killall. This is probably a good time to remind your students about being careful when using fork(). They should ensure that they kill all processes that they created, especially those processes that don't terminate automatically for some reason. You can point out that the command "killall" is useful for this task since a process can be specified by its name. For instance, if the name of the program in the above example was "sigexit" then we could have used killall sigexit to clean up all the remaining processes that didn't terminate after the second kill command.