Chapter 2 -- The LWP Lightweight Process Package

Section 2.1: Introduction

This chapter describes a package allowing multiple threads of control to coexist and cooperate within one unix process. Each such thread of control is also referred to as a lightweight process, in contrast to the traditional unix (heavyweight) process. Except for the limitations of a fixed stack size and non-preemptive scheduling, these lightweight processes possess all the properties usually associated with full-fledged processes in typical operating systems. For the purposes of this document, the terms lightweight process, LWP, and thread are completely interchangeable, and they appear intermixed in this chapter. Included in this lightweight process facility are various sub-packages, including services for locking, I/O control, timers, fast time determination, and preemption.
The Rx facility is not the only client of the LWP package. Other LWP clients within AFS include the file Server, Protection Server, BOS Server, Volume Server, Volume Location Server, and the Authentication Server, along with many of the AFS application programs.

Section 2.2: Description

2.2.1: sec2-2-1 LWP Overview

The LWP package implements primitive functions that provide the basic facilities required to enable procedures written in C to execute concurrently and asynchronously. The LWP package is meant to be general-purpose (note the applications mentioned above), with a heavy emphasis on simplicity. Interprocess communication facilities can be built on top of this basic mechanism and in fact, many different IPC mechanisms could be implemented.
In order to set up the threading support environment, a one-time invocation of the LWP InitializeProcessSupport() function must precede the use of the facilities described here. This initialization function carves an initial process out of the currently executing C procedure and returns its thread ID. For symmetry, an LWP TerminateProcessSupport() function may be used explicitly to release any storage allocated by its counterpart. If this function is used, it must be issued from the thread created by the original LWP InitializeProcessSupport() invocation.
When any of the lightweight process functions completes, an integer value is returned to indicate whether an error condition was encountered. By convention, a return value of zero indicates that the operation succeeded.
Macros, typedefs, and manifest constants for error codes needed by the threading mechanism are exported by the lwp.h include file. A lightweight process is identified by an object of type PROCESS, which is defined in the include file.
The process model supported by the LWP operations is based on a non-preemptive priority dispatching scheme. A priority is an integer in the range [0..LWP MAX PRIORITY], where 0 is the lowest priority. Once a given thread is selected and dispatched, it remains in control until it voluntarily relinquishes its claim on the CPU. Control may be relinquished by either explicit means (LWP_DispatchProcess()) or implicit means (through the use of certain other LWP operations with this side effect). In general, all LWP operations that may cause a higher-priority process to become ready for dispatching preempt the process requesting the service. When this occurs, the dispatcher mechanism takes over and automatically schedules the highest-priority runnable process. Routines in this category, where the scheduler is guaranteed to be invoked in the absence of errors, are:
  • LWP_WaitProcess()
  • LWP_MwaitProcess()
  • LWP_SignalProcess()
  • LWP_DispatchProcess()
  • LWP_DestroyProcess()
The following functions are guaranteed not to cause preemption, and so may be issued with no fear of losing control to another thread:
  • LWP_InitializeProcessSupport()
  • LWP_NoYieldSignal()
  • LWP_CurrentProcess()
  • LWP_ActiveProcess()
  • LWP_StackUsed()
  • LWP_NewRock()
  • LWP_GetRock()
The symbol LWP NORMAL PRIORITY, whose value is (LWP MAX PRIORITY-2), provides a reasonable default value to use for process priorities.
The lwp debug global variable can be set to activate or deactivate debugging messages tracing the flow of control within the LWP routines. To activate debugging messages, set lwp debug to a non-zero value. To deactivate, reset it to zero. All debugging output from the LWP routines is sent to stdout.
The LWP package checks for stack overflows at each context switch. The variable that controls the action of the package when an overflow occurs is lwp overflowAction. If it is set to LWP SOMESSAGE, then a message will be printed on stderr announcing the overflow. If lwp overflowAction is set to LWP SOABORT, the abort() LWP routine will be called. finally, if lwp overflowAction is set to LWP SOQUIET, the LWP facility will ignore the errors. By default, the LWP SOABORT setting is used.
Here is a sketch of a simple program (using some psuedocode) demonstrating the high-level use of the LWP facility. The opening include line brings in the exported LWP definitions. Following this, a routine is defined to wait on a "queue" object until something is deposited in it, calling the scheduler as soon as something arrives. Please note that various LWP routines are introduced here. Their definitions will appear later, in Section 2.3.1.
 #include <afs/lwp.h> 
 static read_process(id) 
 int *id; 
 {  /* Just relinquish control for now */
        LWP_DispatchProcess(); 
        for     (;;) 
        {  
                /* Wait until there is something in the queue */
                while   (empty(q)) LWP_WaitProcess(q); 
                /* Process the newly-arrived queue entry */
                LWP_DispatchProcess(); 
        } 
 } 
