Initial commit

This commit is contained in:
Ben Goldsworthy 2020-06-24 13:51:34 +01:00
commit fcca47f043
8 changed files with 422 additions and 0 deletions

12
.gitignore vendored Normal file
View file

@ -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

BIN
PREEMPTIVE/libprethread.a Executable file

Binary file not shown.

164
chan.c Executable file
View file

@ -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 <stdlib.h>
#include <stdio.h>
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 */

22
chan.h Executable file
View file

@ -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 */

79
queue.c Executable file
View file

@ -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 <stdio.h>
/************************************************************************
* 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 */

21
queue.h Executable file
View file

@ -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 */

103
sem.c Executable file
View file

@ -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 <stdlib.h>
#include <stdio.h>
/* 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 */

21
sem.h Executable file
View file

@ -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 */