Initial commit
This commit is contained in:
commit
fcca47f043
8 changed files with 422 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal 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
BIN
PREEMPTIVE/libprethread.a
Executable file
Binary file not shown.
164
chan.c
Executable file
164
chan.c
Executable 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
22
chan.h
Executable 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
79
queue.c
Executable 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
21
queue.h
Executable 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
103
sem.c
Executable 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
21
sem.h
Executable 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 */
|
Reference in a new issue