Project 2 - User-Level Threads

Outline

Out: Monday, October 13
Part 1 and 2 Due: Monday, October 27 at 4:00 PM
Part 3 and 4 Due: Wednesday, November 5   Friday, NOvember 7 at 4:00 PM
Updated 10/23: added more instructions on webclient
Updated 10/27: Changed due date to Friday 11/7 and added more detail on write-up.

Tasks:

  1. Implement a user-level thread manager.
  2. Add a set of common synchronization primitives to your thread package.
  3. Implement a multithreaded web-server to test your thread package.
  4. Analyze your design and test results in a report.

Updates:

Assignment Goals

Background

In the beginning (well, the relative beginning), there was UNIX. UNIX supported multiprogramming; that is, there could be multiple independent processes each running with a single thread of control.

As new applications were developed, programmers increasingly desired multiple threads of control within a single process so they could all access common data. For example, databases might be able to process several queries at the same time, all using the same data. Sometimes, they used multiple processes to get this effect, but it was difficult to share data between processes (which is typically viewed as advantage, since it provides isolation, but here it was a problem).

What they wanted was multithreading support: the ability to run several threads of control within a single address space, all able to access the same memory (because they all have the same mapping function). The programmers realized that they could implement this entirely in the user-level, without modifying the kernel at all, if they were clever.

As you'll discover in the last part of this assignment, there were some problems with this approach, which motivated kernel developers to include thread support in the kernel itself (and motivated researchers to do it better; see Scheduler Activations).

Simplethreads

You will be using the simplethreads package for this assignment. It is available on spinlock/coredump in /cse451/projects/simplethreads-1.X.tar.gz (Where X is the release number number, which may be updated. Use the latest version. Please watch for updates as the project progresses.)

This project, since it does not involve modifying the kernel, does not require VMWare. The simplethreads package should work on any Linux 2.4 machine (it will not work on Linux 2.2 due to missing support for a necessary syscall), and other varieties of UNIX that support pthreads as well. (Please do not use attu.) It will not work with Cygwin. It has been reported to work with Mac OS X v10.1, but this hasn't been reproducable. Please send mail to the list if you get it working on Cygwin or Mac OS X.

To begin, copy that file to your work directory (/cse451/LOGIN) and run
tar -xvzf simplethreads-1.X.tar.gz
to expand it (if you are working on a different machine, see scp(1)).

Simplethreads contains a lot of files, but most are safe to ignore. Pay attention to:
Dir/FileContents
lib/ The simplethreads thread library itself.
lib/sthread_user.c Your part 1 and part 2 implementations go here.
lib/sthread_ctx.h Support for creating new stacks and switching between them.
lib/sthread_queue.h A simple queue that you may find useful.
include/ Contains sthread.h, the public API to the library
(the functions available for apps using the library).
test/ Test programs for the library.
web/ The multithreaded webserver for part 3.