The next routine, write process(), sits in a loop, putting messages on the shared queue and signalling the reader, which is waiting for activity on the queue. Signalling a thread is accomplished via the LWP SignalProcess() library routine.
 static write_process() 
 { ... 
        /* Loop, writing data to the shared queue.  */
        for     (mesg = messages; *mesg != 0; mesg++) 
        { 
                insert(q, *mesg); 
                LWP_SignalProcess(q); 
        } 
 } 
finally, here is the main routine for this demo pseudocode. It starts by calling the LWP initialization routine. Next, it creates some number of reader threads with calls to LWP CreateProcess() in addition to the single writer thread. When all threads terminate, they will signal the main routine on the done variable. Once signalled, the main routine will reap all the threads with the help of the LWP DestroyProcess() function.
 main(argc, argv) 
 int argc; 
 char **argv; 
 { 
        PROCESS *id;  /* Initial thread ID */
        /* Set up the LWP package, create the initial thread ID. */
        LWP_InitializeProcessSupport(0, &id); 
        /* Create a set of reader threads.  */
        for (i = 0; i < nreaders; i++) 
                LWP_CreateProcess(read_process, STACK_SIZE, 0, i, "Reader",
                &readers[i]); 
 
        /* Create a single writer thread.  */
        LWP_CreateProcess(write_process, STACK_SIZE, 1, 0, "Writer", &writer); 
        /* Wait for all the above threads to terminate.  */
        for (i = 0; i <= nreaders; i++) 
                LWP_WaitProcess(&done); 
 
        /* All threads are done. Destroy them all.  */
        for (i = nreaders-1; i >= 0; i--) 
                LWP_DestroyProcess(readers[i]); 
 } 

Section 2.2.2: Locking

The LWP locking facility exports a number of routines and macros that allow a C programmer using LWP threading to place read and write locks on shared data structures. This locking facility was also written with simplicity in mind.
In order to invoke the locking mechanism, an object of type struct Lock must be associated with the object. After being initialized with a call to LockInit(), the lock object is used in invocations of various macros, including ObtainReadLock(), ObtainWriteLock(), ReleaseReadLock(), ReleaseWriteLock(), ObtainSharedLock(), ReleaseSharedLock(), and BoostSharedLock().
Lock semantics specify that any number of readers may hold a lock in the absence of a writer. Only a single writer may acquire a lock at any given time. The lock package guarantees fairness, legislating that each reader and writer will eventually obtain a given lock. However, this fairness is only guaranteed if the priorities of the competing processes are identical. Note that ordering is not guaranteed by this package.
Shared locks are read locks that can be "boosted" into write locks. These shared locks have an unusual locking matrix. Unboosted shared locks are compatible with read locks, yet incompatible with write locks and other shared locks. In essence, a thread holding a shared lock on an object has effectively read-locked it, and has the option to promote it to a write lock without allowing any other writer to enter the critical region during the boost operation itself.
It is illegal for a process to request a particular lock more than once without first releasing it. Failure to obey this restriction will cause deadlock. This restriction is not enforced by the LWP code.
Here is a simple pseudocode fragment serving as an example of the available locking operations. It defines a struct Vnode object, which contains a lock object. The get vnode() routine will look up a struct Vnode object by name, and then either read-lock or write-lock it.
As with the high-level LWP example above, the locking routines introduced here will be fully defined later, in Section 2.3.2.
 #include <afs/lock.h> 
 
 struct Vnode { 
        ... 
        struct Lock lock;  Used to lock this vnode  
 ... }; 
 
 #define READ 0 
 #define WRITE 1 
 
 struct Vnode *get_vnode(name, how) char *name; 
 int how; 
 { 
        struct Vnode *v; 
        v = lookup(name); 
        if (how == READ) 
                ObtainReadLock(&v->lock); 
        else 
                ObtainWriteLock(&v->lock); 
 } 

Section 2.2.3: IOMGR

