Slide 1: Lecture 05: fork and Understanding execvp

Principles of Computer Systems

Winter 2020

Stanford University

Computer Science Department

Instructors: Chris Gregg and

                            Nick Troccoli

 

Reading: Bryant & O'Hallaron,  Chapters 10 and 8

PDF of this presentation

Slide 2: CS110 Topic 2: How can our programs create and interact with other programs?

Slide 3: Learning About Processes

Creating processes and running other programs

Inter-process communication

Signals

Race Conditions

Today

1/22

1/27

1/29

Slide 4: Today's Learning Goals

Slide 5: Plan For Today

Slide 6:
fork()

pid_t pidOrZero = fork();
// both parent and child run code here onwards
printf("This is printed by two processes.\n");

Slide 7:
fork()

What happens to variables and addresses?

// fork-copy.c
int main(int argc, char *argv[]) {
    char str[128];
    strcpy(str, "Hello");
    printf("str's address is %p\n", str);
    
    pid_t pid = fork();
    
    if (pid == 0) {
        // The child should modify str
        printf("I am the child. str's address is %p\n", str);
        strcpy(str, "Howdy");
        printf("I am the child and I changed str to %s. str's address is still %p\n", str, str);
    } else {
        // The parent should sleep and print out str
        printf("I am the parent. str's address is %p\n", str);
        printf("I am the parent, and I'm going to sleep for 2 seconds.\n");
        sleep(2);
        printf("I am the parent. I just woke up. str's address is %p, and its value is %s\n", str, str);
    }

    return 0;
}

Slide 8:
fork()

How can the parent and child use the same address to store different data?

$ ./fork-copy
str's address is 0x7ffc8cfa9990
I am the parent. str's address is 0x7ffc8cfa9990
I am the parent, and I'm going to sleep for 2 seconds.
I am the child. str's address is 0x7ffc8cfa9990
I am the child and I changed str to Howdy. str's address is still 0x7ffc8cfa9990
I am the parent. I just woke up. str's address is 0x7ffc8cfa9990, and its value is Hello

Slide 9:
fork()

Isn't it expensive to make copies of all memory when forking?

$ ./fork-copy
str's address is 0x7ffc8cfa9990
I am the parent. str's address is 0x7ffc8cfa9990
I am the parent, and I'm going to sleep for 2 seconds.
I am the child. str's address is 0x7ffc8cfa9990
I am the child and I changed str to Howdy. str's address is still 0x7ffc8cfa9990
I am the parent. I just woke up. str's address is 0x7ffc8cfa9990, and its value is Hello

Slide 10: Why is fork useful?

Slide 11: Practice: fork()

int main(int argc, char *argv[]) {
    // Initialize the random number with a "seed value"
    // this seed state is used to generate future random numbers
    srandom(time(NULL));

    printf("I'm unique and just get printed once.\n");
    pid_t pidOrZero = fork();
    exitIf(pidOrZero == -1, kForkFailure, stderr, "Call to fork failed... aborting.\n");

    // force exactly one of the two to sleep (why exactly one?)
    bool isParent = pidOrZero != 0;
    int result = random() % 2;
    if ((result == 0) == isParent) {
        sleep(1); 
    }
  
    printf("I get printed twice (this one is being printed from the %s).\n", isParent  ? "parent" : "child");
    return 0;
}

Slide 12: Practice: fork()

int main(int argc, char *argv[]) {
    // Initialize the random number with a "seed value"
    // this seed state is used to generate future random numbers
    srandom(time(NULL));

    printf("I'm unique and just get printed once.\n");
    pid_t pidOrZero = fork();
    exitIf(pidOrZero == -1, kForkFailure, stderr, "Call to fork failed... aborting.\n");

    // force exactly one of the two to sleep (why exactly one?)
    bool isParent = pidOrZero != 0;
    int result = random() % 2;
    if ((result == 0) == isParent) {
        sleep(1); 
    }
  
    printf("I get printed twice (this one is being printed from the %s).\n", isParent  ? "parent" : "child");
    return 0;
}

Key Idea: all state is copied from the parent to the child, even the random number generator seed!  ​Both the parent and child will get the same return value from random().

Slide 13: Practice: fork()

int main(int argc, char *argv[]) {
    printf("Starting the program\n");
    pid_t pidOrZero1 = fork();
    pid_t pidOrZero2 = fork();
    
    if (pidOrZero1 != 0 && pidOrZero2 != 0) {
        printf("Hello\n");
    }

    if (pidOrZero2 != 0) {
        printf("Hi there\n");
    }

    return 0;
}

How many processes run in total?

a) 1        b) 2        c) 3        d) 4

 

How many times is "Hello" printed?

a) 1        b) 2        c) 3        d) 4

 

How many times is "Hi there" printed?

a) 1        b) 2        c) 3        d) 4

Slide 14: Plan For Today

Slide 15:
waitpid()

A function that a parent can call to wait for its child to exit:

Slide 16:
waitpid()