Like many UNIX programs, simplethreads uses a configure script to determine parameters of the machine needed for compilation (in fact, you'll find many UNIX packages follow exactly the same steps as simplethreads for compilation). In the simplethreads-1.X directory, run ./configure to test all these parameters.

Finally, build the package by typing make. You can also build an individual part, such as the library, by running make in a subdirectory. Or, if you just want to compile one file, run make myfile.o (from within the directory containing myfile.c).

Note that, as you make changes, it is only necessary to repeat the last step (make).

One interesting feature of simplethreads is that applications built using it can use either the native, kernel-provided threads or the user-level threads that you'll implement. When running the test programs, use --sthread-pthread to select kernel threads or --sthread-user to select user threads (they default to kernel threads).

In summary, the steps are:

  1. Copy the tar.gz source to your directory.
  2. tar -xvzf simplethreads-1.X.tar.gz
  3. cd simplethreads-1.X
  4. ./configure
  5. make
  6. Run the programs in test/

To Add a Source File

If you add a new source file, do the following:
  1. Edit the Makefile.am in the directory containing the new file, adding it to the _SOURCES for the library/executable the file is a part of. E.g., to add a new file in the lib/ directory that will become part of the sthread library, add it to the libsthread_la_SOURCES line.
  2. From the top-level directory (simplethreads-1.X), run automake.
  3. Also from the top-level directory, run ./configure.
  4. Your file is now added; run make as usual to build it.

To Add a New Test Program

  1. Edit test/Makefile.am. Add your program to the list bin_PROGRAMS. Create new variables prog_SOURCES and prog_LDADD following the examples of the programs already there. For example, if the new program is named test-silly, add:
    test_silly_SOURCES = test-silly.c other sources here
    test_silly_LDADD = $(ldadd)
  2. Follow steps 2-4 above.

The Assignment

Part 1: Implement Thread Scheduling

For part 1, we give you:

  1. An implementation of a simple queue (sthread_queue.h).
  2. A context-switch function that, given a new stack pointer, will switch contexts (sthread_ctx.h).
  3. A new-stack function that will create and initialize a new stack such that it is ready to run.
  4. Skeleton routines for a user-level thread system (sthread_user.c).

It is your job to complete the thread system, implementing:

  1. Data structures to represent threads (struct _sthread in sthread_user.c).
  2. A thread creation routine (sthread_user_create()).
  3. A thread destruction routine (sthread_user_exit()).
  4. A simple non-preemptive thread scheduler.
  5. A mechanism for a thread to voluntarily yield, allowing another thread to run (sthread_user_yield()).
  6. A routine to initialize your data structures (sthread_user_init()).

The routines in sthread_ctx.h do all of the stack manipulation, PC changing, register storing, and nasty stuff like that. Rather than focusing on that, this assignment focuses on managing the thread contexts. Viewed as a layered system, you need to implement the grey box below:

At the top layer, applications will use the sthread package (through the API defined in sthread.h). sthread.c will then call either the routines in sthread_pthread.c or your routines in sthread_user.c (it chooses based on the value passed to sthread_init()). Your sthread_user.c, in turn, builds on the sthread_ctx functions (as described in sthread_ctx.h).

Applications (the top-layer) may not use any routines except those listed in sthread.h. They must not know how the threads are implemented; they simply request threads be created (after initializing the library), and maybe request yeilds/exits. For example, they have no access to sthread_queue. Nor do they keep lists of running threads around; that is the job of the grey box.

Similarly, your grey box - sthread_user.c - should not need to know how sthread_ctx is implemented. Do not attempt to modify the sthread_ctx_t directly; use the routines declared in sthread_ctx.h.

Recommended Procedure

  1. Figure out what each function you need to implement does. Look at some of the test programs to see usage examples.
  2. Examine the supporting routines we've provided (primarily in sthread_queue.h and sthread_ctx.h).
  3. Design your threading algorithm: When are threads put on the ready queue? When are threads removed? Where is sthread_switch called?
  4. Figure out how to start a new thread and what to do about the initial thread (the one that calls sthread_user_init).
  5. Talk to your fellow students and the TAs about your design.
  6. Implement it.
  7. Test it. The test programs provided are not adequate; for full confidence in your implementation, you'll need to create some of your own.

Hints

Part 2: Implement Mutexs and Condition Variables

For part 2, you'll use the thread system you wrote in part 1, extending it to provide support for mutexs (a.k.a. locks) and condition variables. Skeleton functions are again provided in lib/sthread_user.c. You need to:

The non-preemptive nature of this assignment aids you greatly in your tasks (since it gives you atomic critical sections). However, you should think about what you would need to do if your threads were preemptive; add comments to the routines indicating precisely where the critical sections are and what appropriate protection might be applicable (e.g. how you would use a spinlock or other atomic operation).

Hints

Part 3: Implement a Multithreaded Web Server

Every web server has the following basic algorithm:

  1. Web server starts and listens (see listen(2)) for incoming connections.
  2. A client (i.e. web browser) opens a connection.
  3. The server accepts (see accept(2)) the connection.
  4. The client sends an http request.
  5. The server services the request by:
    1. Parsing the request.
    2. Finding the requested file.
    3. Reading the file.
    4. Sending a set of http headers, followed by the contents of the file, to the client.
    5. Closing the connection.
  6. The client displays the file to the user.

The sioux webserver in the web directory impelements the server side of the above algorithm.

For part 3 you will make sioux into a multithreaded web server. It must use a thread pool approach; it should not create a new thread for each request. Instead, incoming requests should be distributed to a pool of waiting threads (this is to eleminate thread creation costs from your experimental data). Make sure your threads are properly and efficiently synchronized. Use the routines you implemented in part 2.

Note that you are implementing an application now. That means the only interface to the thread system that you should use is that described by sthread.h (as distributed in the tar.gz). Do not use functions internal to sthreads directly from sioux.

The webserver should accept the --sthread-pthread and --sthread-user arguments (see the provided routine sthread_parse_impl() in sthread.h). This will allow you to test with both user-level and kernel-level threads and compare.

You should also accept a command-line flag indicating how many threads to use.

In testing, you may encounter "Address already in use" errors. TCP connections require a delay before a given port can be reused, so simply waiting a minute or two should be sufficient.

Part 4: Analysis and Report

Design Discussion

Your report should include a section on the design of your user-level threads, synchronization primitives, and webserver. Discuss the goals of each, how well your design meets those goals, and the tradeoffs that were made. Justify your design decisions as compared with other possible designs. Goals for some aspects (e.g. synchronization) are discussed in the text; for others, you need to determine what appropriate goals are. Some goals you may wish to consider for all systems code are speed, complexity, and usefulness.

Experiment

For your analysis, design an experiment to determine the effectiveness of user-level threads as compared to kernel-level threads in your webserver. This experiment should be conducted in the standard scientific method. It should explore the performance of the server under a variety of different conditions chosen to confirm or deny various aspects of your hypothesis.

You should see how your web server responds (i.e. how good is its throughput) with various number of simultaneous clients and with various numbers of threads in the thread pool. Is there a maximum number of threads that is useful?

Include a presentation of your experimental design, data, analysis, and conclusions in your report.

Conclusions

Discuss conclusions you have reached from this project. What recomendations do you have for designers of thread systems? Can you recommend user threads or kernel threads? Why? What other features might be useful?

Using the Web Benchmark

This will be installed by the time you need it.

The WebStone web benchmark tool has been installed on spinlock and coredump as /cse451/projects/webclient. It measures the throughput and latency of a webserver under varying loads. It simulates a number of active clients, all sending requests in parallel (it does this by forking several times). Each client requests a set of files, possibly looping through that set multiple times. When the test is complete, each client outputs the connection delay, response time, bytes transfered, and throughput it observed (depending on the server, the clients may all observe very similar results, or the data may vary widely). The tool takes the following arguments:

All of the above parameters are required. The URLLIST file should contain one relative URL (just the file name) per line followed by a space and then a number (the number is the weight representing how often to request the file relative to other files in the list - most likely, 1 is the value you want).

For example, to test a webserver on almond.cs.washington.edu, with two simulated clients each fetching the index.html file twice, one would run:

/cse451/projects/webclient -w almond.cs.washington.edu -p 12703 -l 2 -n 2 -u ./urls
Where the file urls would contain the following single line:
/index.html 1

Turnin

Parts 1 and 2

In your top-level simplethreads directory, run make dist. This will produce a file named simplethreads-X.X.tar.gz. Make a new directory called username where username is your CSE login and move simplethreads-X.X.tar.gz into that directory. Submit this directory using the turnin program under project name project2a by 4:00 PM Monday, October 27. Turnin will not work on coredump/spinlock, so you'll need to use attu.

If you have added any files, run tar -tzf simplethreads-X.X.tar.gz and check to make sure your new files are listed.

Part 3 and 4

Follow the same instructions as for parts 1 and 2. Turnin a final version of your code including any scripts or other files you used in your experiment by 4:00 PM Friday November 7. It should be submitted under the project name project2b.

You should turn in your report on paper in class on Friday, November 7