
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
=========

 - Yuan An is away this week and so I've told his students to attend one of
   the other tutorials. As a result, you may have a few extra people in your
   tutorials this week.


Tutorial 8 Notes -- July 13
===========================

 We started systems programming a couple weeks ago and will continue on this
 topic until the end of the course. In particular, we've looked at process
 creation using fork(), process/program replacement using exec(), and simple
 inter-process communication using pipes. This week I'd like you to go over a
 few examples of these topics.

 (1) Fork

     int main()
     {
         int i, status;
         pid_t childpid, retval;

         for (i = 0 ; i < 4 ; i++)
             if ((childpid = fork()))
                 break;

         while(childpid != (retval = wait(&status)))
             if ((retval == -1) && (errno != EINTR))
                 break;

         fprintf(stderr, "I am process %d, my parent is %d\n",
                 getpid(), getppid());

         if (retval != -1 && WIFEXITED(status))
             fprintf(stderr, "Status from child: %d\n", WEXITSTATUS(status));

         exit(i);
     }

     Running this program should produce output of the form:

         I am process 5552, my parent is 5551
         I am process 5551, my parent is 5550
         Status from child: 4
         I am process 5550, my parent is 5549
         Status from child: 3
         I am process 5549, my parent is 5548
         Status from child: 2
         I am process 5548, my parent is 4813
         Status from child: 1

     Some points you can make:

      - You can ask your class how many processes are created when the program
        is run (5 processes total). The PIDs will be different each time.
      - You can also ask your class about the parent/child relationships of
        the processes that are created (i.e., the code creates a process
        chain; each process creates one child process until all processes are
        created).
      - Each forked child waits for its own child to complete before
        outputting its message. Messages appear in reverse order of process
        creation.
      - If a child process terminated normally (tested with WIFEXITED), we can
        retrieve its exit status using WEXITSTATUS. (In this example the exit
        status depends on i, to illustrate different status values.)

 (2) Exec

     int main(int argc, char *argv[])
     {
         pid_t childpid;
         int status;

         if ((childpid = fork()) == -1) {
             perror("fork");
             exit(1);
         }
         else if (childpid == 0) {
             if (execvp(argv[1], &argv[1]) < 0) {
                 perror("exec");
                 exit(1);
             }
         }
         else
             while(childpid != wait(&status))
                 if ((childpid == -1) && (errno != EINTR))
                     break;

         exit(0);
     }

     Some points you can make:

      - This program uses one of the exec() family of function calls (in this
        case execvp()) to execute a command and its arguments.
      - The original (parent) process creates a child process and then waits
        for the child to terminate.
      - The child process uses execvp() to run the command at argv[1].
      - The argument list is passed as the second argument of execvp().
        (Note that we skip argv[0] which would be the name of the original
        program.)

 (3) Pipes

     #define MAX     256

     int main()
     {
         int l, pid, retval, status;
         int fd[2];
         char buf[MAX];

         retval = pipe(fd);
         if (retval < 0) {
             perror("pipe");
             exit(1);
         }

         pid = fork();
         if (pid == 0) {
             close(fd[0]);
             sprintf(buf, "this is a test");
             l = strlen(buf);
             printf("Child sending %d bytes to pipe: %s\n", l, buf);
             write(fd[1], buf, l);
         }
         else {
             close(fd[1]);
             l = read(fd[0], buf, MAX);
             buf[l] = '\0';
             printf("Parent read %d bytes from pipe: %s\n", l, buf);
             wait(&status);
         }

         exit(0);
     }

     Running this program should produce output of the form:

         Child sending 14 bytes to pipe: this is a test
         Parent read 14 bytes from pipe: this is a test

     Here are some notes you can make:

       - The parent process creates a child process and a pipe is established
         between the two processes.
       - The communication flow is from the child process (which writes to the
         pipe) to the parent process (which reads from the pipe).
       - Once the pipe is established, reading/writing to the pipe is similar
         to reading/writing to a file.
       - The actual reading and writing is done using the low-level functions
         read() and write(). Note that these functions operate on file
         descriptors, rather than file pointers (compare with fread() and
         fwrite()).

