Project 1: Mondo Threads Library

CS33100 - Advanced Operating Systems
Fall, 2002
Due : Friday, October 18, 2002, 11:59 P.M.

Introduction

To get better performance, computers are increasingly relying on multiple CPUs and symmetric multiprocessing. A common programming model for these machines is to use threads. Although most operating systems provide some kernel-level support for threading, threads are often implemented in the form of a programming library that operates entirely within a user-level process (e.g., POSIX threads).

In this project, you will implement a small threads library for Solaris. Your library will include some of the the basic functionality of pthreads, but will include some additional features to make your life more "interesting." To implement your library, you will utilize a variety of low-level Solaris system calls related to lightweight processes (LWPs) and you will need to worry about issues such as signal handling, thread scheduling, and synchronization. Your library will not use any existing threads library (i.e., you may not use Solaris threads or POSIX threads in your implementation). You may, however, use low-level Solaris system calls.

For development, you will be using a 4 processor Sun Enterprise 420R server (schlitz.cs.uchicago.edu). You may use C or C++ to implement your library.

Threads library

Your threads library must implement the following functions.

Thread creation and destruction

int mthread_init(void)

Initialize the threads library. User programs execute this prior to creating any threads.

int mthread_create(mthread_t *thr, void (*func)(void *), void *arg, int flags, size_t stacksize)

Creates a new thread. The newly created thread id is placed in thr. func is the function that will start executing when the thread begins. arg is the argument passed to func. flags is the bitwise-or of the following values:
MTHREAD_NEW_LWP     - Create a new LWP
MTHREAD_DETACH      - Detached thread
MTHREAD_BOUND       - Bind thread to a new LWP.
MTHREAD_SUSPEND     - Thread is started in a suspended state.
MTHREAD_MONDO       - Mondo thread
stacksize is the number of bytes to use for the thread stack. If set to 0, then the default stack size is used (typically 1 Mbyte). Returns 0 on success, and a negative value for an error.

int mthread_join(mthread_t thr, void **status)

Waits for thread thr to call mthread_exit() or mthread_detach(). The return value of the specified thread is placed in status. status is set to NULL when a thread detaches. Returns 0 on success.

int mthread_detach(mthread_t thr)

The specified thread is detached. When a detached thread terminates, its resources are automatically recovered. Note: It is not possible to join a detached thread.

void mthread_exit(void *status)

The current thread is terminated. status is an optional pointer to data that will be passed to a matching mthread_join() call. This data may not be placed on the thread's stack (which is reclaimed upon termination).

mthread_t mthread_self(void)

Returns the thread id of the calling thread.

int mthread_equal(mthread_t t1, mthread_t t2)

Returns true if thread t1 and t2 are the same thread.

int mthread_yield()

The calling thread yields. If other threads can run, the calling thread is simply placed on a ready queue and rescheduled. If no other threads are available to run, the calling thread continues to execute.

int mthread_suspend(mthread_t thr)

Suspends the execution of thread thr. If thread is already suspended, does nothing.

int mthread_resume(mthread_t thr)

Resumes execution of thread thr. If the thread is already running, this function has no effect.

Locking

int mthread_mutex_init(mutex_t *mtx)

Initialize a mutex lock. Locks are initialized in the unlocked state.

int mthread_mutex_lock(mutex_t *mtx)

Lock a mutex. If already locked, a thread blocks until the lock is released.

int mthread_mutex_unlock(mutex_t *mtx)

Unlock a mutex. If any other threads are waiting on the lock, one of them is awakened and given the lock.

int mthread_sema_init(sema_t *s, unsigned int val)

Initialize a semaphore with initial value val.

int mthread_sema_post(sema_t *s)

Increase semaphore value. If any threads are waiting on the signal, one is awakened.

int mthread_sema_wait(sema_t *s)

If semaphore value is 0, go to sleep. If non-zero, decrement semaphore value and continue.

Condition variables

(extra credit)

int mthread_cond_init(cond_t *cv)

Initialize the condition variable cv.

int mthread_cond_wait(cond_t *cv,mutex_t *lck, void (*cont)(void *), void *arg)

The calling thread goes to sleep on the condition variable cv. lck is a mutex lock which must be locked prior to calling mthread_cond_wait(). When the calling thread goes to sleep, the lock is released. The cont and arg functions define an optional continuation function. If NULL, execution resumes with the next statement. If cont is supplied, the thread gives up its stack and context when it goes to sleep and resumes execution in a new thread starting with the function cont when it resumes.

int mthread_cond_signal(cond_t *cv)

Signal the condition variable cv. One of the threads waiting on cv is awakened.
Proper use of condition variables requires special attention to locking. Here is an example:
mutex_t    m;
cond_t     cv;
...
mthread_mutex_init(&m);
mthread_cond_init(&cv);
...

void foo() {
    mthread_mutex_lock(&m);
    mthread_cond_wait(&cv,&m, NULL, NULL);   /* Sleep until signalled */
    mthread_mutex_unlock(&m);
}

void bar() {
    mthread_mutex_lock(&m);
    mthread_cond_signal(&cv);
    mthread_mutex_unlock(&m);
}

Examples

Simple thread creation

#include "thread.h"
#include <stdio.h>

void howdy(void *arg) {
   int n = *((int *)arg);
   int i;
   for (i = 0; i < n; i++) {
      printf("Howdy  %d\n", i);
   }
   mthread_exit(NULL);
}

int main() {
   mthread_t  tid1;
   int n1 = 10;
   void *status;
   
   /* Start the thread */
   mthread_init(&tid1, howdy, &n1, MTHREAD_NEW_LWP, 0);
   
   /* Wait for the thread */
   mthread_join(tid1, &status);
   exit(0);
}

Implementation Thoughts

I do not know the "best" way to implement this project---you will have to experiment. To implement the library, you should use the low-level Solaris LWP interface. There is almost no documentation on this other than the man pages. You will probbaly want to use the following functions (among others):
_lwp_create
_lwp_makecontext
_lwp_exit
_lwp_setprivate
_lwp_wait
_lwp_suspend
_lwp_continue
_lwp_cond_wait
_lwp_timed_wait
_lwp_cond_broadcast
_lwp_cond_signal
_lwp_kill
_lwp_mutex_lock
_lwp_mutex_unlock
_signotifywait
_lwp_sigredirect
I will also be implementing the library and will share notes as necessary.

What to hand in

You must turn in the following: To test your library, I will build it using your makefile and try to compile a number of simple test programs that exercise various library features. Most of these test programs will be made available prior to handin--don't worry. More details soon.