COMS 3157 Advanced Programming

Recitation 10: Starfork

In this example, we will study the starfork program. You can obtain the skeleton code on CLAC:

git clone ~j-hui/cs3157-pub/examples/starfork

When you run make in here, it will build nine different version of starfork, named starfork-s1 through starfork-s9. You should try to first predict the output of each part before running the executable to validate (or disprove) your hypothesis. Keep in mind that some of these parts are actually unpredictable, so you may need to run them multiple times to see different behavior.

Note that parts 8 and 9 are optional; signals and signal handlers will not appear on exams. However, you may find working through these parts helpful for understanding signal handlers, which you will need to use for your labs.

Part 1

For starters, let’s make sure you understand the skeleton code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

void star(int numstar) {
    if (numstar >= 100)
        exit(1);

    char star = '*';

    if (numstar < 0) {
        numstar = -numstar;
        star = '@';
    }

    char line[100];

    line[numstar] = 0;

    while (0 <= --numstar)
        line[numstar] = star;

    printf("%s\n", line);
}

int main(int argc, char **argv) 
{
    assert(argc == 2);
    int n = atoi(argv[1]);

    for (int i = 1; i <= n; i++) {

        // You can enable each code block below by defining S1, S2, etc.
        // at the preprocessing stage using -D option in gcc. For example,
        //
        //     gcc -Wall -g -D S1 starfork.c && ./a.out 3 
        //
        // will run the program with the code in S1 block. 

        // BEGIN MOD BLOCK

        star(i);

        // END MOD BLOCK
    }
}

How does this code behave? How many arguments does this program expect, and what do those arguments do?

In subsequent parts, we will modify the “mod block”, the portion of the code between // BEGIN MOD BLOCK and // END MOD BLOCK.

Part 2

Change the mod block to:

star(i);
fork();

Run it with different command line arguments, 1, 2, 3, etc.

Part 3

What about the following modification?

star(i);
fork();
star(i);

Part 4

Let’s add some synchronization by having the parent process wait for its child. What would be the output if you change the modification block to the following?

star(i);
pid_t pid = fork();
if (pid == 0) { // Child process
    star(i);
    exit(0);
}
waitpid(pid, NULL, 0); // No status, no options

Part 5

Now what about this version?

star(i);
pid_t pid = fork();
if (pid > 0) { // Parent process
    waitpid(pid, NULL, 0); // No status, no options
    star(i);
    exit(0);
}

Part 6

Now let’s understand what exec() does. How would the following block behave?

star(i);
sleep(1);
char *a[] = { argv[0], argv[1], NULL };
execv(*a, a);
printf("%s\n", "A STAR IS BORN");
exit(0);

Part 7

Now let’s see if you really understood exec(). What would be the output for the following block when you run starfork with arguments 2, 10, and 50?

star(n);
pid_t pid = fork();
if (pid == 0) { // Child process
    char buf[100];
    sprintf(buf, "%d", 2 * n);
    char *a[] = { argv[0], buf, NULL };
    execv(*a, a);
}
waitpid(pid, NULL, 0); // No status, no options
star(n);
exit(0);

Some hints and guiding questions:

Part 8 (optional)

Let’s install some signal handlers to detect when stars die. We will use the supernova() function as our SIGCHLD handler, and use setup_supernova() to install it.

void supernova(int sig) {
    int wstatus;
    while (waitpid(-1, &wstatus, WNOHANG) > 0)
        for (int i = WEXITSTATUS(wstatus); i > 0; i--)
            star(-i);
}

void setup_supernova(int sa_restart) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = supernova;
    if (sa_restart)
        sa.sa_flags = SA_RESTART;
    sigaction(SIGCHLD, &sa, NULL);
}

In the beginning of our main() function, before the loop, we call setup_supernova(1) to install our supernova() handler (with SA_RESTART).

Then, in our mod block, we have the following code:

star(i);
pid_t pid = fork();
if (pid == 0) // Child process
    exit(i);

char c;
// Block until we can read a byte from stdin (fd=0)
read(0, &c, sizeof(char));

read() tells the parent process to block until it receives input from standard input; we can advance through each loop iteration by pressing enter. What is the output?

What happens if you type some extra characters before hitting enter? Keep in mind that your terminal will buffer characters before sending them to the standard input of the starfork program.

Part 9 (optional)

Now, we do the same thing as part 8, but we do not use the SA_RESTART flag by calling setup_supernova(0). The mod block is the same (aside from comments):

star(i);
pid_t pid = fork();
if (pid == 0) // Child process
    exit(i);

char c;
// Block until we can read a byte from stdin (fd=0)
// or we receive and handle a signal
read(0, &c, sizeof(char));

Then, SIGCHLD will interrupt the blocking read(). How does the behavior compare with the part 8?