Homework 2 (part A) & 3 (part B)
(handin instructions)

Use the SUN Cereal machines in Hill 248 to run all the programs.


(A) User level threads


(1) Implement a user-level non-preemptive threads library. Along with your code, you should hand in a makefile that creates a threads library (called libuthreads-np.a) that anyone wishing to use your threads package (in particular, Suresh :) can simply link to.

The API exported by your thread library should have the following primitives:

For the scheduler, use a simple round robin scheme. Measure the
latencies of thread creation and switching threads using your thread
library, and compare it with the (kernel) thread library available on
Solaris.

Hint
You may want to check man pages for getcontext, makecontext,
swapcontextsetcontext etc.


(2) Extend the thread package from part (1) to handle pre-emptive scheduling. A thread can be pre-empted if it is executing in the application code. For pre-emption, set a high-resolution timer, and when the timer expires, do the scheduling. A thread cannot be pre-empted when it is executing the scheduler code or library functions (since they are not guaranteed to be re-entrant.)

To find out whether the thread is executing in the application code, one solution is to to use two dummy functions (DUMMY_BEGIN() and DUMMY_END()) to mark the beginning and end of the user code. Now, scheduling can be done only if, at the time of the timer expiration, the return address (program counter) lies between your begin and end markers.

Hint

The code fragment given above for question C can be used to find the program counter here also.

(The makefile should create a library called libuthreads-p.a.)


(3) Augment your thread library from part (2) above to provide support for mutexes and condition variables. You need to define two types, mutex_type, and condvar_type.

You need to support the following function calls in your thread library.

mutex_init(mutex_type *mutex) - Initialize mutex

mutex_lock(mutex_type *mutex) - Lock mutex

mutex_unlock(mutex_type *mutex) - Unlock mutex 

cond_init(condvar_type *cond) - Initialize cond

cond_wait(condvar_type *cond, mutex_type *mutex) - Wait on cond

cond_signal(condvar_type *cond) - Signal and release
1 thread if any are waiting on cond

cond_broadcast(condvar_type *cond) - Release all threads
waiting on cond

Hint

Check the man pages for the above calls to understand their semantics correctly.

(The makefile should create a library called libuthreads-pmc.a.)


(B) Remote File Server (Client/Server programming using sockets)


1) Write C programs to measure and compare the file I/O bandwidth and latency for read and write operations (for various block sizes) using the following 2 methods:

Hints

To measure the bandwidth, use a range of large block sizes and repeat the operations over a period of time and find the average bytes/sec throughput the operation can achieve.

To measure latency, repeat the operations with very small block sizes and measure the average time taken for a single operation.

Try to filter out the effects of the kernel buffer cache - by accessing different files over different days.


2) Write a remote file server program that supports the following file operations: creat, open, seek, read, write, close and unlink.

    For the client side, you should provide a library that an application can link to access the files at the server. The library should implement the following interfaces:

   u_init() should do the connection setup and any initialization at the client side. Rest of the functions have the same semantics as normal file system operations.

   The server should be implemented as a multi-threaded daemon process. One of the threads will be just waiting in a loop for client requests to arrive at a UDP port. Once the requests arrive, it will queue the request in a task queue and return to listening. One or more 'worker' threads will be waiting for tasks to appear in the queue. When tasks arrive, they will pick them up one by one and execute them, and return the result to the client.

(Why is multi-threading required? If you implement this in stages, implement a single-threaded sequential server first, which gets a request, processes the request, returns the results and then goes back to listening for requests. Compare the performance of this with the multithreaded one.)

   Measure the performance of the remote file operations and compare them with the results from part (1) and justify the numbers (in terms of the additional protocol and  communication costs etc.) Analyse the effect of the UDP packet size (block size of the communication), if any, on the performance. Measure the scalability performance of the server by testing with multiple clients (1, 2 and 4 clients).

So for this part, the performance measurements will have a few orthogonal parameters - the operation's block size, communication block size and varying number of clients.

Extra Credit: Implement some kind of caching scheme and pre-fetching at the server that will improve the performance of the server.

Hint

For UDP/socket programming, refer to "Unix Network Programming" by Richard Stevens or other similar books on Advanced Unix/network programming.

(You will be provided a sample benchmark program soon.)