A3 🎉
This commit is contained in:
BIN
A1/src.zip
Normal file
BIN
A1/src.zip
Normal file
Binary file not shown.
23
A2/Makefile
Normal file
23
A2/Makefile
Normal file
@ -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
|
87
A2/fauxgrep-mt.c
Normal file
87
A2/fauxgrep-mt.c
Normal file
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fts.h>
|
||||
|
||||
// err.h contains various nonstandard BSD extensions, but they are
|
||||
// very handy.
|
||||
#include <err.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#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;
|
||||
}
|
82
A2/fauxgrep.c
Normal file
82
A2/fauxgrep.c
Normal file
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fts.h>
|
||||
|
||||
// err.h contains various nonstandard BSD extensions, but they are
|
||||
// very handy.
|
||||
#include <err.h>
|
||||
|
||||
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;
|
||||
}
|
87
A2/fhistogram-mt.c
Normal file
87
A2/fhistogram-mt.c
Normal file
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fts.h>
|
||||
|
||||
#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 <err.h>
|
||||
|
||||
#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;
|
||||
}
|
93
A2/fhistogram.c
Normal file
93
A2/fhistogram.c
Normal file
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fts.h>
|
||||
|
||||
// err.h contains various nonstandard BSD extensions, but they are
|
||||
// very handy.
|
||||
#include <err.h>
|
||||
|
||||
#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;
|
||||
}
|
122
A2/fibs.c
Normal file
122
A2/fibs.c
Normal file
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fts.h>
|
||||
|
||||
// err.h contains various nonstandard BSD extensions, but they are
|
||||
// very handy.
|
||||
#include <err.h>
|
||||
|
||||
#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");
|
||||
}
|
||||
}
|
||||
}
|
71
A2/histogram.h
Normal file
71
A2/histogram.h
Normal file
@ -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<<i)) {
|
||||
histogram[i]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
21
A2/job_queue.c
Normal file
21
A2/job_queue.c
Normal file
@ -0,0 +1,21 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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);
|
||||
}
|
30
A2/job_queue.h
Normal file
30
A2/job_queue.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef JOB_QUEUE_H
|
||||
#define JOB_QUEUE_H
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
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
|
Reference in New Issue
Block a user