We can use WIFEXITED and WEXITSTATUS (among others) to extract info from the status.

(full program, with error checking, is right here)

// waitpid.c
int main(int argc, char *argv[]) {
  printf("Before.\n");
  pid_t pidOrZero = fork();
  printf("After.\n");
  if (pidOrZero == 0) {
    printf("I'm the child, and the parent will wait up for me.\n");
    return 110;
  } else {
    int status;
    int result = waitpid(pidOrZero, &status, 0);

    if (WIFEXITED(status)) {
      printf("Child exited with status %d.\n", WEXITSTATUS(status));
    } else {
      printf("Child terminated abnormally.\n");
    }
    return 0;
  }
}

The output will be the same every time this program runs

$ ./waitpid 
Before.
After.
After.
I am the child, and the parent will wait up for me.
Child exited with status 110.
$

Slide 17:
waitpid()

A parent process should always wait on its children processes.

Slide 18:
waitpid()

Slide 19: Make sure to reap your zombie children.

(Wait what?)

Slide 20: Plan For Today

Slide 21: Announcements

Slide 22: Assignment 2: The Unix v6 Filesystem

/**
 * Reads the specified sector (e.g. block) from the disk.  Returns the number of bytes read,
 * or -1 on error.
 */
int diskimg_readsector(int fd, int sectorNum, void *buf);

Slide 23: Assignment 2: The Unix v6 Filesystem

One function that can be tricky to write is the following:

/**
 * Gets the location of the specified file block of the specified inode.
 * Returns the disk block number on success, -1 on error.
 */
int inode_indexlookup(struct unixfilesystem *fs, struct inode *inp, int blockNum);

Slide 24: Assignment 2: The Unix v6 Filesystem

  1. Determine if the file is large or not
  2. If it isn't large, you know you only have direct addressing.
  3. If it is large (this file is), then you have indirect addressing.
  4. The 302nd block is going to fall into the second indirect block, because each block has 256 block numbers (each block number is an unsigned short, or a uint16_t).
  5. You, therefore, need to use diskimg_readsector to read the sector listed in the 2nd block number (which is in the inode struct), then extract the (302 % 256)th short from that block, and return the value you find there.
  6. If the block number you were looking for happened to fall into the 8th inode block, then you                        would have a further level of indirection for a doubly-indirect lookup.

Slide 25: Assignment 2: The Unix v6 Filesystem

For the assignment, you will also have to search through directories to locate a particular file.

Slide 26: Assignment 2: The Unix v6 Filesystem

Slide 27: Break / Mid-Lecture Checkin

We now know the answers to the following questions:

Slide 28: Plan For Today

A parent can call fork multiple times, but must reap all the child processes.

 

Demo: Let's see how we might use this (reap-as-they-exit.c)

Slide 29: Waiting On Multiple Children

What if we want to wait for children in the order in which they were created?

Check out the abbreviated program below (full program with error checking right here):

// reap-in-fork-order.c
int main(int argc, char *argv[]) {
  pid_t children[8];
  for (size_t i = 0; i < 8; i++) {
    if ((children[i] = fork()) == 0) exit(110 + i);
  }
  for (size_t i = 0; i < 8; i++) {
    int status;
    pid_t pid = waitpid(children[i], &status, 0);
    assert(pid == children[i]);
    assert(WIFEXITED(status) && (WEXITSTATUS(status) == (110 + i)));
    printf("Child with pid %d accounted for (return status of %d).\n", 
           children[i], WEXITSTATUS(status));
  }
  return 0;
}

Slide 30: Waiting On Multiple Children

Slide 31: Waiting On Multiple Children

myth60$ ./reap-as-they-exit 
Child with pid 4689 accounted for (return status of 110).
Child with pid 4690 accounted for (return status of 111).
Child with pid 4691 accounted for (return status of 112).
Child with pid 4692 accounted for (return status of 113).
Child with pid 4693 accounted for (return status of 114).
Child with pid 4694 accounted for (return status of 115).
Child with pid 4695 accounted for (return status of 116).
Child with pid 4696 accounted for (return status of 117).
myth60$

The most common use for fork is not to spawn multiple processes to split up work, but instead to run a completely separate program under your control and communicate with it.

Slide 32: execvp()

execvp is a function that lets us run another program in the current process.

 

 

It runs the specified program executable, completely cannibalizing the current process.

Slide 33: execvp()

Slide 34: Plan For Today

We are going to use execvp to implement our own shell program!

Demo: first-shell.c

Slide 35: execvp()

static int mysystem(const char *command) {
  pid_t pid = fork();
  if (pid == 0) {
    char *arguments[] = {"/bin/sh", "-c", (char *) command, NULL};
    execvp(arguments[0], arguments);
    printf("Failed to invoke /bin/sh to execute the supplied command.");
    exit(0);
  }
  int status;
  waitpid(pid, &status, 0);
  return WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status);
}

Slide 36: mysystem()

Slide 37: Lecture Recap


Next time: inter-process communication