The IOMGR facility associated with the LWP service allows threads to wait on various unix events. The exported IOMGR Select() routine allows a thread to wait on the same set of events as the unix select() call. The parameters to these two routines are identical. IOMGR Select() puts the calling LWP to sleep until no threads are active. At this point, the built-in IOMGR thread, which runs at the lowest priority, wakes up and coalesces all of the select requests together. It then performs a single select() and wakes up all threads affected by the result.
The IOMGR Signal() routine allows an LWP to wait on the delivery of a unix signal. The IOMGR thread installs a signal handler to catch all deliveries of the unix signal. This signal handler posts information about the signal delivery to a global data structure. The next time that the IOMGR thread runs, it delivers the signal to any waiting LWP.
Here is a pseudocode example of the use of the IOMGR facility, providing the blueprint for an implemention a thread-level socket listener.
 void rpc_SocketListener() 
 { 
        int ReadfdMask, WritefdMask, ExceptfdMask, rc; 
        struct timeval *tvp; 
        while(TRUE) 
        { ... 
                ExceptfdMask = ReadfdMask = (1 << rpc_RequestSocket); 
                WritefdMask = 0; 
 
                rc = IOMGR_Select(8*sizeof(int), &ReadfdMask, &WritefdMask,
                &ExceptfdMask, tvp); 
 
                switch(rc) 
                { 
                        case 0: /* Timeout */ continue; 
                        /* Main while loop */
 
                        case -1: /* Error */ 
                        SystemError("IOMGR_Select"); 
                        exit(-1); 
 
                        case 1: /* RPC packet arrived! */ ... 
                        process packet ... 
                        break; 
 
                        default: Should never occur 
                } 
        } 
 } 

Section 2.2.4: Timer

The timer package exports a number of routines that assist in manipulating lists of objects of type struct TM Elem. These struct TM Elem timers are assigned a timeout value by the user and inserted in a package-maintained list. The time remaining to each timer's timeout is kept up to date by the package under user control. There are routines to remove a timer from its list, to return an expired timer from a list, and to return the next timer to expire.
A timer is commonly used by inserting a field of type struct TM Elem into a structure. After setting the desired timeout value, the structure is inserted into a list by means of its timer field.
Here is a simple pseudocode example of how the timer package may be used. After calling the package initialization function, TM Init(), the pseudocode spins in a loop. first, it updates all the timers via TM Rescan() calls. Then, it pulls out the first expired timer object with TM GetExpired() (if any), and processes it.
 static struct TM_Elem *requests; 
 ... 
 TM_Init(&requests); /* Initialize timer list */ ... 
 for (;;) { 
        TM_Rescan(requests);  /* Update the timers */
        expired = TM_GetExpired(requests); 
        if (expired == 0) 
        break; 
        . . . process expired element . . . 
        } 

Section 2.2.5: Fast Time

The fast time routines allows a caller to determine the current time of day without incurring the expense of a kernel call. It works by mapping the page of the kernel that holds the time-of-day variable and examining it directly. Currently, this package only works on Suns. The routines may be called on other architectures, but they will run more slowly.
The initialization routine for this package is fairly expensive, since it does a lookup of a kernel symbol via nlist(). If the client application program only runs for only a short time, it may wish to call FT Init() with the notReally parameter set to TRUE in order to prevent the lookup from taking place. This is useful if you are using another package that uses the fast time facility.

Section 2.2.6: Preemption

The preemption package provides a mechanism by which control can pass between lightweight processes without the need for explicit calls to LWP DispatchProcess(). This effect is achieved by periodically interrupting the normal flow of control to check if other (higher priority) procesess are ready to run.
The package makes use of the BSD interval timer facilities, and so will cause programs that make their own use of these facilities to malfunction. In particular, use of alarm(3) or explicit handling of SIGALRM is disallowed. Also, calls to sleep(3) may return prematurely.
Care should be taken that routines are re-entrant where necessary. In particular, note that stdio(3) is not re-entrant in general, and hence multiple threads performing I/O on the same fiLE structure may function incorrectly.
An example pseudocode routine illustrating the use of this preemption facility appears below.
 #include <sys/time.h> 
 #include "preempt.h" 
        ...  struct timeval tv; 
        LWP_InitializeProcessSupport( ... ); 
        tv.tv_sec = 10; 
        tv.tv_usec = 0; 
        PRE_InitPreempt(&tv); 
        PRE_PreemptMe(); ... 
        PRE_BeginCritical(); ... 
        PRE_EndCritical(); ... 
        PRE_EndPreempt(); 

