empserver/src/lib/lwp/misc/lwp.tex

189 lines
8.4 KiB
TeX

\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}