189 lines
8.4 KiB
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}
|