commit fcca47f0430f3cff7e4fd195e8056ad658401eff Author: Ben Goldsworthy Date: Wed Jun 24 13:51:34 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07b1505 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +thread.* +APPS/ +PREEMPTIVE/libprethreat.a +PREEMPTIVE/makefile +PREEMPTIVE/README +PREEMPTIVE/sem.c +PREEMPTIVE/sem.h +PREEMPTIVE/thread.c +libthread.a +core +makefile +README diff --git a/PREEMPTIVE/libprethread.a b/PREEMPTIVE/libprethread.a new file mode 100755 index 0000000..0a6c895 Binary files /dev/null and b/PREEMPTIVE/libprethread.a differ diff --git a/chan.c b/chan.c new file mode 100755 index 0000000..bf6a80f --- /dev/null +++ b/chan.c @@ -0,0 +1,164 @@ +/************************************************************************ + * * + * file: chan.c * + * * + * Module implementing message passing IPC for threads. * + * * + * Our implementation assumes that threads calling us are * + * subject to arbitrary preemption. * + * * + ************************************************************************/ + +#include "thread.h" +#include "chan.h" +#include +#include + +Sem* chan_mutex; /* protect chan module from race conditions */ + +/************************************************************************ + * INTERFACE: chan_create() * + * * + * Make a channel and return a pointer to it. Return (Chan *)0 on * + * failure. A channel is a struct which we allocate using malloc(). * + * Internally, the channel consists of 3 semaphores which are created * + * using chan_create(). We initalise the data field of the channel to 0.* + * Failure occurs if either the malloc() fails, or we fail to create * + * some of the semaphores. In case of failure we are careful to * + * free any just-allocated resources to avoid memory leaks. * + ************************************************************************/ +Chan* +chan_create() +{ + Chan* c; + static int firstCall = 1; + + if (firstCall) { + firstCall = 0; + if((chan_mutex = sem_create(1)) == (Sem*)0) + return (Chan*)0; + } + + if ((c = (Chan*)malloc(sizeof(Chan))) == (Chan*)0) + return (Chan*)0; + + if ((c->sblock = sem_create(0)) == (Sem*)0) { + free((void*)c); + return (Chan*)0; + } + + if ((c->rblock = sem_create(0)) == (Sem*)0) { + free((void*)c); + sem_destroy(c->sblock); + return (Chan*)0; + } + + if ((c->send_serialiser = sem_create(1)) == (Sem*)0) { + free((void*)c); + sem_destroy(c->sblock); + sem_destroy(c->rblock); + return (Chan*)0; + } + + c->data = 0; + return c; +} + +/************************************************************************ + * INTERFACE: chan_destroy() * + * * + * Destroy a channel. Must only be allowed to succeed if we are able * + * to successfully destroy *all* the semaphores. Must handle the case * + * where we can destroy some semaphores but not others (in which case * + * we should obviously report failure). If we fail to destroy * + * a semaphore, we need to restore previously-destroyed ones to return * + * the channel to its pre-call state. * + * Return -1 on failure; and 1 on success. * + ************************************************************************/ +int +chan_destroy(Chan* c) +{ + Chan* backup; + + sem_P(chan_mutex); + if ((backup = chan_create()) == (Chan*)0) + return -1; + + if ((sem_destroy(c->rblock)) == -1) { + sem_destroy(backup->rblock); + sem_destroy(backup->sblock); + sem_destroy(backup->send_serialiser); + free((void*)backup); + sem_V(chan_mutex); + return -1; + } + /* So far: successfully destroyed rblock */ + if ((sem_destroy(c->sblock)) == -1) { + c->rblock = backup->rblock; + sem_destroy(backup->sblock); + sem_destroy(backup->send_serialiser); + free((void*)backup); + sem_V(chan_mutex); + return -1; + } + + /* So far: successfully destrotyed rblcok and sblock */ + if ((sem_destroy(c->send_serialiser)) == -1) { + c->rblock = backup->rblock; + c->sblock = backup->sblock; + sem_destroy(backup->send_serialiser); + free((void*)backup); + sem_V(chan_mutex); + return -1; + } + + /* So far: successfully destroyed rblock, sblock and send_serialiser */ + sem_destroy(backup->rblock); + sem_destroy(backup->sblock); + sem_destroy(backup->send_serialiser); + free((void*)backup); + free((void*)c); + sem_V(chan_mutex); + return 1; +} + +/************************************************************************ + * INTERFACE: chan_send() * + * * + * Send a message on a channel. * + * This will involve, first, attaching the given int 'message' to the * + * channel, then telling chan_receive() that we have a message to send * + * (by calling sem_V() on one sem), and then waiting (by calling sem_P()* + * on the other sem) until chan_receive() has taken the message and * + * told us we can proceed. We can use the send_serialiser semaphore to * + * ensure that concurrent calls to chan_send() are serialised. * + ************************************************************************/ +void +chan_send(Chan* c, int sentdata) +{ + sem_P(c->send_serialiser); + c->data = sentdata; + sem_V(c->rblock); + sem_P(c->sblock); + sem_V(c->send_serialiser); +} + +/************************************************************************ + * INTERFACE: chan_receive() * + * * + * Receive a message on a channel. * + * This will involve, first, waiting on one semaphore (using sem_P()) * + * until chan_send() has given us something to receive; then making * + * receiveddata point at the message in the channel; then * + * telling chan_send() that it may proceed (using sem_V() on the other * + * semaphore). * + ************************************************************************/ +void +chan_receive(Chan* c, int* receiveddata) +{ + sem_P(c->rblock); + *receiveddata = c->data; + sem_V(c->sblock); +} + +/* end file: chan.c */ diff --git a/chan.h b/chan.h new file mode 100755 index 0000000..a5b1fd3 --- /dev/null +++ b/chan.h @@ -0,0 +1,22 @@ +/* file: chan.h -- public interface to message passingfunctions. */ + +#ifndef CHAN_DEFINED +#define CHAN_DEFINED + +#include "sem.h" +#include "thread.h" + +typedef struct { + Sem *rblock; /* receivers block on this */ + Sem *sblock; /* senders block on this */ + Sem *send_serialiser; /* to prevent concurrent senders clashing */ + int data; +} Chan; + +Chan *chan_create(void); +int chan_destroy(Chan *chan); +void chan_send(Chan *chan, int sentdata); +void chan_receive(Chan *chan, int *receiveddata); + +#endif +/* end file: chan.h */ diff --git a/queue.c b/queue.c new file mode 100755 index 0000000..62c0e1a --- /dev/null +++ b/queue.c @@ -0,0 +1,79 @@ +/************************************************************************ + * * + * file: queue.c * + * * + * Module implementing queue manipulation functions. The module can * + * hold structs of any type as long as the user of the module * + * does the appropriate casting -- all it assumes is that the first * + * member of the struct is a pointer, 'next', to a struct. * + * * + ************************************************************************/ + +#include "queue.h" +#include + +/************************************************************************ + * queue_init() * + * * + * Initialise a Queue struct. * + * We assume this will never fail - so always return 1 for success. * + ************************************************************************/ +int +queue_init(Queue* q) +{ + q->front = q->back = (Qitem*)0; + return 1; +} + +/************************************************************************ + * queue_empty() * + * * + * Return TRUE (i.e. non-zero) iff queue is empty; else FALSE (i.e. 0). * + ************************************************************************/ +int +queue_empty(Queue* q) +{ + return q->front == (Qitem*)0; +} + +/************************************************************************ + * queue_put() * + * * + * Add a new Qitem to the back of the specified queue. * + * Make sure the next pointer of the new Qitem is 0! * + * We assume the given Qitem can be used directly by us and does not * + * need to be copied. * + ************************************************************************/ +void +queue_put(Queue* q, Qitem* new_item) +{ + new_item->next = (Qitem*)0; + if (queue_empty(q)) + q->front = new_item; + else + q->back->next = new_item; + q->back = new_item; +} + +/************************************************************************ + * queue_get() * + * * + * Remove the front Qitem struct from the specified queue and return * + * a pointer to it. When we remove and return the struct we pass all * + * future responsibility for it to our caller. Return (Qitem *)0 if * + * queue is empty. * + ************************************************************************/ +Qitem* +queue_get(Queue* q) +{ + Qitem* p = q->front; + if (!queue_empty(q)) { + q->front = q->front->next; + if (q->front == (Qitem*)0) q->back = (Qitem*)0; + } + return p; +} + +/* end file: queue.c */ + + diff --git a/queue.h b/queue.h new file mode 100755 index 0000000..fc7021c --- /dev/null +++ b/queue.h @@ -0,0 +1,21 @@ +/* file: queue.h -- definitions for queue manipulation routines. */ + +#ifndef QUEUE_DEFINED +#define QUEUE_DEFINED + +typedef struct qitem { + struct qitem *next; /* this must always be first; can cast to any struct with this first */ +} Qitem; + +typedef struct queue { + Qitem *front, *back; +} Queue; + +int queue_init(Queue *q); +int queue_empty(Queue *q); +void queue_put(Queue *q, Qitem *new_item); +Qitem *queue_get(Queue *q); + +#endif +/* end file: queue.h */ + diff --git a/sem.c b/sem.c new file mode 100755 index 0000000..eee13a2 --- /dev/null +++ b/sem.c @@ -0,0 +1,103 @@ +/************************************************************************ + * * + * file: sem.c * + * * + * Module implementing semaphores. * + * * + * All routines should call thread_yield() where reasonable to encourage* + * fair execution. * + * * + * Note that our implementation assumes that threads calling us are not * + * subject to arbitrary preemption - if they were, we would need to * + * add a protecting spin lock to our semaphore implementation. * + * * + ************************************************************************/ + +#include "sem.h" +#include +#include + +/* External stuff defined in thread.c. + */ +extern Queue ready_queue; +extern int thread_block_and_switch(Queue* q); + +/************************************************************************ + * INTERFACE: sem_create() * + * * + * Make and return a semaphore (i.e. return a pointer to a Sem struct). * + * This will involve allocating a Sem structure using malloc(), * + * creating/initialising a queue for threads blocked on the * + * semaphore and also initialising the semaphore to the value specified * + * by our caller. This value must be validated to be >= 0. The * + * function can only fail if malloc() fails - in this case we return * + * (Sem *)0. * + ************************************************************************/ +Sem* sem_create(int val) +{ + Sem* s; + + if (val < 0 || ((s = (Sem*)malloc(sizeof(Sem))) == (Sem*)0)) + return (Sem*)0; + + s->val = val; + queue_init(&(s->queue)); + thread_yield(); + return s; +} + +/************************************************************************ + * INTERFACE: sem_destroy() * + * * + * Destroy a semaphore. * + * Forbid destruction (return -1) if the semaphore has waiting threads. * + * Otherwise return 1 for success. * + ************************************************************************/ +int +sem_destroy(Sem* s) +{ + if (!queue_empty(&(s->queue))) return -1; + free((void*)s); + return 1; +} + +/************************************************************************ + * INTERFACE: sem_P() * + * * + * Standard semaphore operation. * + * That is, decrement the value of the semaphore and if this takes * + * its value below 0 block the calling thread on the semaphore's queue * + * using thread_block_and_switch(). * + * We know that a semaphore value can't theoretically get less than 0, * + * but we internally (hidden from the semaphore user) use the negative * + * range of val to record how many threads are currently blocked on the * + * semaphore. * + ************************************************************************/ +void +sem_P(Sem* s) +{ + if (--s->val < 0) thread_block_and_switch(&(s->queue)); + else thread_yield(); +} + +/************************************************************************ + * INTERFACE: sem_V() * + * * + * Standard semaphore operation. * + * That is, increment the value of the semaphore. If there were threads * + * blocked on the semaphore (i.e. the value after incrementing the * + * semaphore is still <= 0), we get the first thread from the * + * semaphore's queue and put it on the thread package's ready queue * + * using queue_put(). * + * Because the sem module is solely responsible for managing a sem's * + * queue, we can guarantee that if (s->val <= 0) there is always an * + * item on the sem's queue. * + ************************************************************************/ +void +sem_V(Sem *s) +{ + if (++s->val <= 0) queue_put(&ready_queue, queue_get(&(s->queue))); + thread_yield(); +} + +/* end file: sem.c */ diff --git a/sem.h b/sem.h new file mode 100755 index 0000000..1d616bf --- /dev/null +++ b/sem.h @@ -0,0 +1,21 @@ +/* file: sem.h -- definitions for semaphores. */ + +#ifndef SEM_DEFINED +#define SEM_DEFINED + +/* Include public interface. */ +#include "queue.h" +#include "thread.h" + +typedef struct semtype { + int val; + Queue queue; +} Sem; + +Sem *sem_create(int val); +int sem_destroy(Sem *s); +void sem_P(Sem *s); +void sem_V(Sem *s); + +#endif +/* end file: sem.h */