Ancient CS 61 Content Warning!!!!!1!!!
This is not the current version of the class.
This site was automatically translated from a wiki. The translation may have introduced mistakes (and the content might have been wrong to begin with).

fork

When using fork, a typical skeleton will look like the below (if done in the main function). We will take this skeleton and add to it in each syscall section.

   int main(int argc, char *argv[]) {
       pid_t pid = fork();
       if (pid == -1) {
           perror(“Could not fork!\n”);
       } else if (pid == 0) { // child
           // do stuff in child process
       } else { // parent
           // do stuff in parent process
       }
   }

exec

argv[1]` would be the command program and the rest of `argv

would be the arguments for that command (e.g. in ls -a, ls is the program and -a is the argument). In this case, we are not taking arguments for the command being passed in, so the child's argv will not have additional arguments. Remember that argv still contains the program name, not only the arguments passed to it!

   int main(int argc, char *argv[]) {

       if (argc != 2) {        printf(“Usage: %s program-name”, argv[0]);        exit(1);        }

       pid_t pid = fork();
       if (pid == -1) {
           perror(“Could not fork!\n”);
       } else if (pid == 0) { // child

           char *child_argv[] = {argv[1], NULL};            execvp(argv[1], child_argv); // does not return on success            perror(“Couldn’t execute program!\n”);

       } else { // parent
           // do stuff in parent process
       }
   }

waitpid

We put waitpid in the parent process because it is the one waiting. We are also given the pid of the child process because it is returned by fork.

   int main(int argc, char *argv[]) {
       if (argc != 2) {
           printf(“Usage: %s program-name”, argv[0]);
           exit(1);
       }
       pid_t pid = fork();
       if (pid == -1) {
           perror(“Could not fork!\n”);
       } else if (pid == 0) { // child
           char *child_argv[] = {argv[1], NULL};
           execvp(argv[1], child_argv); // does not return on success
           perror(“Couldn’t execute program!\n”);
       } else { // parent

           waitpid(pid, NULL, 0);            printf(“done\n”);

       }
   }

dup2

STDIN_FILENO and STDOUT_FILENO are built-in constants that contain the file descriptors for stdin and stdout, respectively. After opening infile to fdin, dup2(fdin, STDIN_FILENO) will tell the system that whenever STDIN_FILENO is used, it is actually referencing infile, not stdin. This works similarly for

outfile`, except with `STDOUT_FILENO`. After copying the `fdin

and fdout file descriptors, they are no longer used anywhere else because their respective files are now handled by STDIN_FILENO and

STDOUT_FILENO` everywhere that we need them, so we `close(fdin)

and repeat with fdout.

   int main(int argc, char *argv[]) {

       if (argc != 4) {            printf(“Usage: %s program-name infile outfile”, argv[0]);

           exit(1);
       }
       pid_t pid = fork();
       if (pid == -1) {
           perror(“Could not fork!\n”);
       } else if (pid == 0) { // child

           int fdin = open(argv[2], O_RDONLY);            int fdout = open(argv[3], O_WRONLY|O_CREAT);            dup2(fdin, STDIN_FILENO); // copy fdin fd to stdin fd            dup2(fdout, STDOUT_FILENO); // copy fdout to stdout fd            // close now unused fds            close(fdin);            close(fdout);

           char *child_argv[] = {argv[1], NULL};
           execvp(argv[1], child_argv); // does not return on success
           perror(“Couldn’t execute program!\n”);
       } else { // parent
           waitpid(pid, NULL, 0);
           printf(“done\n”);
       }
   }

pipe

After creating the pipe using the file descriptor array pipefd, we want the write end of the pipe to be in the parent (so that the parent's stdin can be redirected or /written/ to the child) and the read of the pipe to be in the child (so that the child can /read/ the parent's output and treat it as its stdin). We close the respective unused end of the pipe in the parent and the child. In the child, we want whatever it is reading through the pipe to replace the stdin, so we use dup2 to copy the pipe read-end file descriptor to its stdin. In the parent, we want to take the text and send it through the pipe; we can use write because each end of the pipe is given by a file descriptor, so writing to a pipe is equivalent to writing to a file.

   int main(int argc, char *argv[]) {

       if (argc != 3) {            printf(“Usage: %s program-name text-to-pipe”, argv[0]);

           exit(1);
       }

       int pipefd[2];        pipe(pipefd); // creates the pipe

       pid_t pid = fork();
       if (pid == -1) {
           perror(“Could not fork!\n”);
       } else if (pid == 0) { // child

           close(pipefd[1]); // close unused write end (for child)            // make child’s stdin the pipe’s read end            dup2(pipefd[0], STDIN_FILENO);            close(pipefd[0]);

           char *child_argv[] = {argv[1], NULL};
           execvp(argv[1], child_argv); // does not return on success
           perror(“Couldn’t execute program!\n”);
       } else { // parent

           close(pipefd[0]); // close unused read end (for parent)            // write the given text to the pipe’s write end            write(pipefd[1], argv[2], strlen(argv[2]));            close(pipefd[1]);

           waitpid(pid, NULL, 0);
           printf(“done\n”);
       }
   }

Exercise: Process Management

  1. A

    B (child1)

    C

    D (child2)

    E

    7

    6, 1, 2
    or 6, 2, 1
    or 1, 6, 2

    3, 1, 2
    or 3, 2, 1
    or 2, 3, 1

    1, 2, 8, 9
    or 2, 1, 9, 8, etc.

    OR

    7

    6, 1, 2
    or 6, 2, 1
    or 1, 6, 2

    2

    3, 1

    1, 8, 9
    or 1, 9, 8

  2. Anything that doesn’t close the pipe’s write end will do it. Below we leave both ends of the pipe open in the shell. We could also enter just “3” in slot D.

    A B (child1) C D (child2) E
    7 6, 1, 2 3, 1, 2 8, 9
  3. Anything that doesn’t redirect the left-hand side’s standard output will do it. It is important that the read end of the pipe be properly redirected, or wc would block reading from the shell’s standard input.

    A

    B (child1)

    C

    D (child2)

    E

    7

    1, 2
    (anything without 6)

    3, 1, 2

    1, 2, 8, 9

  4. This happens when we execute the two sides of the pipe in series: first the left-hand side, then the right-hand side. Since the pipe contains 64KiB of buffering, this will often appear to work.

    A B (child1) C D (child2) E
    7 6, 1, 2 8 3, 1, 2 1, 2, 9
  5. Given these system calls, the only way to make this happen is to redirect the wrong ends of the pipe into stdin/stdout.

    A B (child1) C D (child2) E
    7 4, 1, 2 5, 1, 2 1, 2, 8, 9