CSE 30 -- Lecture 18 -- Dec 1


The due date for assignment 6 has been extended to midnight Friday. There was a question about how well commented this assignment should be. As I said, in this case comments are worth only 20% instead of the usual 40% of the grade; the comments need only explain the speedup techniques used in your code -- as a block of comments at the beginning. No need to have per-line comments, as we will not read your code. (Though you may wish to do so for your own benefit; it can help debugging.) On Monday morning I will make my 3.4 instructions per pixel solution available, with some (perhaps too terse) explanations on how it works.

In this lecture, I went over in some more detail over how the user level threads package would work. In our simple design, we have only

thread_spawn(void       (*fn)(void *),
             void       *arg,
             void       *stack);

thread_yield(void);

thread_exit(void);

thread_wait(void);
The big picture is that a process would initially have the main thread only, and that thread may call thread_spawn to spawn new threads, perhaps in some initialization code or as part of a work loop, and eventually the main thread will call thread_wait to await the termination of all other threads. Meanwhile, these other spawned threads may in turn spawn other threads, and context switch occurs only on calls to thread_yield. thread_exit is how a non-main thread exits.

We first look at thread_yield, which is responsible for the context switching.

The per-thread data, sketched out as C code, would look like:

struct thread {
	unsigned int	reg[32]; /* actually fewer */
	int		alive;
} thread_array[128];
where we have a compile-time limit of a maximum of 128 threads. (This is not the best threads package design; instead, we are trying to keep it simple for pedagogical reasons.)

thread_yield must save all the registers for the current thread and load the register set of the next thread to context switch. A global variable cur_thread would be used for round-robin scheduling: we scan down the thread array until we find the next alive thread, and make that the current thread, loading its registers.

I noted that the registers need not include the $t registers, nor the $pc. Upon a reload of the other registers, thread_yield will return to $ra per standard calling convention, so it will return to where that thread had last called thread_yield.

There are some design decisions that come up when we look at the birth and death of a thread. Do we allow the startup function for a thread to return? A reasonable design would be to permit it to do so, which would implicitly be a call to thread_exit. Do we allow the main thread to context switch using thread_yield? If not, it could make some of the thread management a little simpler: the main thread is a special case, and when thread_yield scans for a next available thread to run, it can detect the no-living-threads condition a little simpler. The no-living-threads condition causes the main thread's call to thread_wait to return.

In any case, thread_spawn is responsible for making sure that the $pc, $sp, $a0 and $ra registers have reasonable values in them ($a0 has the arg parameter). $ra would contain thread_exit or the address of code that will call it, so that when the thread function fn returns, the appropriate cleanup will happen. thread_exit is responsible for marking the thread as dead (and the thread_array slot available) and then context switching using thread_yield to the next runnable thread.


[ CSE home | CSE talks | bsy's home page | webster i/f | yahoo | lycos | altavista | pgp key svr | spam | commerce ]
picture of bsy

bsy@cse.ucsd.edu, last updated Wed Dec 3 20:00:26 PST 1997.

email bsy


Don't make me hand over my privacy keys!