\documentstyle[tgrind, a4]{article} \title{The {\sc Rex} lightweight process library} \author{Stephen Crane\\ (jsc@doc.ic.ac.uk)\thanks{Thanks to Mark Little (m.c.little@ncl.ac.uk) for the Linux port}} \begin{document} \maketitle This document describes the interface to and the behaviour underlying my threads library for Rex.\footnote{Available as lwp.tar.gz by anonymous ftp from gummo.doc.ic.ac.uk:/rex. Rex (Esprit project 2080) was axed by Men in Suits.} It has been tested on Sun-3, Sun-4, Mips, 386-BSD and Linux systems. Counting semi-colons, it is 260 lines long, including support for the different architectures (this figure includes variable- but not function-declarations). A word from our sponsor: \begin{quote} This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but {\sc WITHOUT ANY WARRANTY}; without even the implied warranty of {\sc MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE}. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. \end{quote} (Note that while this library is protected by the GNU copyleft, it is not supported by the Free Software Foundation.) \section{Threads} Threads are prioritised and non-preemptive. Operations supported on threads are: \begin{tgrind} \L{\LB{}\Tab{8}{\K{struct} pcb *initlp (\K{int} priority)}} \L{\LB{}\Tab{8}{\K{struct} pcb *creatp (priority, entry, size, argc, argv, envp)}} \L{\LB{}\Tab{8}{\K{void} readyp (\K{struct} pcb *p)}} \L{\LB{}\Tab{8}{\K{void} yieldp (\K{void})}} \L{\LB{}\Tab{8}{\K{void} *getenvp (\K{struct} pcb *p)}} \L{\LB{}\Tab{8}{\K{void} setenvp (\K{struct} pcb *p, \K{void} *)}} \L{\LB{}\Tab{8}{\K{void} suicidep (\K{void})}} \L{\LB{}\Tab{8}{\K{void} destroyp (\K{struct} pcb *p)}} \end{tgrind} \begin{description} \item[initlp] initialises the threads runtime, creating a thread with specified priority for the invoker. \item[creatp] creates a new thread with specified {\em priority}, {\em entry} point, with a stack of {\em size}, {\em argc} arguments in {\em argv} and a user-defined environment pointer. \item[getenvp] returns the environment pointer associated with the given thread. If the thread is null, the current thread is assumed. \item[setenvp] reassigns the environment pointer associated with the given thread. \item[readyp] makes the specified thread ready to run, or the current thread if null. \item[yieldp] makes the current thread ready to run. If no thread of higher priority is runnable, the current thread will run. \item[suicidep] marks the invoking thread as dead. It will never be rescheduled. \item[destroyp] marks the specified thread as dead. It will be removed at the next reschedule. If it is currently running, it will be unaffected until the next reschedule. \end{description} \section{Semaphores} For synchronisation, counting semaphores are provided. Available operations are: \begin{tgrind} \L{\LB{}\Tab{8}{\K{struct} sem *creats (\K{int} count)}} \L{\LB{}\Tab{8}{\K{void} signals (\K{struct} sem *s)}} \L{\LB{}\Tab{8}{\K{void} waits (\K{struct} sem *s)}} \end{tgrind} \begin{description} \item[creats] allocates a new semaphore from the heap and initialises its count. \item[signals] increments the semaphore's count, makes a waiting process ready if there is one. If the readied process's priority is greater than that of the signaller, a reschedule is done. \item[waits] decrements the semaphore's count. If it becomes negative, the current process is suspended and a reschedule is done. \end{description} \section{Signals} The library is concerned with two types of signal, {\sc sigio} and {\sc sigalrm}. These signals are normally blocked until the null process is scheduled. It uses {\tt sigpause()} to reenable them and wait for one to arrive. While awaiting a signal, the null process `runs' at maximum priority. Thus users will not be able to cause a reschedule from their handlers. When {\tt sigpause()} returns, the signal will have been handled and the null process drops back down to the lowest priority and yields to any thread which has been made ready to run from the user-level handler. These semantics make the library rather unresponsive to signals in the presence of busy processes. If a more responsive system is required, the constant {\sc LCOUNT} may be changed. This value determines the number of times the {\tt reschedp ()} function must be called before signals are re-enabled. If given a value of {\tt 1}, it will affect context-switching time by about 50\%. Its default value is {\tt -1}. \subsection{Input and output} Input and output present a problem to threads, because they require calls to the underlying {\sc Unix} system, which will block not only the invoking thread, but also all others in the process. Thus, in general, a thread must wait until the descriptor on which I/O is to be done becomes ready for the operation. This is done by trapping {\sc sigio}. Two routines are provided: \begin{tgrind} \L{\LB{}\Tab{8}{\K{int} sigioset (\K{int} fd, \K{void} (*han) (\K{void} *, \K{int}), \K{void} *ctx)}} \L{\LB{}\Tab{8}{\K{int} sigioclr (\K{int} fd)}} \end{tgrind} The general model adopted for processing {\sc sigio} is to install a {\em handler} routine for the I/O descriptor using {\em sigioset} and remove it using {\em sigioclr}. The user is responsible for setting up the device correctly to generate {\sc sigio}. When {\sc sigio} arrives for the descriptor, the handler will be called with a context pointer as its argument. (In C++, this context is the instance pointer of the invoking thread.) \subsection{The timer} A single routine is provided to block the invoking thread for the specified time: \begin{tgrind} \L{\LB{}\Tab{8}{\K{void} delayp (\K{int} n)}} \end{tgrind} This routine blocks the invoker for {\em at least\/} the time specified. If this is zero, a reschedule is done. Delays are implemented as a delta queue, using {\sc sigalrm}. $n$ specifies a microsecond delay which is of limited utility in practice. \section{Performance} \begin{figure}[htb] \begin{center} \begin{tabular}{||l|c|c|c||} \hline Arch & ctxsw & creat & comment \\ \hline sun3 & 308 & 778 & 3/240 \\ \hline 386bsd & 186 & 464 & 486/33 \\ \hline sun4 & 96 & 436 & IPX \\ \hline sun4 & 59 & 212 & Sparc-10 \\ \hline linux & 56 & 382 & 486-DX2/50 \\ \hline mips & 17 & 85 & Decstation \\ \hline \end{tabular} \caption{Performance with architecture (times in microseconds).} \end{center} \end{figure} \begin{description} \item[sun3] has very lightweight process initialisation, compared with context switching. \item[sun4] has a high context switch time as a result of {\tt setjmp ()} and {\tt longjmp ()} implementations. Process initialisation is also relatively heavyweight: it requires two calls to {\tt setjmp ()} and one to {\tt longjmp ()}. \item[mips] provides its own context switching in assembly language. \end{description} \section{Porting to another architecture} Although the threads library is quite portable, a few guidelines should be observed when moving to a new architecture. \begin{itemize} \item Create two new files for your architecture/kernel (e.g. {\tt sun4.c} and {\tt sun4.h}). The `.c' file should contain any routines which your version of {\sc Unix} is missing and a thread initialisation routine: \begin{tgrind} \L{\LB{}\Tab{8}{\K{void} initp (\K{struct} pcb *, \K{void} *)}} \end{tgrind} The `.h' file contains any machine-specific definitions. \item If {\tt setjmp ()} and {\tt longjmp ()} don't work on your machine (for example the {\sc Ultrix} {\tt longjmp ()} implementation checks that the frame being jumped to is an ancestor of the current one), you will have to write {\tt savep ()} and {\tt restorep ()} and put the following define into your machine-specific header file: \begin{tgrind} \L{\LB{}\Tab{8}{\K{\#define} OWN\_CONTEXT\_SWITCH}} \end{tgrind} \item Compile and run the three test programs: {\tt producer.c}, {\tt timer.c} and {\tt bm.c}. \item Add the name of the architecture to `config'. \item Send {\tt new-arch.[ch]}, any context diffs for the rest of the library and the output of {\tt bm.c} to {\tt jsc@doc.ic.ac.uk}. Also please let me know where you got the library from. \end{itemize} \end{document}