Section 2.3: Interface Specifications

Section 2.3.1: LWP

This section covers the calling interfaces to the LWP package. Please note that LWP macros (e.g., ActiveProcess) are also included here, rather than being relegated to a different section.

Section 2.3.1.1: LWP_InitializeProcessSupport

_ Initialize the LWP package

int LWP_InitializeProcessSupport(IN int priority; OUT PROCESS *pid)
Description
This function initializes the LWP package. In addition, it turns the current thread of control into the initial process with the specified priority. The process ID of this initial thread is returned in the pid parameter. This routine must be called before any other routine in the LWP library. The scheduler will NOT be invoked as a result of calling LWP_InitializeProcessSupport().
Error Codes
LWP EBADPRI The given priority is invalid, either negative or too large.

Section 2.3.1.2: LWP_TerminateProcessSupport

_ End process support, perform cleanup

int LWP_TerminateProcessSupport()
Description
This routine terminates the LWP threading support and cleans up after it by freeing any auxiliary storage used. This routine must be called from within the process that invoked LWP InitializeProcessSupport(). After LWP TerminateProcessSupport() has been called, it is acceptable to call LWP InitializeProcessSupport() again in order to restart LWP process support.
Error Codes
---Always succeeds, or performs an abort().

Section 2.3.1.3: LWP_CreateProcess _ Create a

new thread

int LWP_CreateProcess(IN int (*ep)(); IN int stacksize; IN int priority; IN char *parm; IN char *name; OUT PROCESS *pid)
Description
This function is used to create a new lightweight process with a given printable name. The ep argument identifies the function to be used as the body of the thread. The argument to be passed to this function is contained in parm. The new thread's stack size in bytes is specified in stacksize, and its execution priority in priority. The pid parameter is used to return the process ID of the new thread.
If the thread is successfully created, it will be marked as runnable. The scheduler is called before the LWP CreateProcess() call completes, so the new thread may indeed begin its execution before the completion. Note that the new thread is guaranteed NOT to run before the call completes if the specified priority is lower than the caller's. On the other hand, if the new thread's priority is higher than the caller's, then it is guaranteed to run before the creation call completes.
Error Codes
LWP EBADPRI The given priority is invalid, either negative or too large.
LWP NOMEM Could not allocate memory to satisfy the creation request.

Section: 2.3.1.4: LWP_DestroyProcess _ Create

a new thread

int LWP_DestroyProcess(IN PROCESS pid)
Description
This routine destroys the thread identified by pid. It will be terminated immediately, and its internal storage will be reclaimed. A thread is allowed to destroy itself. In this case, of course, it will only get to see the return code if the operation fails. Note that a thread may also destroy itself by returning from the parent C routine.
The scheduler is called by this operation, which may cause an arbitrary number of threads to execute before the caller regains the processor.
Error Codes
LWP EINIT The LWP package has not been initialized.

Section 2.3.1.5: WaitProcess _ Wait on an

event

int LWP WaitProcess(IN char *event)
Description
This routine puts the thread making the call to sleep until another LWP calls the LWP SignalProcess() or LWP NoYieldSignal() routine with the specified event. Note that signalled events are not queued. If a signal occurs and no thread is awakened, the signal is lost. The scheduler is invoked by the LWP WaitProcess() routine.
Error Codes
LWP EINIT The LWP package has not been initialized.
LWP EBADEVENT The given event pointer is null.

Section 2.3.1.6: MwaitProcess _ Wait on a set

of events

int LWP MwaitProcess(IN int wcount; IN char *evlist[])
Description
This function allows a thread to wait for wcount signals on any of the items in the given evlist. Any number of signals of a particular event are only counted once. The evlist is a null-terminated list of events to wait for. The scheduler will be invoked.
Error Codes
LWP EINIT The LWP package has not been initialized.
LWP EBADCOUNT An illegal number of events has been supplied.

Section 2.3.1.7: SignalProcess _ Signal an

event

