Commit ff5769f1 authored by Henry K. Sun's avatar Henry K. Sun
Browse files

Initial commit

parents
No related merge requests found
Showing with 1403 additions and 0 deletions
+1403 -0
#
# Compiler configuration
#
CFLAGS = -Wall -Werror -g
ASFLAGS = -g
# Object files:
OFILES = glue.o sthread.o queue.o timer.o semaphore.o bounded_buffer.o
#
# How to build the program
#
all: fibtest testlock
fibtest: fibtest.o $(OFILES)
$(CC) $(CFLAGS) -o $@ $^
testlock: testlock.o $(OFILES)
$(CC) $(CFLAGS) -o $@ $^
clean:
rm -f *.o *~ fibtest testlock
.PHONY: all clean
#
# Dependencies
#
timer.o: timer.h glue.h
sthread.o: sthread.h glue.h queue.h
queue.o: queue.h sthread.h
bounded_buffer.o: bounded_buffer.h sthread.h glue.h
fibtest.o: sthread.h bounded_buffer.h
testlock.o: glue.h
/*
* Define a bounded buffer containing records that describe the
* results in a producer thread.
*
*--------------------------------------------------------------------
* Adapted from code for CS24 by Jason Hickey.
* Copyright (C) 2003-2010, Caltech. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include "sthread.h"
#include "bounded_buffer.h"
/*
* The bounded buffer data.
*/
struct _bounded_buffer {
/* The maximum number of elements in the buffer */
int length;
/* The index of the first element in the buffer */
int first;
/* The number of elements currently stored in the buffer */
int count;
/* The values in the buffer */
BufferElem *buffer;
};
#define EMPTY -1
/*
* Allocate a new bounded buffer.
*/
BoundedBuffer *new_bounded_buffer(int length) {
BoundedBuffer *bufp;
BufferElem *buffer;
int i;
/* Allocate the buffer */
buffer = (BufferElem *) malloc(length * sizeof(BufferElem));
bufp = (BoundedBuffer *) malloc(sizeof(BoundedBuffer));
if (buffer == 0 || bufp == 0) {
fprintf(stderr, "new_bounded_buffer: out of memory\n");
exit(1);
}
/* Initialize */
memset(bufp, 0, sizeof(BoundedBuffer));
for (i = 0; i != length; i++) {
buffer[i].id = EMPTY;
buffer[i].arg = EMPTY;
buffer[i].val = EMPTY;
}
bufp->length = length;
bufp->buffer = buffer;
return bufp;
}
/*
* Add an integer to the buffer. Yield control to another
* thread if the buffer is full.
*/
void bounded_buffer_add(BoundedBuffer *bufp, const BufferElem *elem) {
/* Wait until the buffer has space */
while (bufp->count == bufp->length)
sthread_yield();
/* Now the buffer has space. Copy the element data over. */
int idx = (bufp->first + bufp->count) % bufp->length;
bufp->buffer[idx].id = elem->id;
bufp->buffer[idx].arg = elem->arg;
bufp->buffer[idx].val = elem->val;
bufp->count = bufp->count + 1;
}
/*
* Get an integer from the buffer. Yield control to another
* thread if the buffer is empty.
*/
void bounded_buffer_take(BoundedBuffer *bufp, BufferElem *elem) {
/* Wait until the buffer has a value to retrieve */
while (bufp->count == 0)
sthread_yield();
/* Copy the element from the buffer, and clear the record */
elem->id = bufp->buffer[bufp->first].id;
elem->arg = bufp->buffer[bufp->first].arg;
elem->val = bufp->buffer[bufp->first].val;
bufp->buffer[bufp->first].id = -1;
bufp->buffer[bufp->first].arg = -1;
bufp->buffer[bufp->first].val = -1;
bufp->count = bufp->count - 1;
bufp->first = (bufp->first + 1) % bufp->length;
}
/*
* An implementation of a bounded buffer of integers.
*
*--------------------------------------------------------------------
* Adapted from code for CS24 by Jason Hickey.
* Copyright (C) 2003-2010, Caltech. All rights reserved.
*/
#ifndef _BOUNDED_BUFFER_H
#define _BOUNDED_BUFFER_H
/*
* The bounded buffer data type.
*/
typedef struct _bounded_buffer BoundedBuffer;
/*
* The buffer holds elements of this type.
*/
typedef struct buffer_elem {
int id; /* Identifier of the producer */
int arg; /* Argument to the producer */
int val; /* Value produced */
} BufferElem;
/*
* Create a new buffer with the specified maximum length.
*/
BoundedBuffer * new_bounded_buffer(int length);
/*
* Add and remove values from the queue.
*/
void bounded_buffer_add(BoundedBuffer *bufp, const BufferElem *s);
void bounded_buffer_take(BoundedBuffer *bufp, BufferElem *s);
#endif /* _BOUNDED_BUFFER_H */
/*
* Test program implements a producer-consumer problem.
* There are two producers that each produce Fibonacci
* numbers and add the results to the buffer. There
* is a single consumer that takes the results from
* the buffer, checks that the values are correct,
* and prints them out.
*
*--------------------------------------------------------------------
* Adapted from code for CS24 by Jason Hickey.
* Copyright (C) 2003-2010, Caltech. All rights reserved.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include "sthread.h"
#include "semaphore.h"
#include "bounded_buffer.h"
/*
* Queue for passing values between the producers and the consumer.
*/
#define DEFAULT_BUFFER_LENGTH 20
static BoundedBuffer *queue;
/*
* Recursive Fibonacci.
*/
static int fib(int i) {
if (i < 2)
return 1;
return fib(i - 1) + fib(i - 2);
}
/*
* Producers generate Fibonacci numbers in a particular range, because we
* need them to take a certain amount of time to allow for context switches
* to occur. The timer is configured to context-switch every 500us. If we
* generate Fibonacci numbers that take between 5us and 100us to generate,
* threads can generate a handful of values before being preempted. This
* will exercise our bounded buffer, etc.
*/
#define MIN_FIB_USEC 5
#define MAX_FIB_USEC 100
static int MIN_FIB;
static int MAX_FIB;
static void producer(void *arg) {
BufferElem elem;
int i = MIN_FIB;
elem.id = (intptr_t) arg;
while (1) {
/* Place the next computed Fibonacci result into the buffer */
elem.arg = i;
elem.val = fib(i);
bounded_buffer_add(queue, &elem);
i++;
if (i > MAX_FIB)
i = MIN_FIB;
}
}
/*
* Consumer prints them out.
*/
static void consumer(void *arg) {
BufferElem elem;
int i;
/* Read the contents of the buffer, and print them */
while (1) {
bounded_buffer_take(queue, &elem);
i = fib(elem.arg);
printf("Result from producer %d: Fib %2d = %8d; should be %8d; %s\n",
elem.id, elem.arg, elem.val, i,
i == elem.val ? "matched" : "NO MATCH");
}
}
/* Find the lowest Fibonacci number that takes at least the specified
* amount of time to compute on this system.
*/
int find_fib_time(int i_start, int length_us) {
int i = i_start;
while (1) {
struct timespec t_start, t_end;
long diff;
if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t_start)) {
perror("get start time");
exit(1);
}
(void) fib(i);
if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t_end)) {
perror("get end time");
exit(1);
}
// Compute difference between end time and start time in us.
diff = t_end.tv_nsec - t_start.tv_nsec;
diff += (t_end.tv_sec - t_start.tv_sec) * 1000000000;
diff /= 1000;
if (diff >= length_us) {
return i;
break;
}
i++;
}
}
/*
* The main function starts the two producers and the consumer,
* the starts the thread scheduler.
*/
int main(int argc, char **argv) {
fprintf(stderr, "Calibrating test.\n");
/* Find the lowest Fibonacci number that takes at least MIN_FIB_USEC
* to compute on this system.
*/
MIN_FIB = find_fib_time(1, MIN_FIB_USEC);
/* Find the lowest Fibonacci number that takes at least MAX_FIB_USEC
* to compute on this system.
*/
MAX_FIB = find_fib_time(MIN_FIB, MAX_FIB_USEC);
fprintf(stderr, "Computing Fibonacci numbers in the range [%d, %d].\n\n",
MIN_FIB, MAX_FIB);
sleep(1); /* Pause for effect! */
queue = new_bounded_buffer(DEFAULT_BUFFER_LENGTH);
sthread_create(producer, (void *) 0);
sthread_create(producer, (void *) 1);
sthread_create(consumer, (void *) 0);
/*
* Start the thread scheduler. By default, the timer is
* not started. Change the argument to 1 to start the timer.
*/
sthread_start(0);
return 0;
}
/* C declarations for the functions in the glue code.
*--------------------------------------------------------------------
* Adapted from code for CS24 by Jason Hickey.
* Copyright (C) 2003-2010, Caltech. All rights reserved.
*/
#ifndef _GLUE_H
#define _GLUE_H
/* Lock the scheduler. This function is used to ensure
* mutual exclusion in the scheduler. The function returns
* 1 if the lock was successful, otherwise it returns 0.
*/
extern int __sthread_lock(void);
/* Unlock the scheduler. */
extern void __sthread_unlock(void);
/* The entry-point into the context-switch mechanism. */
void __sthread_switch(void);
#endif /* _GLUE_H */
#============================================================================
# Keep a pointer to the main scheduler context. This variable should be
# initialized to %rsp, which is done in the __sthread_start routine.
#
.data
.align 8
scheduler_context: .quad 0
#============================================================================
# Integer variable for locking the scheduler
#
scheduler_lock: .long 0
.text
#============================================================================
# This function can be called from C to obtain the scheduler lock.
# It returns 1 if the lock is granted, 0 otherwise.
#
.align 8
.globl __sthread_lock
__sthread_lock:
# TODO: currently this code always returns 1 (it always grants
# the lock). Fix this code, using the "scheduler_lock" variable
# to ensure mutual exlucsion.
movl $1, %eax
ret
#============================================================================
# This function can be called from C to release the scheduler lock.
#
.align 8
.globl __sthread_unlock
__sthread_unlock:
# TODO: release the lock.
ret
#============================================================================
# __sthread_switch is the main entry point for the thread scheduler.
# It has three parts:
#
# 1. Save the context of the current thread on the stack.
# The context includes all of the integer registers and RFLAGS.
#
# 2. Call __sthread_scheduler (the C scheduler function), passing the
# context as an argument. The scheduler stack *must* be restored by
# setting %rsp to the scheduler_context before __sthread_scheduler is
# called.
#
# 3. __sthread_scheduler will return the context of a new thread.
# Restore the context, and return to the thread.
#
.align 8
.globl __sthread_switch
__sthread_switch:
# Save the process state onto its stack
pushq %rax
pushq %rbx
pushq %rcx
pushq %rdx
pushq %rsi
pushq %rdi
pushq %rbp
pushq %r8
pushq %r9
pushq %r10
pushq %r11
pushq %r12
pushq %r13
pushq %r14
pushq %r15
pushf
# Call the high-level scheduler with the current context as an argument
movq %rsp, %rdi
movq scheduler_context(%rip), %rsp
call __sthread_scheduler
# The scheduler will return a context to start.
# Restore the context to resume the thread.
__sthread_restore:
# The returned context should become the new stack, so we can
# restore the machine context off of the stack.
movq %rax, %rsp
popf
popq %r15
popq %r14
popq %r13
popq %r12
popq %r11
popq %r10
popq %r9
popq %r8
popq %rbp
popq %rdi
popq %rsi
popq %rdx
popq %rcx
popq %rbx
popq %rax
ret
#============================================================================
# Initialize a process context, given:
# 1. the stack for the process
# 2. the function to start
# 3. its argument
# The context should be consistent with that saved in the __sthread_switch
# routine.
#
# A pointer to the newly initialized context is returned to the caller.
# (This is the thread's stack-pointer after its context has been set up.)
#
# This function is described in more detail in sthread.c.
#
#
.align 8
.globl __sthread_initialize_context
__sthread_initialize_context:
# %rdi = stackp
# %rsi = f
# %rdx = arg
# Set up return value in %rax. This is the incoming stack pointer,
# minus 144 bytes for the new thread context details and return
# address.
movq %rdi, %rax
subq $144, %rax
leaq __sthread_finish(%rip), %rdi
movq %rdi, 136(%rax) # When f returns, go here
movq %rsi, 128(%rax) # Return address
movq $0, 120(%rax) # rax = 0
movq $0, 112(%rax) # rbx = 0
movq $0, 104(%rax) # rcx = 0
movq $0, 96(%rax) # rdx = 0
movq $0, 88(%rax) # rsi = 0
movq %rdx, 80(%rax) # rdi = arg
movq $0, 72(%rax) # rbp = 0
movq $0, 64(%rax) # r8 = 0
movq $0, 56(%rax) # r9 = 0
movq $0, 48(%rax) # r10 = 0
movq $0, 40(%rax) # r11 = 0
movq $0, 32(%rax) # r12 = 0
movq $0, 24(%rax) # r13 = 0
movq $0, 16(%rax) # r14 = 0
movq $0, 8(%rax) # r15 = 0
movq $0, (%rax) # rflags = 0
ret
#============================================================================
# The start routine initializes the scheduler_context variable, and calls
# the __sthread_scheduler with a NULL context.
#
# The scheduler will return a context to resume.
#
.align 8
.globl __sthread_start
__sthread_start:
# Remember the context
movq %rsp, scheduler_context(%rip)
# Call the scheduler with no context
movl $0, %edi # Also clears upper 4 bytes of %rdi
call __sthread_scheduler
# Restore the context returned by the scheduler
jmp __sthread_restore
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"
/*!
* Returns true (1) if the specified queue is empty. Otherwise, returns
* false (0).
*/
int queue_empty(Queue *queuep) {
assert(queuep != NULL);
return (queuep->head == NULL);
}
/*!
* Add the process to the head of the queue. If the queue is empty, add the
* singleton element. Otherwise, add the element as the tail.
*/
void queue_append(Queue *queuep, Thread *threadp) {
QueueNode *nodep = (QueueNode *) malloc(sizeof(QueueNode));
if (nodep == NULL) {
fprintf(stderr, "Couldn't allocate QueueNode\n");
abort();
}
nodep->threadp = threadp;
if(queuep->head == NULL) {
nodep->prev = NULL;
nodep->next = NULL;
queuep->head = nodep;
queuep->tail = nodep;
}
else {
queuep->tail->next = nodep;
nodep->prev = queuep->tail;
nodep->next = NULL;
queuep->tail = nodep;
}
}
/*!
* Get the first thread from the queue. Returns NULL if the queue is empty.
*/
Thread *queue_take(Queue *queuep) {
QueueNode *nodep;
Thread *threadp;
assert(queuep != NULL);
/* Return NULL if the queue is empty */
if(queuep->head == NULL)
return NULL;
/* Go to the final element */
nodep = queuep->head;
if(nodep == queuep->tail) {
queuep->head = NULL;
queuep->tail = NULL;
}
else {
nodep->next->prev = NULL;
queuep->head = nodep->next;
}
threadp = nodep->threadp;
free(nodep);
return threadp;
}
/*!
* Remove a thread from a queue.
*
* Returns 1 if the thread was found in the queue, or 0 if the thread was not
* found in the queue.
*
* NOTE: DO NOT use this operation in an assertion, e.g.
* assert(queue_remove(somequeuep, somethreadp));
* Assertions may be compiled out of a program, and if they are, any
* side-effects in the assertion's test will also be compiled out.
*/
int queue_remove(Queue *queuep, Thread *threadp) {
QueueNode *nodep;
assert(queuep != NULL);
assert(threadp != NULL);
/* Find the node in the queue with the specified thread. */
nodep = queuep->head;
while (nodep != NULL && nodep->threadp != threadp)
nodep = nodep->next;
if (!nodep)
return 0;
/* Found the node; unlink it from the list. */
if (nodep->prev != NULL)
nodep->prev->next = nodep->next;
if (nodep->next != NULL)
nodep->next->prev = nodep->prev;
/* Reset head and tail pointers */
if(queuep->head == nodep)
queuep->head = nodep->next;
if(queuep->tail == nodep)
queuep->tail = nodep->prev;
/* Delete the node. */
free(nodep);
/* We removed a node so return 1. */
return 1;
}
#ifndef QUEUE_H
#define QUEUE_H
#include "sthread.h"
/*! A single node of a queue of threads. */
typedef struct _queuenode {
Thread *threadp;
struct _queuenode *prev;
struct _queuenode *next;
} QueueNode;
/*!
* A queue of threads. This type is used to keep track of threads in
* various states within the user-space threaading library.
*/
typedef struct _queue {
QueueNode *head; /*!< The first thread in the queue. */
QueueNode *tail; /*!< The last thread in the queue. */
} Queue;
int queue_empty(Queue *queuep);
void queue_append(Queue *queuep, Thread *threadp);
Thread *queue_take(Queue *queuep);
int queue_remove(Queue *queuep, Thread *threadp);
#endif /* QUEUE_H */
/*
* General implementation of semaphores.
*
*--------------------------------------------------------------------
* Adapted from code for CS24 by Jason Hickey.
* Copyright (C) 2003-2010, Caltech. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <assert.h>
#include "sthread.h"
#include "semaphore.h"
/*
* The semaphore data structure contains TODO
*/
struct _semaphore {
/*
* TODO: define the semaphore data struct, and update the above
* comment to properly reflect your changes.
*/
};
/************************************************************************
* Top-level semaphore implementation.
*/
/*
* Allocate a new semaphore. The initial value of the semaphore is
* specified by the argument.
*/
Semaphore *new_semaphore(int init) {
Semaphore *semp = NULL;
/*
* TODO: allocate and initialize a semaphore data struct.
*/
return semp;
}
/*
* Decrement the semaphore.
* This operation must be atomic, and it blocks iff the semaphore is zero.
*/
void semaphore_wait(Semaphore *semp) {
/* TODO */
}
/*
* Increment the semaphore.
* This operation must be atomic.
*/
void semaphore_signal(Semaphore *semp) {
/* TODO */
}
/*
* Semaphores for the sthread package.
*
*--------------------------------------------------------------------
* Adapted from code for CS24 by Jason Hickey.
* Copyright (C) 2003-2010, Caltech. All rights reserved.
*/
#ifndef _SEMAPHORE_H
#define _SEMAPHORE_H
/*
* The semaphore's contents are opaque to users of the semaphore. The
* actual definition of the semaphore type is within semaphore.c.
*/
typedef struct _semaphore Semaphore;
/*
* Create a new seamphore.
* The argument is the initial value of the semaphore.
*/
Semaphore *new_semaphore(int init);
/*
* Decrement the semaphore.
* This operation will block if the semaphore value is zero.
*/
void semaphore_wait(Semaphore *semp);
/*
* Increment the semaphore.
*/
void semaphore_signal(Semaphore *semp);
#endif /* _SEMAPHORE_H */
/*
* The simple thread scheduler.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "sthread.h"
#include "queue.h"
#include "timer.h"
/*! The default stack size of threads, 1MiB of stack space. */
#define DEFAULT_STACKSIZE (1 << 20)
/************************************************************************
* Interface to the assembly.
*/
/*! This function must be called to start thread processing. */
void __sthread_start(void);
/*!
* The is the entry point into the scheduler, to be called
* whenever a thread action is required.
*/
void __sthread_switch(void);
/*!
* Initialize the context for a new thread.
*
* The stackp pointer should point to the *end* of the area allocated for the
* thread's stack. (Don't forget that x86 stacks grow downward in memory.)
*
* The function f is initially called with the argument given. When f
* returns, __sthread_finish() is automatically called by the threading
* library to ensure that the thread is cleaned up and deallocated properly.
*/
ThreadContext *__sthread_initialize_context(void *stackp, ThreadFunction f,
void *arg);
/************************************************************************
* Internal helper functions.
*/
/*
* The initial thread context is initialized such that this function
* will be called automatically when a thread's function returns.
*/
void __sthread_finish(void);
/*
* This function deallocates the memory associated with a thread
* when it terminates.
*/
void __sthread_delete(Thread *threadp);
/************************************************************************
* Global variables and scheduler-level queue operations.
*/
/*!
* The thread that is currently running.
*
* Invariant: during normal operation, there is exactly one thread in
* the ThreadRunning state, and this variable points to that thread.
*
* Note that at start-up of the threading library, this will be NULL.
*/
static Thread *current;
/*!
* The queue of ready threads. Invariant: All threads in the ready queue
* are in the state ThreadReady.
*/
static Queue ready_queue;
/*!
* The queue of blocked threads. Invariant: All threads in the blocked
* queue are in the state ThreadBlocked.
*/
static Queue blocked_queue;
/*!
* Add a thread to the appropriate scheduling queue, based on its state.
*/
static void enqueue_thread(Thread *threadp) {
assert(threadp != NULL);
switch(threadp->state) {
case ThreadReady:
queue_append(&ready_queue, threadp);
break;
case ThreadBlocked:
queue_append(&blocked_queue, threadp);
break;
default:
fprintf(stderr, "Thread state has been corrupted: %d\n", threadp->state);
exit(1);
}
}
/************************************************************************
* Scheduler.
*/
/*
* The scheduler is called with the context of the current thread,
* or NULL when the scheduler is first started.
*
* The general operation of this function is:
* 1. Save the context argument into the current thread.
* 2. Either queue up or deallocate the current thread,
* based on its state.
* 3. Select a new "ready" thread to run, and set the "current"
* variable to that thread.
* - If no "ready" thread is available, examine the system
* state to handle this situation properly.
* 4. Return the context of the thread to run, so that a context-
* switch will be performed to start running the next thread.
*
* This function is global because it needs to be called from the assembly.
*/
ThreadContext *__sthread_scheduler(ThreadContext *context) {
if (context != NULL) {
/* Add the current thread to the ready queue */
assert(current != NULL);
/* The thread could be Running, Blocked or Finished.
* If Running, we must switch it back to Ready.
*/
if (current->state == ThreadRunning)
current->state = ThreadReady;
/* If Running or Blocked, we just store the thread context and
* re-queue the thread. If Finished, then we need to deallocate
* the thread's memory, because it is done.
*/
if (current->state != ThreadFinished) {
current->context = context;
enqueue_thread(current);
}
else {
__sthread_delete(current);
}
}
/* Choose a new thread from the ready queue. If there are no more
* Ready threads, the system is either deadlocked, or we are finished
* running and the program can be terminated.
*/
current = queue_take(&ready_queue);
if (current == NULL) {
if (queue_empty(&blocked_queue)) {
fprintf(stderr, "All threads completed, exiting.\n");
exit(0);
}
else {
fprintf(stderr, "The system is deadlocked!\n");
exit(1);
}
}
current->state = ThreadRunning;
/* Return the next thread to resume executing. */
return current->context;
}
/************************************************************************
* Thread operations.
*/
/*
* Start the scheduler.
*/
void sthread_start(int timer) {
if (timer)
start_timer();
__sthread_start();
}
/*
* Create a new thread.
*
* This function allocates a new context, and a new Thread
* structure, and it adds the thread to the Ready queue.
*/
Thread * sthread_create(void (*f)(void *arg), void *arg) {
Thread *threadp;
void *memory;
/* Create a stack for use by the thread */
memory = (void *) malloc(DEFAULT_STACKSIZE);
if (memory == NULL) {
fprintf(stderr, "Can't allocate a stack for the new thread\n");
exit(1);
}
/* Create a thread struct */
threadp = (Thread *) malloc(sizeof(Thread));
if (threadp == NULL) {
fprintf(stderr, "Can't allocate a thread context\n");
free(memory);
exit(1);
}
/* Initialize the thread */
threadp->state = ThreadReady;
threadp->memory = memory;
threadp->context = __sthread_initialize_context(
(char *) memory + DEFAULT_STACKSIZE, f, arg);
enqueue_thread(threadp);
return threadp;
}
/*
* Return the pointer to the currently running thread.
*/
Thread * sthread_current(void) {
return current;
}
/*
* This function is called automatically when a thread's function returns,
* so that the thread can be marked as "finished" and can then be reclaimed.
* The scheduler will call __sthread_delete() on the thread before scheduling
* a new thread for execution.
*
* This function is global because it needs to be referenced from assembly.
*/
void __sthread_finish(void) {
fprintf(stderr, "Thread %p has finished executing.\n", (void *) current);
current->state = ThreadFinished;
__sthread_switch();
}
/*!
* This function is used by the scheduler to release the memory used by the
* specified thread. The function deletes the memory used for the thread's
* context, as well as the memory for the Thread struct.
*/
void __sthread_delete(Thread *threadp) {
assert(threadp != NULL);
free(threadp->memory);
free(threadp);
}
/*!
* Yield, so that another thread can run. This is easy: just
* call the scheduler, and it will pick a new thread to run.
*/
void sthread_yield() {
__sthread_switch();
}
/*!
* Block the current thread. Set the state of the current thread
* to Blocked, and call the scheduler.
*/
void sthread_block() {
current->state = ThreadBlocked;
__sthread_switch();
}
/*!
* Unblock a thread that is blocked. The thread is placed on
* the ready queue.
*/
void sthread_unblock(Thread *threadp) {
/* Make sure the thread was blocked */
assert(threadp->state == ThreadBlocked);
/* Remove from the blocked queue */
queue_remove(&blocked_queue, threadp);
/* Re-queue it */
threadp->state = ThreadReady;
enqueue_thread(threadp);
}
/*!
* Simple thread API.
*
* ----------------------------------------------------------------
*
* @begin[license]
* Copyright (C) 2003 Jason Hickey, Caltech
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Author: Jason Hickey
* @end[license]
*/
#ifndef _STHREAD_H
#define _STHREAD_H
/*!
* The structure of the thread's machine-context is opaque to the C code.
* Thread structs reference the context by a ThreadContext *, i.e. a void *
* (untyped pointer).
*/
typedef void ThreadContext;
/*!
* This enumeration defines all valid states of threads in the library.
* A thread can be running, ready, blocked, or finished.
*/
typedef enum {
/*!
* The thread is currently running on the CPU. Only one thread should be
* in this state.
*/
ThreadRunning,
/*!
* The thread is ready to run, but doesn't have access to the CPU yet. The
* thread will be kept in the ready-queue.
*/
ThreadReady,
/*!
* The thread is blocked and currently unable to progress, so it is not
* scheduled on the CPU. Blocked threads are kept in the blocked-queue.
*/
ThreadBlocked,
/*!
* The thread's function has returned, and therefore the thread is ready to
* be permanently removed from the scheduling mechanism and deallocated.
*/
ThreadFinished
} ThreadState;
/*! All details for recording the state of a thread. */
typedef struct _thread {
/*! State of the thread. */
ThreadState state;
/*!
* The start of the memory region being used for the thread's stack
* and machine context.
*/
void *memory;
/*!
* The machine context itself. This will be some address within the
* memory region referenced by the previous field.
*/
ThreadContext *context;
} Thread;
/*! The function that is passed to a thread must have this signature. */
typedef void (*ThreadFunction)(void *arg);
/*
* Thread operations.
*/
Thread *sthread_create(ThreadFunction f, void *arg);
Thread *sthread_current(void);
void sthread_yield(void);
void sthread_block(void);
void sthread_unblock(Thread *threadp);
/*
* The start function should be called *once* in
* the main() function of your program. This function
* never returns.
*/
void sthread_start(int timer);
#endif /* _STHREAD_H */
#include <stdio.h>
#include "glue.h"
void report_result(const char *msg, int pass) {
printf("%s: %s\n", pass ? "PASS" : "ERROR", msg);
}
/* A simple program for exercising the scheduler lock to make sure it
* works correctly. Note that this program does not test the concurrency
* behavior of the scheduler lock!
*/
int main() {
int ret;
/* Initially the scheduler lock should be 0, so it should be possible
* to acquire it.
*/
ret = __sthread_lock();
report_result("Initial call to __sthread_lock() should acquire lock.",
ret != 0);
/* Now that it is held, a second call should report failure to acquire
* the lock.
*/
ret = __sthread_lock();
report_result("Second call to __sthread_lock() should not acquire lock.",
ret == 0);
printf("Releasing lock.\n");
__sthread_unlock();
/* The lock should now be 0, so a third attempt to acquire the lock
* should succeed.
*/
ret = __sthread_lock();
report_result("Third call to __sthread_lock() should acquire lock.",
ret != 0);
/* Finally, a fourth attempt to acquire the lock should fail, since it is
* currently held.
*/
ret = __sthread_lock();
report_result("Fourth call to __sthread_lock() should not acquire lock.",
ret == 0);
return 0;
}
/*
* This module adds a timer interrupt to time-slice the threads. The
* details of operation of this file are not important for understanding
* HW7. The end result is to ensure that __sthread_schedule is called
* periodically.
*
* The principle is as follows:
*
* 1. Set up a periodic timer interrupt (the period is specified
* by the QUANTUM_* constants below).
*
* 2. In the signal handler for SIGALRM, set up the return context
* so that the signal handler returns to __sthread_schedule,
* instead than the point where the exception occurred. The
* return address is saved on the stack.
*
*--------------------------------------------------------------------
* Adapted from code for CS24 by Jason Hickey.
* Copyright (C) 2004-2010, Caltech. All rights reserved.
*/
#define __USE_GNU
#define _GNU_SOURCE
#include <ucontext.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <memory.h>
#include <sys/time.h>
#include "timer.h"
#include "glue.h"
/*
* Default timeslice quantum. This is configured for a 0.5ms timeslice so
* that context-switches occur frequently enough to manifest concurrency
* issues in the code. Normally we would want context-switches to be far
* less frequent so that we could minimize the overhead of the switch.
*
* This mechanism relies on the Linux High-Resolution Timer support that
* was integrated into the Linux 2.6.21 kernel, so as long as the OS version
* is at least that far along, and HRT is enabled in the kernel (which it
* usually is) then we should indeed get interrupts at the requested rate.
*/
#define QUANTUM_SEC 0
#define QUANTUM_USEC 500
/*
* Default size of signal stack is set to 64k.
*/
#define SIGNAL_STACKSIZE (1 << 16)
/*
* When the timer fires, set up the process context so that
* the signal handler returns to the __sthread_schedule function.
*/
static void timer_action(int signum, siginfo_t *infop, void *data) {
/*
* Get the scheduler lock.
* If the scheduler is already active, ignore this timer interrupt.
*/
if (__sthread_lock()) {
ucontext_t *contextp = (ucontext_t *) data;
greg_t *regs = contextp->uc_mcontext.gregs;
void **rsp = (void **) regs[REG_RSP];
void *rip = (void *) regs[REG_RIP];
/* Push the current program counter as the new return address */
*--rsp = rip;
/* Set the program counter to the __sthread_schedule function */
rip = (void *) __sthread_switch;
/* Save these two registers back to the context */
regs[REG_RSP] = (greg_t) rsp;
regs[REG_RIP] = (greg_t) rip;
}
}
/*
* Start the timer to generate periodic interrupts.
* Signals are handled on an alternate stack, so that
* we can manipulate the process stack in the signal
* handler.
*/
void start_timer() {
static char sigstack[SIGNAL_STACKSIZE];
struct sigaction action;
struct itimerval itimer;
stack_t stackinfo;
/* Handle signals on an alternate stack */
stackinfo.ss_sp = sigstack;
stackinfo.ss_flags = SS_ONSTACK;
stackinfo.ss_size = sizeof(sigstack);
if (sigaltstack(&stackinfo, (stack_t *) 0) < 0) {
perror("sigaltstack");
exit(1);
}
/* Install the signal handler */
memset(&action, 0, sizeof(action));
action.sa_sigaction = timer_action;
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
if (sigaction(SIGALRM, &action, (struct sigaction *) 0) < 0) {
perror("sigaction");
exit(1);
}
/* Start the periodic timer */
itimer.it_interval.tv_sec = QUANTUM_SEC;
itimer.it_interval.tv_usec = QUANTUM_USEC;
itimer.it_value.tv_sec = QUANTUM_SEC;
itimer.it_value.tv_usec = QUANTUM_USEC;
if (setitimer(ITIMER_REAL, &itimer, (struct itimerval *) 0) < 0) {
perror("setitimer");
exit(1);
}
}
/*
* This module adds a periodic timer to ensure that the
* __sthread_schedule function is called periodically.
*
*--------------------------------------------------------------------
* Adapted from code for CS24 by Jason Hickey.
* Copyright (C) 2004-2010, Caltech. All rights reserved.
*/
#ifndef _TIMER_H
#define _TIMER_H
/*
* Start the periodic timer.
*/
void start_timer(void);
#endif /* _TIMER_H */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment