Page Numbers: Yes First Page: 45 X: 527 Y: 10.5"
Margins: Binding: 13
Odd Heading: Not-on-first-page
Alto/Mesa Processes and Monitors
Even Heading:
Alto/Mesa Processes and Monitors
Alto/Mesa Processes and Monitors
October 1980
Warning: The Alto/Mesa operating system software does not fully exploit the capabilities of the process mechanism. In particular, arbitrary preemptive processes are not supported, and there are several restrictions on processes running at interrupt level.
Most aspects of processes and monitors are made available via constructs built into the Mesa language and described in Chapter 10 of the Mesa Language Manual. Some facilities whose frequency of use does not justify such treatment are cast as procedures, and form part of the Mesa system. The types described below are defined in PSBDefs.
PSB: PRIVATE TYPE = MACHINE DEPENDENT RECORD [ . . . ];
ProcessHandle: TYPE = POINTER TO PSB;
A PSB defines the data structure underlying the Mesa process implementation. In general no client programs should be concerned with these types (except possibly for debugging).
The types and procedures described below are defined in ProcessDefs. Any operation that takes a PROCESS as an argument (i.e., JOIN, Abort, and Detach) may generate the following signal.
InvalidProcess: SIGNAL [process: UNSPECIFIED];
This signal indicates that the argument does not correspond to any process known to the Mesa system. The check on the validity of the argument is not infallible. In particular, Mesa is unable to distinguish between a process which has been deleted and a new, recently created one which coincidentally has the same value.
A call to FORK may generate the following signal.
TooManyProcesses: ERROR;
The Mesa system contains a fixed number of PSBs. This signal will result when there are no PSBs available to create a new process.
GetCurrent: PROCEDURE RETURNS [PROCESS];
It returns the ProcessHandle of the current process.
Initialization
Every instance of a monitor and every condition variable must be initialized before it can be used. There are three cases:
If the lock and the condition variables reside in the global frame (or possibly the local frame, in the case a condition variable) The compiler will automatically initialize them along with the other global/local variables when the module is STARTed or the procedure is entered.
The lock and/or condition variables reside in storage that is allocated from an UNCOUNTED ZONE or MdsZone by means of the NEW operation. The compiler will initialize them even if no other initialization is given in the NEW expression.
The lock and/or condition variables reside in records allocated by other means. Initialization is the responsibility of the client.
Using uninitialized monitor locks or condition variables or reinitializing monitor locks or condition variables after they have have begun to be used will lead to totally unpredictable behavior.
The following operations are provided for initializing monitor locks and condition variables in client-created data structures by means other than someZone.NEW.
InitializeMonitor: PROCEDURE [monitor: POINTER TO MONITORLOCK];
InitializeMonitor leaves the monitor unlocked and sets the queue of waiting processes to empty. It may be called before or after the monitor data is initialized, but must be called before any entry procedure is invoked. Once use of the monitor has begun, InitializeMonitor must never be called again.
InitializeCondition: PROCEDURE [condition: POINTER TO CONDITION, ticks: Ticks];
InitializeCondition sets the queue of waiting processes to empty and sets the timeout interval of the condition variable to ticks (measured in units of "ticks" of an internal clock). It may be called before or after the other monitor data is initialized, but must be called before any WAIT or NOTIFY operations are performed on the condition variable. Once use of the condition variable has begun, InitializeCondition must never be called again.
Clients may convert clock ticks, milliseconds and seconds using the following operations:
Ticks: TYPE = CARDINAL;
Milliseconds: TYPE = CARDINAL;
Seconds: TYPE = CARDINAL;
MsecToTicks: PROCEDURE [Milliseconds] RETURNS [Ticks];
TicksToMsec: PROCEDURE [Ticks] RETURNS [Milliseconds];
SecondsToTicks: PROCEDURE [Seconds] RETURNS [Ticks];
Timeouts
Condition variables which are initialized automatically are assigned a default timeout of a few seconds. The timeout of any condition variable may be changed by the following operation.
SetTimeout: PROCEDURE [condition: POINTER TO CONDITION, ticks: Ticks];
DisableTimeout: PROCEDURE [POINTER TO CONDITION];
SetTimeout adjusts the timeout interval for all subsequent WAIT operations applied to that condition variable. DisableTimeout disables timeouts for all subsequent WAIT operations applied to that condition variable. However, neither operation has any effect on processes that are already WAITing.
SetTimeout and DisableTimeout are the only available operations to adjust a condition variable once it has been used. In particular, InitializeCondition must not be used for this purpose, especially for condition variables which are automatically initialized.
Pause: PROCEDURE [ticks: Ticks];
Pause delays execution of its caller by the specified number of ticks.
Detaching Processes
A process which will never be joined is detached using the following operation.
Detach: PROCEDURE [PROCESS];
This operation sets the state of the process so that when it returns from its root procedure, it will be deleted immediately and its results, if any, will be discarded. If the process is invalid (e.g., if the process has already been deleted), the signal InvalidProcess may be generated.
Priorities of Processes
When a process is created with FORK, it inherits the priority of the FORKing process. If this proves unsatisfactory, the FORKed process may change its own priority with the following operation.
SetPriority: PROCEDURE [Priority];
Priority: TYPE = [0..7];
A process may determine its own priority by calling:
GetPriority: PROCEDURE RETURNS [Priority];
There is no way for a process to alter the priority of another process.
CAUTION: Use of multiple priorities in the current implementation is severely restricted. Any process running at other than the default priority (currently, 1) is forbidden to use many of the standard runtime support features of the Mesa environment. In practice, this means that non-standard priorities should be used only for interrupt handling, while all "normal" processing takes place concurrently at the default priority level. In addition, all interrupt level code must be locked in memory and should perform only a minimal amount of processing.
Aborting a process
A process can be aborted by calling the following operation.
Abort: PROCEDURE [PROCESS];
The effect of this operation is to generate the signal ABORTED the next time the process executes a WAIT statement on any condition variable. If the process is already WAITing, an implicit NOTIFY is issued at the time this procedure is called.
The argument to Abort is actually of type UNSPECIFIED, and is validated as a PROCESS at run-time. This is necessary since there is no generic type which includes all PROCESS types, regardless of result types.
ABORTED: ERROR;-- Predeclared Mesa Symbol
Aborted: ERROR;
The catch phrase for this signal may be attached to the WAIT statement, or it may be enabled in some scope according to the scope rules of Mesa. The catch phrase is executed with the corresponding monitor locked. Mesa has a predefined error ABORTED, which for compatibility reasons is also declared in ProcessDefs (i.e., Aborted = ABORTED). Programmers should use the predefined one in new code.
The intended use of Abort is to provide a means whereby one process may hint to another that the latter should go away, after first cleaning up. An Abort signal may occur on any condition variable, and thus every monitor should be protected by some catch phrase for it.
DisableAborts: PROCEDURE [POINTER TO CONDITION];
This procedure prevents a process from aborting when it waits on the specified condition variable. It is currently not implemented.
Control of scheduling
The Mesa process mechanism does not attempt to allocate processor time fairly among processes of equal priority. Because of this, it may be desirable for a process which does not execute WAIT statements very frequently in the normal course of its computation to occasionally yield control of the processor by calling the following operation.
Yield: PROCEDURE;
This is a hint to the scheduler to run other processes of the same priority. However, there is no guarantee that any other process will execute before the calling process resumes execution, even if there are processes able to execute.
In no case must the logical correctness of client programs depend on the presence or absence of calls to Yield; priorities and yielding are not intended as a process-synchronization mechanism. They are only hints to assist clients in meeting performance requirements.
Interrupt Level Processes
This section should be of interest only to programmers of interrupt level code.
The Mesa monitor mechanism includes an extension to cover the case of communication between software processes and Input/Output controllers (hardware and/or firmware). This is done using the artifact of naked condition variables; that is, condition variables which are not effectively protected by a monitor lock. The need for this arises from the fact that communication with I/O controllers, while similar to normal interprocess communication, suffers from the problem that the controllers are intrinsically unable to enter monitors. This means that two important atomicity properties provided by monitor locks are lost:
Atomicity of wakeups: Monitor locks eliminate the need for a traditional "wakeup waiting" (or "interrupt pending") flag. Lack of the monitor lock requires the provision of such a flag if lost interrupts are not to result.
Atomicity of data manipulations: The monitor lock avoids the problems of critical races on shared data; traditionally, I/O architectures take an ad hoc approach to this problem, rather than providing any general mechanism.
The approach taken is to solve the first problem in a general way, and leave the second problem for case-by-case resolution by the designers of specific controller/software interfaces. (This closely mirrors the approach normally taken in more traditional I/O-interrupt architectures.)
A software process that deals with an I/O controller does so from within what appears to be a normal monitor. The monitor data includes the status and control blocks of the device and a condition variable which the device notifies to raise an "interrupt". Since the controller can access the shared data at any time, however, special care must be taken by the software to avoid conflicts. Similarly, if the controller tried to notify the software between the time the software decided to wait on the condition variable, and the time that it actually performed the WAIT operation, the NOTIFY would be lost. To prevent this, the controller does a special form of NOTIFY (a naked notify), which sets a wakeup-waiting flag in the condition variable. This difference is invisible to the software, which does a normal WAIT operation on the condition variable.
Fine Point:
The compiler generates procedure calls to system functions for long division, block equal, etc. There is a compilation switch which flags all such calls; this is useful for writers of interrupt routines. See the Mesa User’s Handbook, Appendix A, for more details.
The traditional operations EnableInterrupts and DisableInterrupts are provided for those rare circumstances in which seizing the entire machine is the only form of mutual exclusion which proves sufficient. Doing a WAIT while interrupts are disabled is not recommended.
InterruptLevel: TYPE = [0..15];
InterruptLevels correspond to the interrupt channels described in the Alto Hardware Manual (except that the numbering is different). Interrupt level 0 corresponds to Alto interrupt channel 15, the memory parity interrupt channel. Several levels are used by the Mesa system. Programmers of interrupt level code should see the definitions in ProcessDefs.
ConditionVector: TYPE = ARRAY InterruptLevel OF POINTER TO CONDITION;
CV: POINTER TO ConditionVector = -- magic constant --;
CV points to an array of pointers to the naked condition variables described above. To associate a CONDITION with an interrupt level, assign a pointer to the CONDITION to the corresponding element of CV (CV[level] ← @cVar). To disable the naked NOTIFYs, assign NIL.
DisableInterrupts, EnableInterrupts: PROCEDURE;
These are MACHINE CODE procedures which disable and enable the handling of interrupts at the lowest level. They should be used only in matching pairs and only when no other exclusion mechanism will suffice. A count is maintained of the number of unmatched DisableInterrupts so that nested pairs will work correctly.