int LWP SignalProcess(IN char *event)
Description
This routine causes the given event to be signalled. All threads waiting for this event (exclusively) will be marked as runnable, and the scheduler will be invoked. Note that threads waiting on multiple events via LWP MwaitProcess() may not be marked as runnable. Signals are not queued. Therefore, if no thread is waiting for the signalled event, the signal will be lost.
Error Codes
LWP EINIT The LWP package has not been initialized. LWP EBADEVENT A null event pointer has been provided. LWP ENOWAIT No thread was waiting on the given event.

Section 2.3.1.8: NoYieldSignal _ Signal an

event without invoking scheduler

int LWP NoYieldSignal(IN char *event)
Description
This function is identical to LWP SignalProcess() except that the scheduler will not be invoked. Thus, control will remain with the signalling process.
Error Codes
LWP EINIT The LWP package has not been initialized. LWP EBADEVENT A null event pointer has been provided. LWP ENOWAIT No thread was waiting on the given event.

Section 2.3.1.9: DispatchProcess _ Yield

control to the scheduler

int LWP DispatchProcess()
Description
This routine causes the calling thread to yield voluntarily to the LWP scheduler. If no other thread of appropriate priority is marked as runnable, the caller will continue its execution.
Error Codes
LWP EINIT The LWP package has not been initialized.

Section 2.3.1.10: CurrentProcess _ Get the

current thread's ID

int LWP CurrentProcess(IN PROCESS *pid)
Description
This call places the current lightweight process ID in the pid parameter.
Error Codes
LWP EINIT The LWP package has not been initialized.

Section 2.3.1.11: ActiveProcess _ Get the

current thread's ID (macro)

int LWP ActiveProcess()
Description
This macro's value is the current lightweight process ID. It generates a value identical to that acquired by calling the LWP CurrentProcess() function described above if the LWP package has been initialized. If no such initialization has been done, it will return a value of zero.

Section: 2.3.1.12: StackUsed _ Calculate

stack usage

int LWP StackUsed(IN PROCESS pid; OUT int *max; OUT int *used)
Description
This function returns the amount of stack space allocated to the thread whose identifier is pid, and the amount actually used so far. This is possible if the global variable lwp stackUseEnabled was TRUE when the thread was created (it is set this way by default). If so, the thread's stack area was initialized with a special pattern. The memory still stamped with this pattern can be determined, and thus the amount of stack used can be calculated. The max parameter is always set to the thread's stack allocation value, and used is set to the computed stack usage if lwp stackUseEnabled was set when the process was created, or else zero.
Error Codes
LWP NO STACK Stack usage was not enabled at thread creation time.

Section 2.3.1.13: NewRock _ Establish

thread-specific storage

int LWP NewRock (IN int tag; IN char **value)
Description
This function establishes a "rock", or thread-specific information, associating it with the calling LWP. The tag is intended to be any unique integer value, and the value is a pointer to a character array containing the given data.
Users of the LWP package must coordinate their choice of tag values. Note that a tag's value cannot be changed. Thus, to obtain a mutable data structure, another level of indirection is required. Up to MAXROCKS (4) rocks may be associated with any given thread.
Error Codes
ENOROCKS A rock with the given tag field already exists. All of the MAXROCKS are in use.

Section: 2.3.1.14: GetRock _ Retrieve

thread-specific storage

int LWP GetRock(IN int tag; OUT **value)
Description
This routine recovers the thread-specific information associated with the calling process and the given tag, if any. Such a rock had to be established through a LWP NewRock() call. The rock's value is deposited into value.
Error Codes
LWP EBADROCK A rock has not been associated with the given tag for this thread.

Section 2.3.2: Locking

This section covers the calling interfaces to the locking package. Many of the user-callable routines are actually implemented as macros.

Section 2.3.2.1: Lock Init _ Initialize lock

structure

void Lock Init(IN struct Lock *lock)
Description
This function must be called on the given lock object before any other operations can be performed on it.
Error Codes
---No value is returned.

Section 2.3.2.2: ObtainReadLock _ Acquire a

read lock

void ObtainReadLock(IN struct Lock *lock)
Description
This macro obtains a read lock on the specified lock object. Since this is a macro and not a function call, results are not predictable if the value of the lock parameter is a side-effect producing expression, as it will be evaluated multiple times in the course of the macro interpretation. Read locks are incompatible with write, shared, and boosted shared locks.
Error Codes
---No value is returned.

Section 2.3.2.3: ObtainWriteLock _ Acquire a

write lock

