Skip to content

LWP User Guide

The LWP library implements primitive functions providing basic facilities that enable procedures written in C, to proceed in an unsynchronized fashion. These separate threads of control may effectively progress in parallel, and more or less independently of each other. This facility is meant to be general purpose 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. The RPC2 remote procedure call library is one such IPC mechanism.

The LWP library makes the following key design choices:

  • The library should be small and fast;
  • All processes are assumed to be trustworthy -- processes are not protected from each others actions;
  • There is no time slicing or preemption -- the processor must be yielded explicitly.

In order to set up the environment needed by the lightweight process support, a one-time invocation of the LWP_Init function must precede the use of the facilities described here. The initialization function carves an initial process out of the currently executing C procedure. The process id of this initial process is returned as the result of the LWP_Init function. For symmetry a LWP_TerminateProcessSupport function may be used explicitly to release any storage allocated by its initial counterpart. If used, it must be issued from the process created by the LWP_Init function.

Upon completion of any of the lightweight process functions, an integer value is returned to indicate whether any error conditions were encountered.

Macros, typedefs, and manifest constants for error codes needed by the lightweight process mechanism reside in the file <lwp/lwp.h>. A process is identified by an object of type PROCESS, which is defined in the include file.

The process model supported by the operations described here 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 lightweight process is selected and dispatched, it remains in control until it voluntarily relinquishes its claim on the CPU. Relinquishment may be either explicit (LWP_DispatchProcess) or implicit (through the use of certain other LWP operations). 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 priority dispatching mechanism takes over and dispatches the highest priority process automatically. Services in this category (where the scheduler is guaranteed to be invoked in the absence of errors) are

  • LWP_CreateProcess
  • LWP_WaitProcess
  • LWP_MwaitProcess
  • LWP_SignalProcess
  • LWP_DispatchProcess
  • LWP_DestroyProcess

The following services are guaranteed not to cause preemption (and so may be issued with no fear of losing control to another lightweight process):

  • LWP_Init
  • LWP_NoYieldSignal
  • LWP_CurrentProcess
  • LWP_StackUsed
  • LWP_NewRock
  • LWP_GetRock

The symbol LWP_NORMAL_PRIORITY provides a good default value to use for process priorities.

A word about initialization

The LWP, IOMGR, and FastTime components of the LWP library have routines that perform global initialization for the package. Each of these routines may be called more than once, and only the parameters from the first invocation will be used. In addition, each routine calls any of the others that it needs for proper operation.

The result is that if you only use one package directly, you need only call the initialization routine for that package. You may call the initialization routines for other packages anyway in order to set the initialization parameters yourself. If you wish to initialize all of these packages yourself, you must call the initialization routines in this order: FastTime, LWP, IOMGR.

Only after the LWP components have been initialized can you initialize dependent libraries like RPC2 and RVM.

In contrast, the Preemption component initialization routine may be called multiple times to change the value of the preemption time slice.

The Lock and Timer component have initialization routines that initialize objects instead of global data. The only restriction on the order of the initialization calls to these components is that calls to TM_Init must follow your call to FT_Init, if you have one.

A Simple Example

#include <lwp/lwp.h>

static void read_process(int *id)
{
    LWP_DispatchProcess();  /* Just relinquish control for now */

    for (;;) {
        /* Wait until there is something in the queue */
        while (empty(q))
            LWP_WaitProcess(q);

        /* Process queue entry */

        LWP_DispatchProcess();
   }
}

static void write_process()
{
    ...

    /* Loop & write data to queue */
    for (mesg = messages; *mesg != 0; mesg++) {
        insert(q, *mesg);
        LWP_SignalProcess(q);
    }
}

int main (int argc, char **argv)
{
    PROCESS *id;
    int i;

    LWP_Init(LWP_VERSION, 0, &id);

    /* Now create readers */
    for (i = 0; i < nreaders; i++)
        LWP_CreateProcess(read_process, STACK_SIZE, 0, i, "Reader", &readers[i]);

    LWP_CreateProcess(write_process, STACK_SIZE, 1, 0, "Writer", &writer);

    /* Wait for processes to terminate */
    LWP_WaitProcess(&done);

    for (i = nreaders-1; i >= 0; i--)
        LWP_DestroyProcess(readers[i]);

    return 0;
}

LWP Runtime Calls

LWP_Init

int LWP_Init(
    in char *VersionId,
    in int priority,
    out PROCESS *pid
);

Initializes the LWP library. In addition, this routine turns the current thread of control into the initial process with the specified priority. The process id of this initial process will be returned in parameter pid. This routine must be called to ensure proper initialization of the LWP routines. This routine will not cause the scheduler to be invoked.

Parameters
VersionId

Set this to the constant LWP_VERSION. The current value of this string constant must be identical to the value at the time the client runtime system was compiled.

priority

Priority at which initial process is to run.

pid

The process id of the initial process will be returned in this parameter.

Completion Codes
LWP_SUCCESS

All went well.

LWP_EBADPRI

Illegal priority specified (< 0 or too large).

LWP_TerminateProcessSupport

int LWP_TerminateProcessSupport();

This routine will terminate the LWP process support and clean up by freeing any auxiliary storage used. This routine must be called from within the procedure and process that invoked LWP_Init. After LWP_TerminateProcessSupport has been called, LWP_Init may be called again to resume LWP process support.

LWP_CreateProcess

int LWP_CreateProcess(
    in int (*ep) (),
    in int stacksize,
    in int priority,
    in char *parm,
    in char *name,
    out PROCESS *pid
);

This routine is used to create and mark as runnable a new light-weight process. This routine will cause the scheduler to be called. Note that the new process will begin execution before this call returns only if the priority of the new process is greater than or equal to the priority of the creating process.

Parameters
ep

This is the address of the code that is to execute the function of this process. This parameter should be the address of a C routine with a single parameter.

stacksize

This is the size (in bytes) to make the stack for the newly-created process. The stack cannot be shrunk or expanded, it is fixed for the life of the process.

priority

This is the priority to assign to the new process.

parm

This is the single argument that will be passed to the new process. Note that this argument is a pointer and, in general, will be used to pass the address of a structure containing further "parameters".

name

This is an ASCII string that will be used for debugging purposes to identify the process. The name may be a maximum of 32 characters.

pid

The process id of the new process will be returned in this parameter.

Completion Codes
LWP_SUCCESS

Process created successfully.

LWP_ENOMEM

Not enough free space to create process.

LWP_EBADPRI

Illegal priority specified (< 0 or too large).

LWP_EINIT

LWP_Init has not been called.

LWP_DestroyProcess

int LWP_DestroyProcess(
    in PROCESS pid
);

This routine will destroy the specified process. The specified process will be terminated immediately and its internal storage will be freed. A process is allowed to destroy itself (of course, it will only get to see the return code if the destroy fails). Note a process may also destroy itself by executing a return from the C routine. This routine calls the scheduler.

Parameters
pid

The process id of the process to be destroyed.

Completion Codes
LWP_SUCCESS

Process destroyed successfully.

LWP_EINIT

LWP_Init has not been called.

LWP_WaitProcess

int LWP_WaitProcess(
    in char *event
);

Wait for event. This routine will put the calling process to sleep until another process does a call of LWP_SignalProcess or LWP_NoYieldSignal with the specified event. Note that signals of events are not queued: if a signal occurs and no process is woken up, the signal is lost. This routine invokes the scheduler.

Parameters
event

The event to wait for. This can be any memory address. But, 0 is an illegal event.

Completion Codes
LWP_SUCCESS

The event has occurred.

LWP_EINIT

LWP_Init has not been called.

LWP_EBADEVENT

The specified event was illegal (0).

LWP_MwaitProcess

int LWP_MwaitProcess(
    in int wcount,
    in char *evlist
);

Wait for a specified number of a group of signals. This routine allows a process to wait for wcount signals of any of the signals in evlist. Any number of signals of a particular event is only counted once. The scheduler will be invoked.

Parameters
wcount

Is the number of events that must be signaled to wake up this process.

evlist

This a null-terminated list of events (remember that 0 is not a legal event). There may be at most LWP_MAX_EVENTS events

Completion Codes
LWP_SUCCESS

The specified number of appropriate signals has occurred.

LWP_EBADCOUNT

There are too few events (0) or wcount > the number of events in evlist.

LWP_EINIT

LWP_Init has not been called.

LWP_SignalProcess

int LWP_SignalProcess(
    in char *event
);

