diff --git a/A1/src.zip b/A1/src.zip new file mode 100644 index 0000000..5eb9a4c Binary files /dev/null and b/A1/src.zip differ diff --git a/A2/Makefile b/A2/Makefile new file mode 100644 index 0000000..e10c2f4 --- /dev/null +++ b/A2/Makefile @@ -0,0 +1,23 @@ +CC=gcc +CFLAGS=-g -Wall -Wextra -pedantic -std=gnu99 -pthread +EXAMPLES=fibs fauxgrep fauxgrep-mt fhistogram fhistogram-mt + +.PHONY: all test clean ../src.zip + +all: $(TESTS) $(EXAMPLES) + +job_queue.o: job_queue.c job_queue.h + $(CC) -c job_queue.c $(CFLAGS) + +%: %.c job_queue.o + $(CC) -o $@ $^ $(CFLAGS) + +test: $(TESTS) + @set e; for test in $(TESTS); do echo ./$$test; ./$$test; done + +clean: + rm -rf $(TESTS) $(EXAMPLES) *.o core + +../src.zip: + make clean + cd .. && zip src.zip -r src diff --git a/A2/fauxgrep-mt.c b/A2/fauxgrep-mt.c new file mode 100644 index 0000000..25b47ce --- /dev/null +++ b/A2/fauxgrep-mt.c @@ -0,0 +1,87 @@ +// Setting _DEFAULT_SOURCE is necessary to activate visibility of +// certain header file contents on GNU/Linux systems. +#define _DEFAULT_SOURCE + +#include +#include +#include +#include + +#include +#include +#include + +// err.h contains various nonstandard BSD extensions, but they are +// very handy. +#include + +#include + +#include "job_queue.h" + +int main(int argc, char * const *argv) { + if (argc < 2) { + err(1, "usage: [-n INT] STRING paths..."); + exit(1); + } + + int num_threads = 1; + char const *needle = argv[1]; + char * const *paths = &argv[2]; + + + if (argc > 3 && strcmp(argv[1], "-n") == 0) { + // Since atoi() simply returns zero on syntax errors, we cannot + // distinguish between the user entering a zero, or some + // non-numeric garbage. In fact, we cannot even tell whether the + // given option is suffixed by garbage, i.e. '123foo' returns + // '123'. A more robust solution would use strtol(), but its + // interface is more complicated, so here we are. + num_threads = atoi(argv[2]); + + if (num_threads < 1) { + err(1, "invalid thread count: %s", argv[2]); + } + + needle = argv[3]; + paths = &argv[4]; + + } else { + needle = argv[1]; + paths = &argv[2]; + } + + assert(0); // Initialise the job queue and some worker threads here. + + // FTS_LOGICAL = follow symbolic links + // FTS_NOCHDIR = do not change the working directory of the process + // + // (These are not particularly important distinctions for our simple + // uses.) + int fts_options = FTS_LOGICAL | FTS_NOCHDIR; + + FTS *ftsp; + if ((ftsp = fts_open(paths, fts_options, NULL)) == NULL) { + err(1, "fts_open() failed"); + return -1; + } + + FTSENT *p; + while ((p = fts_read(ftsp)) != NULL) { + switch (p->fts_info) { + case FTS_D: + break; + case FTS_F: + assert(0); // Process the file p->fts_path, somehow. + break; + default: + break; + } + } + + fts_close(ftsp); + + assert(0); // Shut down the job queue and the worker threads here. + + return 0; +} diff --git a/A2/fauxgrep.c b/A2/fauxgrep.c new file mode 100644 index 0000000..80eedc8 --- /dev/null +++ b/A2/fauxgrep.c @@ -0,0 +1,82 @@ +// Setting _DEFAULT_SOURCE is necessary to activate visibility of +// certain header file contents on GNU/Linux systems. +#define _DEFAULT_SOURCE + +#include +#include +#include +#include + +#include +#include +#include + +// err.h contains various nonstandard BSD extensions, but they are +// very handy. +#include + +int fauxgrep_file(char const *needle, char const *path) { + FILE *f = fopen(path, "r"); + + if (f == NULL) { + warn("failed to open %s", path); + return -1; + } + + char *line = NULL; + size_t linelen = 0; + int lineno = 1; + + while (getline(&line, &linelen, f) != -1) { + if (strstr(line, needle) != NULL) { + printf("%s:%d: %s", path, lineno, line); + } + + lineno++; + } + + free(line); + fclose(f); + + return 0; +} + +int main(int argc, char * const *argv) { + if (argc < 2) { + err(1, "usage: STRING paths..."); + exit(1); + } + + char const *needle = argv[1]; + char * const *paths = &argv[2]; + + // FTS_LOGICAL = follow symbolic links + // FTS_NOCHDIR = do not change the working directory of the process + // + // (These are not particularly important distinctions for our simple + // uses.) + int fts_options = FTS_LOGICAL | FTS_NOCHDIR; + + FTS *ftsp; + if ((ftsp = fts_open(paths, fts_options, NULL)) == NULL) { + err(1, "fts_open() failed"); + return -1; + } + + FTSENT *p; + while ((p = fts_read(ftsp)) != NULL) { + switch (p->fts_info) { + case FTS_D: + break; + case FTS_F: + fauxgrep_file(needle, p->fts_path); + break; + default: + break; + } + } + + fts_close(ftsp); + + return 0; +} diff --git a/A2/fhistogram-mt.c b/A2/fhistogram-mt.c new file mode 100644 index 0000000..b6b4537 --- /dev/null +++ b/A2/fhistogram-mt.c @@ -0,0 +1,87 @@ +// Setting _DEFAULT_SOURCE is necessary to activate visibility of +// certain header file contents on GNU/Linux systems. +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "job_queue.h" + +pthread_mutex_t stdout_mutex = PTHREAD_MUTEX_INITIALIZER; + +// err.h contains various nonstandard BSD extensions, but they are +// very handy. +#include + +#include "histogram.h" + +int main(int argc, char * const *argv) { + if (argc < 2) { + err(1, "usage: paths..."); + exit(1); + } + + int num_threads = 1; + char * const *paths = &argv[1]; + + if (argc > 3 && strcmp(argv[1], "-n") == 0) { + // Since atoi() simply returns zero on syntax errors, we cannot + // distinguish between the user entering a zero, or some + // non-numeric garbage. In fact, we cannot even tell whether the + // given option is suffixed by garbage, i.e. '123foo' returns + // '123'. A more robust solution would use strtol(), but its + // interface is more complicated, so here we are. + num_threads = atoi(argv[2]); + + if (num_threads < 1) { + err(1, "invalid thread count: %s", argv[2]); + } + + paths = &argv[3]; + } else { + paths = &argv[1]; + } + + assert(0); // Initialise the job queue and some worker threads here. + + // FTS_LOGICAL = follow symbolic links + // FTS_NOCHDIR = do not change the working directory of the process + // + // (These are not particularly important distinctions for our simple + // uses.) + int fts_options = FTS_LOGICAL | FTS_NOCHDIR; + + FTS *ftsp; + if ((ftsp = fts_open(paths, fts_options, NULL)) == NULL) { + err(1, "fts_open() failed"); + return -1; + } + + FTSENT *p; + while ((p = fts_read(ftsp)) != NULL) { + switch (p->fts_info) { + case FTS_D: + break; + case FTS_F: + assert(0); // Process the file p->fts_path, somehow. + break; + default: + break; + } + } + + fts_close(ftsp); + + assert(0); // Shut down the job queue and the worker threads here. + + move_lines(9); + + return 0; +} diff --git a/A2/fhistogram.c b/A2/fhistogram.c new file mode 100644 index 0000000..e070983 --- /dev/null +++ b/A2/fhistogram.c @@ -0,0 +1,93 @@ +// Setting _DEFAULT_SOURCE is necessary to activate visibility of +// certain header file contents on GNU/Linux systems. +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +// err.h contains various nonstandard BSD extensions, but they are +// very handy. +#include + +#include "histogram.h" + +int global_histogram[8] = { 0 }; + +int fhistogram(char const *path) { + FILE *f = fopen(path, "r"); + + int local_histogram[8] = { 0 }; + + if (f == NULL) { + fflush(stdout); + warn("failed to open %s", path); + return -1; + } + + int i = 0; + + char c; + while (fread(&c, sizeof(c), 1, f) == 1) { + i++; + update_histogram(local_histogram, c); + if ((i % 100000) == 0) { + merge_histogram(local_histogram, global_histogram); + print_histogram(global_histogram); + } + } + + fclose(f); + + merge_histogram(local_histogram, global_histogram); + print_histogram(global_histogram); + + return 0; +} + +int main(int argc, char * const *argv) { + if (argc < 2) { + err(1, "usage: paths..."); + exit(1); + } + + char * const *paths = &argv[1]; + + // FTS_LOGICAL = follow symbolic links + // FTS_NOCHDIR = do not change the working directory of the process + // + // (These are not particularly important distinctions for our simple + // uses.) + int fts_options = FTS_LOGICAL | FTS_NOCHDIR; + + FTS *ftsp; + if ((ftsp = fts_open(paths, fts_options, NULL)) == NULL) { + err(1, "fts_open() failed"); + return -1; + } + + FTSENT *p; + while ((p = fts_read(ftsp)) != NULL) { + switch (p->fts_info) { + case FTS_D: + break; + case FTS_F: + fhistogram(p->fts_path); + break; + default: + break; + } + } + + fts_close(ftsp); + + move_lines(9); + + return 0; +} diff --git a/A2/fibs.c b/A2/fibs.c new file mode 100644 index 0000000..e92c6d7 --- /dev/null +++ b/A2/fibs.c @@ -0,0 +1,122 @@ +// This program reads a newline-separated sequence of integers from +// standard input. For each such integer, the corresponding Fibonacci +// number is printed. This is similar to the programs we saw at the +// November 20 lecture. + +// Setting _DEFAULT_SOURCE is necessary to activate visibility of +// certain header file contents on GNU/Linux systems. +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +// err.h contains various nonstandard BSD extensions, but they are +// very handy. +#include + +#include "job_queue.h" + +// Whenever we print to the screen, we will first lock this mutex. +// This ensures that multiple threads do not try to print +// concurrently. +pthread_mutex_t stdout_mutex = PTHREAD_MUTEX_INITIALIZER; + +// A simple recursive (inefficient) implementation of the Fibonacci +// function. +int fib (int n) { + if (n < 2) { + return 1; + } else { + return fib(n-1) + fib(n-2); + } +} + +// This function converts a line to an integer, computes the +// corresponding Fibonacci number, then prints the result to the +// screen. +void fib_line(const char *line) { + int n = atoi(line); + int fibn = fib(n); + assert(pthread_mutex_lock(&stdout_mutex) == 0); + printf("fib(%d) = %d\n", n, fibn); + assert(pthread_mutex_unlock(&stdout_mutex) == 0); +} + +// Each thread will run this function. The thread argument is a +// pointer to a job queue. +void* worker(void *arg) { + struct job_queue *jq = arg; + + while (1) { + char *line; + if (job_queue_pop(jq, (void**)&line) == 0) { + fib_line(line); + free(line); + } else { + // If job_queue_pop() returned non-zero, that means the queue is + // being killed (or some other error occured). In any case, + // that means it's time for this thread to die. + break; + } + } + + return NULL; +} + +int main(int argc, char * const *argv) { + int num_threads = 1; + + if (argc == 3 && strcmp(argv[1], "-n") == 0) { + // Since atoi() simply returns zero on syntax errors, we cannot + // distinguish between the user entering a zero, or some + // non-numeric garbage. In fact, we cannot even tell whether the + // given option is suffixed by garbage, i.e. '123foo' returns + // '123'. A more robust solution would use strtol(), but its + // interface is more complicated, so here we are. + num_threads = atoi(argv[2]); + + if (num_threads < 1) { + err(1, "invalid thread count: %s", argv[2]); + } + } + + // Create job queue. + struct job_queue jq; + job_queue_init(&jq, 64); + + // Start up the worker threads. + pthread_t *threads = calloc(num_threads, sizeof(pthread_t)); + for (int i = 0; i < num_threads; i++) { + if (pthread_create(&threads[i], NULL, &worker, &jq) != 0) { + err(1, "pthread_create() failed"); + } + } + + + // Now read lines from stdin until EOF. + char *line = NULL; + ssize_t line_len; + size_t buf_len = 0; + while ((line_len = getline(&line, &buf_len, stdin)) != -1) { + job_queue_push(&jq, (void*)strdup(line)); + } + free(line); + + // Destroy the queue. + job_queue_destroy(&jq); + + // Wait for all threads to finish. This is important, at some may + // still be working on their job. + for (int i = 0; i < num_threads; i++) { + if (pthread_join(threads[i], NULL) != 0) { + err(1, "pthread_join() failed"); + } + } +} diff --git a/A2/histogram.h b/A2/histogram.h new file mode 100644 index 0000000..967e00a --- /dev/null +++ b/A2/histogram.h @@ -0,0 +1,71 @@ +// This header file contains not just function prototypes, but also +// the definitions. This means it does not need to be compiled +// separately. +// +// You should not need to modify this file. + +#ifndef HISTOGRAM_H +#define HISTOGRAM_H + +// Move the cursor down 'n' lines. Negative 'n' supported. +static void move_lines(int n) { + if (n < 0) { + printf("\033[%dA", -n); + } else { + printf("\033[%dB", n); + } +} + +// Clear from cursor to end of line. +static void clear_line() { + printf("\033[K"); +} + +// Print a visual representation of a histogram to the screen. After +// printing, the cursor is moved back to the beginning of the output. +// This means that next time print_histogram() is called, the previous +// output will be overwritten. +static void print_histogram(int histogram[8]) { + int64_t bits_seen = 0; + + for (int i = 0; i < 8; i++) { + bits_seen += histogram[i]; + } + + for (int i = 0; i < 8; i++) { + clear_line(); + printf("Bit %d: ", i); + + double proportion = histogram[i] / ((double)bits_seen); + for (int i = 0; i < 60*proportion; i++) { + printf("*"); + } + printf("\n"); + } + + clear_line(); + printf("%ld bits processed.\n", (long)bits_seen); + move_lines(-9); +} + +// Merge the former histogram into the latter, setting the former to +// zero in the process. +static void merge_histogram(int from[8], int to[8]) { + for (int i = 0; i < 8; i++) { + to[i] += from[i]; + from[i] = 0; + } +} + +// Update the histogram with the bits of a byte. +static void update_histogram(int histogram[8], unsigned char byte) { + // For all bits in a byte... + for (int i = 0; i < 8; i++) { + // count if bit 'i' is set. + if (byte & (1< +#include +#include + +#include "job_queue.h" + +int job_queue_init(struct job_queue *job_queue, int capacity) { + assert(0); +} + +int job_queue_destroy(struct job_queue *job_queue) { + assert(0); +} + +int job_queue_push(struct job_queue *job_queue, void *data) { + assert(0); +} + +int job_queue_pop(struct job_queue *job_queue, void **data) { + assert(0); +} diff --git a/A2/job_queue.h b/A2/job_queue.h new file mode 100644 index 0000000..e21a00f --- /dev/null +++ b/A2/job_queue.h @@ -0,0 +1,30 @@ +#ifndef JOB_QUEUE_H +#define JOB_QUEUE_H + +#include + +struct job_queue { + int dummy; +}; + +// Initialise a job queue with the given capacity. The queue starts out +// empty. Returns non-zero on error. +int job_queue_init(struct job_queue *job_queue, int capacity); + +// Destroy the job queue. Blocks until the queue is empty before it +// is destroyed. +int job_queue_destroy(struct job_queue *job_queue); + +// Push an element onto the end of the job queue. Blocks if the +// job_queue is full (its size is equal to its capacity). Returns +// non-zero on error. It is an error to push a job onto a queue that +// has been destroyed. +int job_queue_push(struct job_queue *job_queue, void *data); + +// Pop an element from the front of the job queue. Blocks if the +// job_queue contains zero elements. Returns non-zero on error. If +// job_queue_destroy() has been called (possibly after the call to +// job_queue_pop() blocked), this function will return -1. +int job_queue_pop(struct job_queue *job_queue, void **data); + +#endif