void ObtainWriteLock(IN struct Lock *lock)
Description
This macro obtains a write lock on the specified lock object. Since this is a macro and not a function call, results are not predictable if the value of the lock parameter is a side-effect producing expression, as it will be evaluated multiple times in the course of the macro interpretation.
Write locks are incompatible with all other locks.
Error Codes
---No value is returned.

Section 2.3.2.4: ObtainSharedLock _ Acquire a

shared lock

void ObtainSharedLock(IN struct Lock *lock)
Description
This macro obtains a shared lock on the specified lock object. Since this is a macro and not a function call, results are not predictable if the value of the lock parameter is a side-effect producing expression, as it will be evaluated multiple times in the course of the macro interpretation.
Shared locks are incompatible with write and boosted shared locks, but are compatible with read locks.
Error Codes
---No value is returned.

Section 2.3.2.5: ReleaseReadLock _ Release

read lock

void ReleaseReadLock(IN struct Lock *lock)
Description
This macro releases the specified lock. The lock must have been previously read-locked. Since this is a macro and not a function call, results are not predictable if the value of the lock parameter is a side-effect producing expression, as it will be evaluated multiple times in the course of the macro interpretation. The results are also unpredictable if the lock was not previously read-locked by the thread calling ReleaseReadLock().
Error Codes
---No value is returned.

Section 2.3.2.6: ReleaseWriteLock _ Release

write lock

void ReleaseWriteLock(IN struct Lock *lock)
Description
This macro releases the specified lock. The lock must have been previously write-locked. Since this is a macro and not a function call, results are not predictable if the value of the lock parameter is a side-effect producing expression, as it will be evaluated multiple times in the course of the macro interpretation. The results are also unpredictable if the lock was not previously write-locked by the thread calling ReleaseWriteLock().
Error Codes
---No value is returned.

Section 2.3.2.7: ReleaseSharedLock _ Release

shared lock

void ReleaseSharedLock(IN struct Lock *lock)
Description
This macro releases the specified lock. The lock must have been previously share-locked. Since this is a macro and not a function call, results are not predictalbe if the value of the lock parameter is a side-effect producing expression, as it will be evaluated multiple times in the course of the macro interpretation. The results are also unpredictable if the lock was not previously share-locked by the thread calling ReleaseSharedLock().
Error Codes
---No value is returned.

Section 2.3.2.8: CheckLock _ Determine state

of a lock

void CheckLock(IN struct Lock *lock)
Description
This macro produces an integer that specifies the status of the indicated lock. The value will be -1 if the lock is write-locked, 0 if unlocked, or otherwise a positive integer that indicates the number of readers (threads holding read locks). Since this is a macro and not a function call, results are not predictable if the value of the lock parameter is a side-effect producing expression, as it will be evaluated multiple times in the course of the macro interpretation.
Error Codes
---No value is returned.

Section 2.3.2.9: BoostLock _ Boost a shared

lock

void BoostLock(IN struct Lock *lock)
Description
This macro promotes ("boosts") a shared lock into a write lock. Such a boost operation guarantees that no other writer can get into the critical section in the process. Since this is a macro and not a function call, results are not predictable if the value of the lock parameter is a side-effect producing expression, as it will be evaluated multiple times in the course of the macro interpretation.
Error Codes
---No value is returned.

Section 2.3.2.10: UnboostLock _ Unboost a

shared lock

void UnboostLock(IN struct Lock *lock)
Description
This macro demotes a boosted shared lock back down into a regular shared lock. Such an unboost operation guarantees that no other writer can get into the critical section in the process. Since this is a macro and not a function call, results are not predictable if the value of the lock parameter is a side-effect producing expression, as it will be evaluated multiple times in the course of the macro interpretation.
Error Codes
---No value is returned.

Section 2.3.3: IOMGR

This section covers the calling interfaces to the I/O management package.

Section: 2.3.3.1: IOMGR Initialize _

Initialize the package

int IOMGR Initialize()
Description
This function initializes the IOMGR package. Its main task is to create the IOMGR thread itself, which runs at the lowest possible priority (0). The remainder of the lightweight processes must be running at priority 1 or greater (up to a maximum of LWP MAX PRIORITY (4)) for the IOMGR package to function correctly.
Error Codes
-1 The LWP and/or timer package haven't been initialized.
<misc> Any errors that may be returned by the LWP CreateProcess() routine.

Section 2.3.3.2: IOMGR finalize _ Clean up

the IOMGR facility