This routine causes event to be signaled. This will mark all processes waiting for only this event as runnable. The scheduler will be invoked. Signals are not queued: if no process is waiting for this event, the signal will be lost and LWP_ENOWAIT will be returned.

Parameters
event

The event to be signaled. An event is any memory address except 0.

Completion Codes
LWP_SUCCESS

The signal was a success (a process was waiting for it).

LWP_EBADEVENT

The specified event was illegal (0).

LWP_EINIT

LWP_Init was not called.

LWP_ENOWAIT

No process was waiting for this signal.

LWP_NoYieldSignal

int LWP_NoYieldSignal(
    in char *event
);

This routine causes event to be signaled. This will mark all processes waiting for only this event as runnable. This call is identical to LWP_SignalProcess except that the scheduler will not be invoked -- control will remain with the signalling process. Signals are not queued: if no process is waiting for this event, the signal will be lost and LWP_ENOWAIT will be returned.

Parameters
event

The event to be signaled. An event is any memory address except 0.

Completion Codes
LWP_SUCCESS

The signal was a success (a process was waiting for it).

LWP_EBADEVENT

The specified event was illegal (0).

LWP_EINIT

LWP_Init was not called.

LWP_DispatchProcess

int LWP_DispatchProcess();

This routine is a voluntary yield to the LWP scheduler.

Completion Codes
LWP_SUCCESS

All went well,

LWP_EINIT

LWP_Init has not been called.

LWP_CurrentProcess

int LWP_CurrentProcess(
    out PROCESS *pid
);

Get the current process id. This routine will place the current process id in the parameter pid.

Parameters
pid

The current process id will be returned in this parameter.

Completion Codes
LWP_SUCCESS

The current process id has been returned.

LWP_EINIT

LWP_Init has not been called.

LWP_StackUsed

int LWP_StackUsed(
    in PROCESS pid,
    out int *max,
    out int *used
);

This routine returns the amount of stack space allocated to the process and the amount actually used by the process so far. It works by initializing the stack to a special pattern at process creation time and checking to see how much of the pattern is still there when LWP_StackUsed is called. The stack of the process is only initialized to the special pattern if the global variable lwp_stackUseEnabled is true when the process is created. This variable is initially true. If lwp_stackUseEnabled was false at the time the process was created, then *used will be set to zero and the routine will return LWP_NO_STACK.

Parameters
pid

The target process.

max

Max stack size given at process creation time.

used

Stack used so far.

Completion Codes
LWP_SUCCESS

No problem.

LWP_NO_STACK

Stack counting was not enabled for this process.

LWP_NewRock

int LWP_NewRock(
    in int Tag,
    in char *Value
);

Find a rock under which private information can be hidden. The rock is exactly what its name implies: a place to squirrel away application-specific information associated with an LWP. The Tag is any unique integer. Users of the LWP library must coordinate their choice of Tag values. Note that you cannot change the value associated with Tag. To obtain a mutable data structure use one level of indirection.

Parameters
Tag

A unique integer identifying this rock.

Value

A value (usually a pointer to some data structure) to be associated with the current LWP and identified by Tag.

Completion Codes
LWP_SUCCESS

No problem.

LWP_EBADROCK

Rock called Tag already exists for this LWP.

LWP_ENOROCKS

All rocks are in use.

LWP_GetRock

int LWP_GetRock(
    in int Tag,
    out char **Value
);

Recovers information hidden by a LWP_NewRock call.

Parameters
Tag

Rock under which to look.

Value

The current value (usually a pointer to some data structure) hidden under this rock.

Completion Codes
LWP_SUCCESS

Value has been filled.

LWP_EBADROCK

Specified rock does not exist.

Debugging

A global variable lwp_debug 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 lwp_overflowAction variable controls the action of the package when an overflow occurs. If it is set to LWP_SOMESSAGE, then LWP will print a message on stderr telling of the overflow and will then be quiet. If it is set to LWP_SOABORT, LWP will call the abort() subroutine. Finally, if it is set to LWP_SOQUIET, LWP will ignore the errors. The LWP_SOABORT setting is the default.

Acknowledgements

The original design and implementation of LWP was done by Larry Raper. Its documentation descends from a manual by Jonathan Rosenberg and Larry Raper, later extended by David Nichols and M. Satyanarayanan.