COMS 4995 Advanced Systems Programming

Index of 2024-1/code/09

Parent directory
Makefile
connect2.c
counter.c

Makefile

CC=gcc
CFLAGS=-g -Wall
LDFLAGS=-pthread

executables = connect2 counter
objects = connect2.o counter.o 

.PHONY: default
default: $(executables)

$(executables):

$(objects):

.PHONY: clean
clean:
	rm -f *~ *.out $(objects) $(executables)

.PHONY: all
all: clean default

connect2.c

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

//////////////////////////////////////////////////////////////////////

int main(int argc, char **argv)
{
    int fd[2];
    pid_t pid1, pid2;

    // Split arguments ["cmd1", ..., "--", "cmd2", ...] into
    //                 ["cmd1", ...] and ["cmd2", ...]
    char **argv1 = argv + 1; // argv for the first command
    char **argv2;            // argv for the second command

    for (argv2 = argv1; *argv2; argv2++) {
        if (strcmp(*argv2, "--") == 0) {
            *argv2++ = NULL;
            break;
        }
    }
    if (*argv1 == NULL || *argv2 == NULL) {
        fprintf(stderr, "%s\n", "separate two commands with --");
        exit(1);
    }

//////////////////////////////////////////////////////////////////////
#ifdef DEBUG1
    fprintf(stderr, "%s", "argv1: ");
    while (*argv1)
        fprintf(stderr, "%s ", *argv1++);
    fprintf(stderr, "\n%s", "argv2: ");
    while (*argv2)
        fprintf(stderr, "%s ", *argv2++);
    fprintf(stderr, "\n");
    exit(0);
#endif
//////////////////////////////////////////////////////////////////////

    pipe(fd);

    if ((pid1 = fork()) == 0) {
        close(fd[0]);   // Close read end of pipe
        dup2(fd[1], 1); // Redirect stdout to write end of pipe
        close(fd[1]);   // stdout already writes to pipe, close spare fd
        execvp(*argv1, argv1);
        // Unreachable
    }

    if ((pid2 = fork()) == 0) {
        close(fd[1]);   // Close write end of pipe
        dup2(fd[0], 0); // Redirect stdin from read end of pipe
        close(fd[0]);   // stdin already reads from pipe, close spare fd
        execvp(*argv2, argv2);
        // Unreachable
    }

    // Parent does not need either end of the pipe
    close(fd[0]);
    close(fd[1]);

//////////////////////////////////////////////////////////////////////

    waitpid(pid1, NULL, 0);
    waitpid(pid2, NULL, 0);
    return 0;
}

counter.c

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>

#define LOOPS 1000

struct counter {
    sem_t sem;
    int cnt;
};

static struct counter *counter = NULL;

static void inc_loop() {
    for (int i = 0; i < LOOPS; i++) {
        sem_wait(&counter->sem);

        // Not an atomic operation, needs lock!
        // 1) Load counter->cnt into tmp
        // 2) Increment tmp
        // 3) Store tmp into counter->cnt
        counter->cnt++;

        sem_post(&counter->sem);
    }
}

int main(int argc, char **argv) {
    // Create a shared anonymous memory mapping, set global pointer to it
    counter = mmap(/*addr=*/NULL, sizeof(struct counter),
                   // Region is readable and writable
                   PROT_READ | PROT_WRITE,
                   // Want to share anonymous mapping with forked child
                   MAP_SHARED | MAP_ANON,
                   /*fd=*/-1,  // No associated file
                   /*offset=*/0);
    assert(counter != MAP_FAILED);

    // Mapping is already zero-initialized.
    assert(counter->cnt == 0);

    sem_init(&counter->sem, /*pshared=*/1, /*value=*/1);

    pid_t pid;
    if ((pid = fork()) == 0) {
        inc_loop();
        return 0;
    }

    inc_loop();
    waitpid(pid, NULL, 0);

    printf("Total count: %d, Expected: %d\n", counter->cnt, LOOPS * 2);

    sem_destroy(&counter->sem);
    assert(munmap(counter, sizeof(struct counter)) == 0);
    return 0;
}