int IOMGR finalize()
Description
This routine cleans up after the IOMGR package when it is no longer needed. It releases all storage and destroys the IOMGR thread itself.
Error Codes
<misc> Any errors that may be returned by the LWP DestroyProcess() routine.

Section 2.3.3.3: IOMGR Select _ Perform a

thread-level select()

int IOMGR Select (IN int numfds; IN int *rfds; IN int *wfds; IN int *xfds; IN truct timeval *timeout)
Description
This routine performs an LWP version of unix select() operation. The parameters have the same meanings as with the unix call. However, the return values will be simplified (see below). If this is a polling select (i.e., the value of timeout is null), it is done and the IOMGR Select() function returns to the user with the results. Otherwise, the calling thread is put to sleep. If at some point the IOMGR thread is the only runnable process, it will awaken and collect all select requests. The IOMGR will then perform a single select and awaken the appropriate processes. This will force a return from the affected IOMGR Select() calls.
Error Codes
-1 An error occurred.
0 A timeout occurred.
1 Some number of file descriptors are ready.

Section 2.3.3.4: IOMGR Signal _ Associate

unix and LWP signals

int IOMGR Signal(IN int signo; IN char *event)
Description
This function associates an LWP signal with a unix signal. After this call, when the given unix signal signo is delivered to the (heavyweight unix) process, the IOMGR thread will deliver an LWP signal to the event via LWP NoYieldSignal(). This wakes up any lightweight processes waiting on the event. Multiple deliveries of the signal may be coalesced into one LWP wakeup. The call to LWP NoYieldSignal() will happen synchronously. It is safe for an LWP to check for some condition and then go to sleep waiting for a unix signal without having to worry about delivery of the signal happening between the check and the call to LWP WaitProcess().
Error Codes
LWP EBADSIG The signo value is out of range.
LWP EBADEVENT The event pointer is null.

Section 2.3.3.5: IOMGR CancelSignal _ Cancel

unix and LWP signal association

int IOMGR CancelSignal(IN int signo)
Description
This routine cancels the association between a unix signal and an LWP event. After calling this function, the unix signal signo will be handled however it was handled before the corresponding call to IOMGR Signal().
Error Codes
LWP EBADSIG The signo value is out of range.

Section 2.3.3.6: IOMGR Sleep _ Sleep for a

given period

void IOMGR Sleep(IN unsigned seconds)
Description
This function calls IOMGR Select() with zero file descriptors and a timeout structure set up to cause the thread to sleep for the given number of seconds.
Error Codes
---No value is returned.

Section 2.3.4: Timer

This section covers the calling interface to the timer package associated with the LWP facility.

Section 2.3.4.1: TM Init _ Initialize a timer

list

int TM Init(IN struct TM Elem **list)
Description
This function causes the specified timer list to be initialized. TM Init() must be called before any other timer operations are applied to the list.
Error Codes
-1 A null timer list could not be produced.

Section 2.3.4.2: TM final _ Clean up a timer

list

int TM final(IN struct TM Elem **list)
Description
This routine is called when the given empty timer list is no longer needed. All storage associated with the list is released.
Error Codes
-1 The list parameter is invalid.

Section 2.3.4.3: TM Insert _ Insert an object

into a timer list

void TM Insert(IN struct TM Elem **list; IN struct TM Elem *elem)
Description
This routine enters an new element, elem, into the list denoted by list. Before the new element is queued, its TimeLeft field (the amount of time before the object comes due) is set to the value stored in its TotalTime field. In order to keep TimeLeft fields current, the TM Rescan() function may be used.
Error Codes
---No return value is generated.

Section 2.3.4.4: TM Rescan _ Update all

timers in the list

int TM Rescan(IN struct TM Elem *list)
Description
This function updates the TimeLeft fields of all timers on the given list. This is done by checking the time-of-day clock. Note: this is the only routine other than TM Init() that updates the TimeLeft field in the elements on the list.
Instead of returning a value indicating success or failure, TM Rescan() returns the number of entries that were discovered to have timed out.
Error Codes
---Instead of error codes, the number of entries that were discovered to have timed out is returned.

Section 2.3.4.5: TM GetExpired _ Returns an

expired timer

