CS520
Fall 2017
Program 4
Due Wednesday November 1


Implement threads to run on a single Intel 64 core.

The two fundamental primitives that you should implement first are:

long thread_create(void (*func)(void*), void* info);
void thread_yield(void);

The thread_create primitive is passed two parameters. The first parameter is a function that should be executed by the newly created thread. The second parameter is a parameter to be passed to that function.

The thread_create primitive returns 0 if it cannot create the thread. Otherwise it returns a non-zero thread identifier.

When a thread is created, it should be given a stack of 65536 bytes.

A FIFO list of active threads should be maintained. This list should be initialized upon the first call to a thread primitive and should initially contain the "main" thread, the thread of control created "automatically" when the process was created. When thread_create is called, the newly created thread should be placed on the end of the list. Note: a newly created thread does not start executing immediately. Rather it is simply placed at the end of the list of active threads.

When thread_yield is called, the currently executing thread should be moved to the end of the list of active threads and the thread that is now at the front of the list should become the thread that is now executing. If there is only one active thread, then that thread continues executing.

There is no status associated with a call to thread_yield (i.e. it must succeed) so the primitive returns nothing.

When the main thread exits, then the process exits and all created threads are therefore terminated. When a created thread returns from its given function, then the thread is terminated, it is removed from the list of active threads, and any associated memory should be freed. Normally the next thread in the list of active threads should now execute. If there are no other active threads, then an error message should be printed to stderr and the program should be halted (call exit(-1)).

Your implementation of thread_create and thread_yield must be done first and will be worth 70% of the points for this assignment.

An additional 15% can be obtained by implementing a thread_mutex_t type and the following primitives:

int thread_mutex_init(thread_mutex_t *mutex);
int thread_mutex_lock(thread_mutex_t *mutex);
int thread_mutex_unlock(thread_mutex_t *mutex);

The thread_mutex_init primitive shall initialize the mutex referenced by its argument. Attempting to initialize an already initialized mutex results in undefined behavior, as does operating on a mutex that is not yet initialized. If successful, the primitive shall return 1. The primitive fails if passed a NULL pointer.

A call to thread_mutex_lock shall lock the mutex object referenced by its argument. If the mutex is already locked, the calling thread shall block until the mutex becomes available. This primitive shall return with the mutex object in the locked state with the calling thread as its owner. If successful, the primitive shall return 1. The primitive fails if passed a NULL pointer or if the referenced mutex is already locked by the calling thread.

The thread_mutex_unlock primitive shall release the mutex object referenced by its argument. If there are threads blocked on the mutex object when thread_mutex_unlock is called, resulting in the mutex becoming available, the longest waiting thread shall get the lock. That is, the wait queue for the mutex should be a FIFO queue. The thread that now owns the lock should be moved to the end of the ready-to-run list. If successful, the primitive shall return 1. The primitive fails if passed a NULL pointer or if the mutex is not in a locked state or if the caller does not own the mutex.

An additional 15% can be obtained by implementing a thread_cond_t type and the following primitives:

int thread_cond_init(thread_cond_t *cond);
int thread_cond_wait(thread_cond_t *cond, thread_mutex_t *mutex);
int thread_cond_signal(thread_cond_t *cond);

The thread_cond_init primitive shall initialize the condition variable referenced by its argument. Attempting to initialize an already initialized condition variable results in undefined behavior, as does operating on a condition variable that is not yet initialized. If successful, the primitive shall return 1. The primitive fails if passed a NULL pointer.

The thread_cond_wait primitive shall be called with the mutex referenced by its second argument locked by the calling thread. The primitive shall release the mutex and cause the calling thread to block on the condition variable referenced by its first argument. Upon successful return, the mutex shall have been locked and shall be owned by the calling thread. If successful, the primitive shall return 1. The primitive fails if passed a NULL pointer or if the referenced mutex is not locked by the calling thread.

The thread_cond_signal primitive shall unblock a thread blocked on the condition variable referenced by its argument, if any threads are blocked. The longest waiting thread will be unblocked. That is, the queue associated with the condition variable is a FIFO queue. Before proceeding to execute, the unblocked thread will contend for the mutex that it associated with the condition variable when it executed the earlier wait that caused it to block. That is, if the mutex is locked, then the unblocked thread will be placed on the end of the queue of threads waiting for the mutex. If the mutex is unlocked, then the unblocked thread is given the mutex and is put on the end of the ready-to-run queue. The primitive shall have no effect if there are no threads currently blocked on the condition variable. If successful, the primitive shall return 1. The primitive fails if passed a NULL pointer.

If the ready-to-run queue becomes empty, then deadlock has occurred. Print an appropriate error message to stderr and terminate the program. This could occur when a thread terminates, or when a thread is blocked by a call to thread_mutex_lock or thread_cond_wait.

You must first implement the thread primitives, then the mutex primitives, and finally the condition variable primitives.

Your implementation should be performed using both C and Intel 64 assembler. Use C for as much of the code as possible and only use assembler when necessary.

Place all your C code in a file called thread.c and place all your the assembler code in a file called thr_asm.s. You will also need to provide a header file called thread.h to contain your definitions of the thread_mutex_t and thread_cond_t types, as well as the prototypes for all the primitives. A sample thread.h file, with stubs for the two types, is provided in ~cs520/public/prog4.

The directory ~cs520/public/prog4 also contains test programs that you can link with your code. Use these files to start your testing, but you will probably need to create other files in order to do more thorough testing.

Your program will be graded primarily by testing it for correct functionality. In addition, remember, you may lose points if your program is not properly structured or adequately documented. Coding guidelines are given on the course overview webpage.

Your programs will be graded using agate.cs.unh.edu so be sure to test in that environment. Your programs will be compiled using these gcc flags: -g -Wall -std=c99.

Your programs should be submitted for grading from agate.cs.unh.edu. To turn in this assignment, type:
~cs520/bin/submit prog4 thread.c thr_asm.s thread.h

Submissions can be checked by typing:
~cs520/bin/scheck prog4

This assignment is due Wednesday November 1. The standard late policy concerning late submissions will be in effect. See the course overview webpage.

Remember: as always you are expected to do your own work on this assignment. Copying code from another student or from sites on the internet is explicitly forbidden!


Last modified on October 25, 2017.

Comments and questions should be directed to pjh@cs.unh.edu