struct TM Elem *TM GetExpired(IN struct TM Elem *list)
Description
This routine searches the specified timer list and returns a pointer to an expired timer element from that list. An expired timer is one whose TimeLeft field is less than or equal to zero. If there are no expired timers, a null element pointer is returned.
Error Codes
---Instead of error codes, an expired timer pointer is returned, or a null timer pointer if there are no expired timer objects.

Section 2.3.4.6: TM GetEarliest _ Returns

earliest unexpired timer

struct TM Elem *TM GetEarliest(IN struct TM Elem *list)
Description
This function returns a pointer to the timer element that will be next to expire on the given list. This is defined to be the timer element with the smallest (positive) TimeLeft field. If there are no timers on the list, or if they are all expired, this function will return a null pointer.
Error Codes
---Instead of error codes, a pointer to the next timer element to expireis returned, or a null timer object pointer if they are all expired.

Section 2.3.4.7: TM eql _ Test for equality

of two timestamps

bool TM eql(IN struct timemval *t1; IN struct timemval *t2)
Description
This function compares the given timestamps, t1 and t2, for equality. Note that the function return value, bool, has been set via typedef to be equivalent to unsigned char.
Error Codes
0 If the two timestamps differ.
1 If the two timestamps are identical.

Section 2.3.5: Fast Time

This section covers the calling interface to the fast time package associated with the LWP facility.

Section 2.3.5.1: FT Init _ Initialize the

fast time package

int FT Init(IN int printErrors; IN int notReally)
Description
This routine initializes the fast time package, mapping in the kernel page containing the time-of-day variable. The printErrors argument, if non-zero, will cause any errors in initalization to be printed to stderr. The notReally parameter specifies whether initialization is really to be done. Other calls in this package will do auto-initialization, and hence the option is offered here.
Error Codes
-1 Indicates that future calls to FT GetTimeOfDay() will still work, but will not be able to access the information directly, having to make a kernel call every time.

Section 2.3.5.2: FT GetTimeOfDay _ Initialize

the fast time package

int FT GetTimeOfDay(IN struct timeval *tv; IN struct timezone *tz)
Description
This routine is meant to mimic the parameters and behavior of the unix gettimeofday() function. However, as implemented, it simply calls gettimeofday() and then does some bound-checking to make sure the value is reasonable.
Error Codes
<misc> Whatever value was returned by gettimeofday() internally.

Section 2.3.6: Preemption

This section covers the calling interface to the preemption package associated with the LWP facility.

Section 2.3.6.1: PRE InitPreempt _ Initialize

the preemption package

int PRE InitPreempt(IN struct timeval *slice)
Description
This function must be called to initialize the preemption package. It must appear sometime after the call to LWP InitializeProcessSupport() and sometime before the first call to any other preemption routine. The slice argument specifies the time slice size to use. If the slice pointer is set to null in the call, then the default time slice, DEFAULTSLICE (10 milliseconds), will be used. This routine uses the unix interval timer and handling of the unix alarm signal, SIGALRM, to implement this timeslicing.
Error Codes
LWP EINIT The LWP package hasn't been initialized.
LWP ESYSTEM Operations on the signal vector or the interval timer have failed.

Section 2.3.6.2: PRE EndPreempt _ finalize

the preemption package

int PRE EndPreempt()
Description
This routine finalizes use of the preemption package. No further preemptions will be made. Note that it is not necessary to make this call before exit. PRE EndPreempt() is provided only for those applications that wish to continue after turning off preemption.
Error Codes
LWP EINIT The LWP package hasn't been initialized.
LWP ESYSTEM Operations on the signal vector or the interval timer have failed.

Section 2.3.6.3: PRE PreemptMe _ Mark thread

as preemptible

int PRE PreemptMe()
Description
This macro is used to signify the current thread as a candidate for preemption. The LWP InitializeProcessSupport() routine must have been called before PRE PreemptMe().
Error Codes
---No return code is generated.

Section 2.3.6.4: PRE BeginCritical _ Enter

thread critical section

int PRE BeginCritical()
Description
This macro places the current thread in a critical section. Upon return, and for as long as the thread is in the critical section, involuntary preemptions of this LWP will no longer occur.
Error Codes
---No return code is generated.

Section 2.3.6.5: PRE EndCritical _ Exit

thread critical section

int PRE EndCritical()
Description
This macro causes the executing thread to leave a critical section previously entered via PRE BeginCritical(). If involuntary preemptions were possible before the matching PRE BeginCritical(), they are once again possible.
Error Codes
---No return code is generated.