Hows and Whys of ASTs in RSX Gary L. Maxwell United States Geological Survey Menlo Park, California Like all other operating systems, RSX-11 implements an interface between programs that run on the system and any external event which affects the execution of the program. As stated in the Executive Reference Manual, there are only two means of notifying a task on RSX that an external event has occurred: event flags, and Asynchronous System Traps, or ASTs. Event flags are relatively simple to implement into any program, but they provide event response "passively." A task must wait for, stop for, or actively poll the event flags. These methods effectively reduce the parallelism of operations within a task. ASTs provide "dynamic" event response. When an event occurs, the affected task's current processing is "interrupted" so that the event is handled responsively by an "AST service routine." The idea of a task "interrupt" is completely analogous to an operating system interrupt when a peripheral device requires attention. This allows a task to perform any background processing at "task level" while events are handled at "AST level." Furthermore, several types of events are only available through the AST mechanism. The major obstacle to applying ASTs is the difficulty to conceptualize and implement them into a task. An additional difficulty is the lack of full support for ASTs in Fortran. Fortunately, structured HLLs (such as DECUS C) either support ASTs or easily adapt to their use. This paper will address the following issues regarding the mechanisms and applications of ASTs: 1. How ASTs are generated and handled by the Executive, and how a task which services ASTs is affected by the AST mechanism. 2. The classes of ASTs in RSX, and the individual ASTs within each class. 3. The rules which should be followed in implementing an AST service routine in order to avoid common pitfalls and obscure programming bugs. 4. Features and restrictions of DEC supported AST services from Fortran programs, plus an example of unsupported AST usage in a Fortran environment. 1.0 How ASTs Work We can see how the Executive processes ASTs and how ASTs affect task operation by following the flow of an AST through the system. 1.1 "Arming" of an AST by a Task A task "arms" (or "disarms") an AST by means of an argument to a system directive. Either the AST argument is the address of the AST service routine which will field the AST (arming the AST), or the argument is zero, indicating that no AST is desired. When a task starts, all ASTs are disarmed. 1.2 Executive Declares the AST An armed AST becomes a "declared" AST when the event associated with the AST occurs. Declaring an AST involves initializing an Executive data structure, called an "AST control block," followed by entering the control block into a queue of declared AST control blocks for the task. The AST control block is a variable-length packet of system pool which is pre-allocated when the task arms the AST. When the AST is declared, the control block is filled with the address of the AST service routine and any event-related information (called "trap-dependent parameters") which will be passed to the service routine. The trap-dependent parameters serve to assist the task's AST service routine with identifying which event has completed. An AST control block has the following general format: |-------------------------------------| | AST queue link word | |-------------------------------------| A.CBL: | AST type and flags word | |-------------------------------------| A.BYT: | No. bytes to allocate on task stack | |-------------------------------------| A.AST: | AST service routine address | |-------------------------------------| A.NPR: | No. AST parameters | |-------------------------------------| A.PRM: | First AST parameter | |-------------------------------------| | Second AST parameter, etc. | |-------------------------------------| The AST control block is queued at the end of a first-in first-out list of other declared ASTs for the task. Therefore, ASTs are ordered by event time history for each task on the system. The AST queue is located in the TCB (Task Control Block) at offset T.ASTL. (We will see later how one class of ASTs, the Kernel ASTs, have their own queueing rules.) 1.3 Executive Dispatches the AST The next time the task is scanned by the Executive scheduler, the AST queue is examined. If the queue is not empty and AST recognition for the task is enabled, then the first queue entry is dispatched, even if the task is in a stopped or waitfor state. Dispatching the AST involves the following steps: 1. Four words of information regarding the task's previous context are pushed on the task's stack: the event flag mask (which identifies the event flag last used in a STOP or WAIT directive), the program counter, the processor status word, and the Directive Status word. 2. Any trap dependent parameters are pushed on the stack. 3. The Executive deallocates the AST control block, and transfers control to the AST service routine. The service routine, like the task itself, still competes with other tasks on the system based on the task's priority. The previous state of the task is saved (i.e., any stopped or waitfor conditions). 1.4 Task AST Service Routine Executes The AST service routine is entered with the task's stack in the following state: |--------------------------| | Event flag mask word | |--------------------------| | PS prior to AST | |--------------------------| | PC prior to AST | |--------------------------| | Directive status word | |--------------------------| | (Zero or more trap | | dependent parameters) | SP -> | | |--------------------------| None of the general purpose registers are preserved; therefore the AST service routine saves and restores these as necessary. The service routine is free to execute most of the system directives and perform regular computations. While the AST service routine is executing, all other declared ASTs are deferred (e.g., "interrupts" are disabled), guaranteeing serialization of ASTs. The service routine exits by removing any trap-dependent parameters from the stack (always leaving the first four words pushed by the Executive) and by issuing an AST Service Exit (ASTX$S) directive. The Executive restores the previous context of the task by popping the DSW, PS, PC, and waitfor mask from the stack. If the AST queue is empty, then the task's context and previous state is restored. If another declared AST is pending, then it is dispatched at this time. 2.0 AST Classes and Their Directives Although all ASTs are handled in a similar manner, there are actually four distict classes of ASTs that the Executive handles. Each of these classes has its own unique personality, and some have their own special rules. For each type of AST within each class, there is a corresponding system directive which arms or disarms the AST. This section contains a description of all AST classes and their members, the AST-related system directives for each member, and the conditions which cause each AST to be declared. 2.1 Miscellaneous AST Directives The following directives do not belong to any partiular class of ASTs. They provide support for manipulating the task's AST environment and perform special AST functions. ASTX$ - AST Service Routine Exit. This directive is required to be the last instruction in an AST service routine. When this directive is issued, the task must be at AST state, and all trap dependent parameters must have been removed from the stack (leaving the four context words pushed by the Executive). DSAR$ - IHAR$ - Disable (or Inhibit) AST Recognition. Issuing this directive prevents the Executive from dispatching any ASTs for the task. This means that ASTs can be declared and queued up for the task, but no AST service routines are ever entered until AST recognition is enabled. This directive is used to place a "guard" around critical sections of code which cannot be guaranteed to execute correctly should an AST be dispatched. AST service routines cannot issue this directive. ENAR$ - Enable AST Recognition. This directive instructs the Executive to dispatch any ASTs presently declared for the task and clears a previously issued DSAR$ or IHAR$ directive condition. If an AST is already declared for the task when this directive is issued, the AST is immediately dispatched. When a task starts, AST recognition is enabled. AST service routines cannot issue this directive. CMKT$ - Cancel One or More Mark Time requests. If an AST service routine is specified with this directive, then all Mark Time requests that were issued with this AST service routine are cancelled. CRVT$ - Create Virtual Terminal (M-Plus systems only). This directive creates a VT: unit for the issuing task (the parent), allowing the arming of three AST service routines. When an offspring task issues an I/O request to the VT: unit created by the parent, an AST is declared for the parent. The three AST service routines are used to separately service input, output, and attach/detach requests to the VT: unit by offspring tasks. The ASTs remain armed until the VT: unit is eliminated by the ELVT$ directive. This directive is supported in Fortran programs. 2.2 Class 1: Dynamic ASTs The most common form of an external event occurs when an operation initiated by a task has completed. Examples of this class are I/O operations and timers. The ASTs which can be declared when such events occur are called "dynamic" ASTs, simply because only one AST is declared for every operation initiated by a task. The following directives for dynamic ASTs are divided into two groups. The first group of directives do not support ASTs in Fortran, and include: QIO$ - Queue I/O Function. The I/O operation specified in the argument list is initiated, and control returns to the program. The AST is declared when the I/O operation is finished. QIOW$ - Queue I/O Function and Wait. Identical to QIO, except the task is placed in a waitfor state. The waitfor condition is cleared and the AST is declared when the I/O operation completes. MRKT$ - Mark Time. The system is instructed to initialize a timer for the task and return control to the program. When the time period expires, the Executive declares a significant event, sets an event flag, and declares the AST. The second group of directives supports ASTs in Fortran, and all of the directives are part of the Executive's parent-offspring tasking support. In all cases, the AST for these directives is declared for the parent task when the offspring task exits or emits status. These directives are: CNCT$ - Connect to task. This directive connects the issuing task to another task (the offspring) which is already active. SDRC$ - Send Data, Request, and Connect. This directive allows the issuing task to queue a standard data packet to, request (if inactive), and connect to an offspring task. SPWN$ - Spawn. This directive spawns (requests), connects to, and optionally queues a command line to an offspring task, VSRC$ - Variable Send Data, Request, and Connect (M-Plus systems only). Same as SDRC$, except the size of the data packet is under program control. 2.3 Class 2: Specified ASTs There is a class of ASTs that addresses the need to handle certain events when and if they MIGHT occur. The family of "Specified ASTs" enables the task to identify to the system which events should be caught and passed on to the task using the AST mechanism. Most of the Specified ASTs provide a task with the ability to recover from certain "disasters," such as power failures. None of the events available through Specified ASTs are available through the event flag mechanism. Note that only the SPRA$ (power recovery) and SREA$/SREX$ (requested exit) Specified ASTs are supported in Fortran programs. The family of Specified ASTs and the function of each member is: SCAA$ - Specify Command Arrival AST. For CLI tasks only, this directive causes an AST to be declared when a command line is queued to the task. The command line is subsequently read with the GCCI$ directive. SFPA$ - Specify Floating Point Processor Exception AST. This directive causes an AST to be declared when the floating point processor traps through the floating point exception vector. This directive is normally issued by the runtime system of any HLL program as part of a standard error recovery procedure. SPEA$ - Specify Parity Error AST (M-Plus systems only). This directive causes an AST to be declared when a memory parity error is detected in one of the task's regions. On M-Plus systems, the Fixer task (FXR...) uses this AST to determine the range of bad memory within the offending region and make the bad memory inaccessible to the system. SPRA$ - Specify Power Recovery AST. This directive causes an AST to be declared when the operating system performs a power down/up sequence as a result of a power failure. SRDA$ - Specify Receive Data AST. This directive causes an AST to be declared when a data packet is queued to the issuing task. The data packet can be dequeued using one of the Receive Data directives. SREA$ - SREX$ - Specify Requested Exit AST. Perhaps the best of the new directives added to RSX, these directives cause an AST to be declared when an attempt is made to abort the issuing task by the ABRT$ directive or the MCR/DCL ABORT command. This allows a task to clean up and exit gracefully instead of being rudely aborted. SRRA$ - Specify Receive by Reference AST. Similar to SRDA$, this directive causes an AST to be declared when a receive-by-reference packet is queued to the issuing task. The task can dequeue the packet by issuing the RREF$ directive. There are some special rules for Specified ASTs. They are: 1. A Specified AST is armed by issuing the appropriate directive with the address of the AST service routine. The AST remains armed until the same directive is issued with a value of zero for the service routine address, or until the task exits. There are two exceptions. The SPEA$ AST is always disarmed after the AST is dispatched. In processing the SREA$/SREX$ AST for non-privileged tasks, the Executive disarms the AST after the AST is dispatched, and any attempts by the task to re-specify the AST are disallowed. 2. None of the Specified AST directives can be issued from an AST service routine. 3. When an event causes a Specified AST to be declared, then that Specified AST is effectively disarmed until the service routine for the Specified AST runs to completion. When the service routine exits, the Specified AST is rearmed. This rule implies that there is not a one-to-one correspondence between the number of events and the number of ASTs declared. This rule is very important, especially when the SRDA$ or SRRA$ directives are used. The program must assume that one or more additional events may occur between the time the AST is declared and the time the service routine exits. The Executive must perform some bookkeeping to keep track of the Specified ASTs that have been armed by a task. When a task arms a Specified AST, the Executive allocates the AST control block for the AST and links the block into a list of other armed Specified ASTs for the task in the TCB (at offset T.SAST). When an event occurs for which there may be a Specified AST, the Executive searches the list of armed Specified ASTs in the TCB. If the correct control block exists, it is removed from the list at T.SAST and queued into the declared AST list at T.ASTL, declaring the Specified AST. Once the Specified AST has been dispatched and serviced by the task, the AST control block is reentered into the Specified AST list at T.SAST, rearming the AST. This procedure forms the basis for Rule 3 above. 2.4 Class 3: "Customized" ASTs Support is provided for system processes to define "customized" ASTs and perform specialized actions when the AST is dispatched. Executive level processing for a customized AST is almost identical to other ASTs. The best (and only?) example of a customized AST is the Full Duplex Terminal Driver's Unsolicited Input Character AST. A task arms the unsolicited character AST by attaching the terminal with a special attach function, IO.ATA. The terminal driver allocates an AST control block, and initializes two offsets which define a special routine in the driver to be executed when the AST is dispatched. When the terminal driver receives an unsolicited character, the AST is declared for the task. When the AST is dispatched, the Executive sets up the task's stack, and calls the terminal driver's special routine. This routine recovers the AST control block, and scans the typeahead buffer for another unsolicited character. If one is already present, the driver declares the AST once again, (which will be deferred until the task services the original AST). The driver returns control to the Executive, which dispatches the AST to the task. 2.5 Class 4: Kernel ASTs A Kernel AST, as the name implies, is declared, dispatched, and serviced within the Executive, and is completely transparent to all tasks on the system. Although Kernel ASTs use the regular AST mechanism, they have their own set of rules and procedures. Since the Kernel AST is a very unique and interesting feature of RSX, we will devote some discussion to them. Just as a task uses ASTs to perform post-event processing, the Executive uses Kernel ASTs to perform certain Executive-level post-event processing. For example, an event may occur which requires the Executive to store some words of data into a task's address space. If the task is checkpointed, then the Executive cannot store the information until the task is reloaded into memory. A Kernel AST serves as a "place marker" for the Executive to complete some event processing when a task's context is fixed and known. A Kernel AST is declared by the Executive when event processing requires a particular task's context to be known and loaded. A Kernel AST is declared in the same manner as a regular AST, except the AST control block is entered at the BEGINNING of the AST queue, before any other ASTs. This is required so that the Executive's AST processing precedes the task's AST processing. As with regular ASTs, a Kernel AST is dispatched when the Scheduler finds a declared AST entry for a task which is granted the CPU. Note that when an AST is dispatched, the Executive has already loaded the task's context. The Kernel AST control block is dequeued, and the Executive dispatches the Kernel AST to a particular service routine within the Executive. The Executive's AST service routine performs the deferred processing specified by the Kernel AST, which completes the Executive's responsibility for handling the event. When the service routine completes, the scheduler is reinvoked to grant the CPU to the task with the highest priority. The following subsections describe each type of Kernel AST. With the exception of the Load Region Kernel AST, all of the Kernel ASTs follow the above rules for declaring, dispatching, and servicing the Kernel AST. 2.5.1 Buffered I/O Kernel AST - On heavily loaded multi-user systems, the checkpointing or shuffling of tasks that have outstanding terminal input requests can improve system performance. The ability to swap out tasks with outstanding I/O requires the Executive to store the input data in temporary buffers. On RSX, this is called Intermediate I/O Buffering, and this feature is used by the Full Duplex Terminal Driver. When a task issues a read request to the terminal driver, and the region containing the task's I/O buffer is checkpointable, the driver allocates an intermediate buffer from pool and makes the region eligible for checkpointing. The driver then supervises the I/O operation, using the intermediate buffer to store input characters as they are received. When the read request is completed, the driver cannot finish the I/O processing since the region may be shuffled or checkpointed. The driver declares a Kernel AST (with code AK.BUF) by calling the $QUEBF Executive routine. The Kernel AST is serviced by a routine ($FINBF) which copies the contents of the intermediate buffer to the I/O buffer in the region, and the intermediate buffer is deallocated. The service routine calls $IOFIN to complete the I/O request in the regular manner. A slightly similar Kernel AST is provided for more generalized intermediate I/O buffering, allowing the device driver to implement its own buffering scheme. Upon I/O completion, the driver declares the Kernel AST with a code of AK.GBI. The Kernel AST control block contains the entry point of a routine within the driver which will copy the intermediate buffer to the task buffer. When the Kernel AST is dispatched (to the $GENBF routine), a call is made to this driver subroutine, which completes the Kernel AST processing. 2.5.2 Debugger Kernel AST - A handy new MCR command on M-Plus systems is "DEBUG taskname," which causes an executing task to trap to its debugging aid (provided it was built with one). This allows the user to force a runaway program to return control to the debugging aid. The DEBUG command is implemented using a Kernel AST. When MCR receives a DEBUG command, it verifies the privilege of the user issuing the command, makes sure the target task is active, and then declares a Kernel AST on behalf of the target task with a code of AK.TBT. When the target task is scanned by the Scheduler, the Kernel AST is dispatched to the $DBTRP routine. The $DBTRP routine first checks the target task to verify that it actually contains a debugging aid. If it doesn't, then TKTN is requested to issue an error message on the terminal which issued the DEBUG command. Otherwise, $DBTRP sets the T-bit in the saved PS (Processor Status word) for the task in its task header, and the service routine exits. When the task is finally granted the CPU, the T-bit trap is immediately effected, and the debugging aid gains control of the task. 2.5.3 Finish Offspring Exit Kernel AST - When an offspring task exits, several actions may be performed for the parent task. An event flag (possibly group global) may be set, an AST can be declared, an exit status block can be copied to a buffer in the parent's task address space, and the OCB (Offspring Control Block) must be deallocated. Since the parent task may be checkpointed, the exit status block cannot be copied until the parent task is loaded. Therefore, the Executive declares a Kernel AST with code AK.OCB for the parent task when an offspring task exits. When the Kernel AST is dispatched to the $FINXT routine, all of the above actions are performed, completing the offspring task rundown. 2.5.4 Group Global Rundown Kernel AST - When a task exits with a pending Receive-by-Reference packet, a Kernel AST with code AK.GGF is declared for the sender task. When the Kernel AST is dispatched to the $GGFRN routine, a rundown on the sender task's Group Global event flags is performed if the Receive-by-Reference was synchronized with a Group Global event flag. 2.5.5 Delayed I/O Kernel AST - This Kernel AST is declared (with code AK.DIO) when a non-transfer I/O request is completed, and the target task has been checkpointed or shuffled. This is similar to the Intermediate I/O Buffering Kernel AST, except only the I/O status block in the task has become unmapped (there are no I/O buffers to copy). The Kernel AST is dispatched to the $FINDI routine to copy the two words of I/O status into the task's address space. 2.5.6 Region Load Kernel AST - As the name indicates, a Region Load Kernel AST deals with activities performed when a region is loaded from a checkpoint file. But the logical flow of a Region Load Kernel AST differs from all other Kernel ASTs, primarily because this type of Kernel AST is declared by another Kernel AST, the Buffered I/O Kernel AST. When a Buffered I/O Kernel AST is dispatched, the $FINBF routine first checks to guarantee that the region containing the I/O buffer is memory resident. If it is, then the buffer is subsequently copied. If the buffer is not resident, then $FINBF must defer its processing until the region is loaded. (From my understanding of the code, this scenario can occur if the task unmaps from the region after issuing the buffered I/O request.) The region is loaded by forcing the task which issued the I/O request to access the region, which places the region into the Loader's queue and blocks the task from executing. The Buffered I/O Kernel AST control block is then inserted into the PCB (Partition Control Block) for the region at offset P.SWSZ (which normally contains the swapping size of the region), and a bit (PS.AST) is set in the PCB's status word. The next action is taken by the Loader when the region is loaded back into memory. Seeing that there is a Kernel AST queued into the region's PCB, the Loader immediately dispatches the Kernel AST, which causes the I/O buffer to be copied immediately. The I/O operation is completed, the region is deaccessed from the task, and the task is unblocked. 3.0 Hints and Kinks in Writing AST Service Routines The primary challenge of ASTs is to write an effective AST service routine. Because of obscure side effects or programming oversights, AST bugs can be extremely difficult to diagnose. An admirable goal is to correctly program ASTs into a task on the first try. Careful adherence to the following sets of rules for AST service routines can prevent many common AST programming problems. Except in trivial applications, careful planning is required before actually writing an AST service routine. The scope of the service routine should be well-defined. An AST service routine is meant to perform time-critical operations in response to an event; therefore, it is wise to defer any processing that is not time-critical to normal task level. 3.1 Rules Governing All AST Service Routines The following rules are basically "hard" rules, which means that breaking one of the rules will probably result in an incorrect AST service routine (which may work correctly some of the time). These rules apply to any AST application, whether the application is written in MACRO assembler or in a HLL. 1. A task's context, except for the four context words pushed on the stack by the Executive, is never preserved when a service routine is entered. A task's context includes the contents of the general purpose and Floating Point Processor registers, all mapping assignments to regions, and any loaded and/or mapped overlay segments. The AST service routine must save and restore any portion of the task's context, if required. 2. As inferred by the above rule, AST service routines must be located in the root segment of an overlaid task. This rule can be stretched by logically guaranteeing that the service routine will always be mapped when the AST is dispatched, but performing such "tricks" is ill-advised. 3. Any subroutines which are commonly used at both task level and AST level must be reentrant. Such subroutines cannot have any static variables which can be overwritten should an AST occur. If common subroutines are not reentrant, then they must be guarded by a critical section, using the IHAR$ and ENAR$ directives at task level. 4. As previously mentioned, all trap-dependent parameters must be popped from the stack before the AST service routine exits. The task will be aborted by the Executive if the stack is incorrect when the service routine exits. 5. Some directives are illegal when issued from an AST service routine. These include the Specified AST directives, the IHAR$ and ENAR$ directives, and any directives which would cause the task to enter a waitfor or stopped state within the service routine. Otherwise, an AST service routine can issue any system directive. 6. The state of the task at task level does not change when an AST is dispatched. This means that if a task is stopped, and an AST is dispatched, the task will remain stopped when the service routine exits. Therefore, if the task has stopped itself (or is waiting) in anticipation of the event handled by the AST, the AST service routine must manually unstop the task. 3.2 Rules Governing Supported Fortran AST Service Routines The following rules apply to Fortran AST routines supported by DEC. Currently, these supported ASTs are the parent-offspring directives, the "requested exit" AST, the Create Virtual Terminal directive, and the power recovery AST. The rules also apply to specialized (and unsupported) AST applications in Fortran. 1. Since the Fortran I/O subsystem is not re-entrant, the AST service routine cannot issue any Fortran I/O commands. One workaround to this problem is to place critical sections around any Fortran I/O commands performed at task level, but this can be cumbersome and inefficient. Note that the QIO directive is not a part of Fortran I/O, so the service routine may issue QIO directives. 2. The Floating Point Processor registers are not saved before the AST service routine is entered, so no floating point operations (including math library functions) can be performed within the service routine. A simple workaround is to use the SAVFPP routine in Appendix A, which is a coroutine that will save the FPP registers and restore them when the service routine exits. 3. A Fortran AST service routine cannot use the STOP or CALL EXIT commands if output files are currently open. Since the Fortran I/O subsystem may be in an indeterminate state when the AST is dispatched, exiting the program at AST state may corrupt the output files. 4. The service routine cannot access any Fortran virtual arrays, since the AST can interrupt the virtual array subsystem at an indeterminate state. 5. The AST service routine must be compiled with traceback disabled (/NOTRACE). Otherwise, the AST service routine will corrupt the program's traceback. Additionally, any runtime error that occurs in an AST service routine will produce either an erroneous error message or other unpredictable results. 6. The Fortran AST interface imposes a finite limit on the number of outstanding armed ASTs a Fortran task can have. Currently, a task may have up to 24 outstanding parent-offspring ASTs pending and up to 24 simultaneous Virtual Terminal units. Exceeding these limits produces a directive status of IE.UPN (insufficient pool). Raising or lowering these limits requires a patch to the SYSLIB module SPTBL. 7. Fortran subroutines are not re-entrant, since they contain static variables. Therefore, Fortran subroutines cannot be used in common between task and AST levels. Fortunately, the Fortran directive calls are re-entrant, so they do not pose any problems. 3.3 Example: Fortran Requested Exit AST Usage There are always occasions when a Fortran task must be aborted. However, this causes any output files to be locked, and usually the last few output records are lost because they were not written out to disk before the task aborted. In another situation, a long-running task (such as a modelling program) may be aborted due to system considerations, or because the user wants to examine intermediate results produced by the task. It would be beneficial to be able to resume the task from the point where it was aborted. Previously, programming such a capability required user interaction at the terminal or a clever scheme using Send and Receive directives. Using the Specify Requested Exit AST, a task becomes aware that it is being aborted, and it can take appropriate action to complete any partial output results, perhaps save its current data environment, and gracefully exit. Fortunately, since this AST is supported in a Fortran program, implementation is relatively simple. In our example, we have two source files, MAIN.FTN, which contains the main program code, and ABORT.FTN, which contains the AST service routine. (It is wise to segregate AST service routines from other sections of the program, since the AST service routines must be compiled with the /NOTRACE option.) The service routine communicates with the main program through a COMMON block. When the AST occurs, the service routine sets a flag in the COMMON block. When the main program checks the flag, it can then orderly shut down. A skeletal outline of the code required might be: File MAIN.FTN: Program MAIN C External ABORT ! Define AST Service Routine Common /ABOCOM/ Iflag C Iflag = 0 ! Reset flag Call SREA(ABORT) ! Arm the abort AST C C The main loop follows. C 1000 Continue ! Perform normal processing C C... (Main loop contents) C If (Iflag .eq. 0) Goto 1000 ! Check abort flag C C Abort request fielded. Break out of loop C Close (Unit=1) ! Close possible output file Call Exit End File ABORT.FTN: Subroutine ABORT C Common /ABOCOM/ Iflag C Iflag = 1 ! Set the Abort flag! Return ! And return from AST state End To compile and build the program, the command sequence might be: >F77 MAIN,MAIN=MAIN >F77 ABORT,ABORT=ABORT/NOTR >TKB MAIN=MAIN,ABORT,LB:[1,1]F77OTS/LB 3.4 Example: Unsolicited Character Input AST From Fortran We perform various types of interactive signal processing on our PDP 11/70 in Menlo Park, using graphics terminals driven at 9600 baud. Typically, a set of traces are drawn on the screen in a 30 second period. We felt it was necessary to allow users to interrupt the display process to change parameters and redraw the screen. The mechanism we used to provide this capability is the Unsolicited Input Character AST mechanism of the terminal driver. Since all of the analysis programs are written in Fortran, a special set of Macro routines were written to allow Fortran programs to arm and disarm the unsolicited input AST mechanism. These routines are listed in Appendix B. A program arms the unsolicited input character by calling the TTYATA routine with the logical unit number assigned to the user's terminal and with the address of a Fortran AST service routine which is invoked when a character is intercepted. The TTYATA routine issues a QIO$ directive to the terminal with the IO.ATA function, which attaches the terminal. The program can perform any Fortran READ or WRITE statements to the terminal while the AST is armed. The AST will not be dispatched, since a READ statement is a solicited read request. However, if the user should enter an unsolicited character when no READ statement is active, then the AST is dispatched. (Note that the terminal driver does not echo the unsolicited input character.) The user's Fortran AST service routine is entered with two arguments: the ASCII value of the unsolicited character, and the task's waitfor mask. In our own applications, the AST service routine compares the input character with a set of legal input characters. If a match is found, the character is stored in a COMMON block. The service routine then exits via a RETURN statement. The main program periodically examines the COMMON block while the AST is armed. When an input character is detected, the program takes appropriate actions based on the particular character entered. When the AST mechanism is no longer required, the TTYDET routine is called, which detaches the terminal and disarms the AST. Examining the Macro code in Appendix B shows how the AST is handled and passed to the Fortran service routine. The IO.ATA QIO$ actually specifies a service routine within the Macro routines. This "primary" service routine saves the general purpose and FPP registers, sets up a standard Fortran argument list pointing to the character and the waitfor mask, and calls the Fortran service routine. When control returns to the Macro service routine, the registers are restored and the ASTX$ directive is issued. 3.5 Tips For Writing Specialized Fortran ASTs To write an application in Fortran using an "unsupported" AST interface, the same procedures can be used that are shown in the Unsolicited Character AST example above. In fact, the same procedures are used by Fortran in implementing "supported" ASTs, such as the parent-offspring directives. The basic procedures to follow are: 1. Define the Fortran subroutine interface to be used to arm the AST mechanism. 2. Define the necessary trap-dependent AST parameters which will be passed to the Fortran AST service routine in the service routine's argument list. 3. Write the Macro-level, Fortran-callable AST arming mechanism routine. 4. Write the Macro "primary" AST service routine which actually fields the AST when it is dispatched. The service routine will save the general purpose and FPP registers, set up the Fortran argument block, and call the Fortran service routine. Upon return, the Macro routine restores the registers, performs any cleanup operations, and issues the ASTX$ directive. The example listing in Appendix B provides a good example of the details involved. Further information regarding the Fortran OTS can be obtained from the Fortran Object Time System Manual supplied with the compiler. 4.0 Conclusions The use of ASTs can be beneficial in a wide variety of applications. When real-time response is critical, ASTs provide the best event mechanism when a task is responsible for managing a large event-oriented environment. Care should be used, however, in avoiding a difficult AST implementation where the benefits may be marginal. From my own experience, a programmer should ask the following questions before journeying into the world of ASTs: 1. Does my program spend a great deal of time waiting for event flags when it could be doing something else? 2. Do I need to be notified of an event for which event flags are useless, such as power fail recovery? 3. Is it worth the programming time to implement ASTs when event flags will perform adequately? Answering "yes" to any of the questions provides a good argument for using ASTs. By combining careful planning and programming, ASTs could be an important performance improvement to your application. Appendix A SAVFPP Routine - Save FPP Registers The following routine must be called by a Fortran AST service routine if the service routine contains any floating point arithmetic or math library calls. The contents of all the FPP hardware registers are saved on the task's stack. Note that a coroutine linkage is used; when the service routine exits, the FPP registers are transparently restored. The SAVFPP routine may be assembled in a straightforward manner, e.g.: >MAC SAVFPP,SAVFPP/-SP=SAVFPP The listing for the file SAVFPP follows: .TITLE SAVFPP -- Save FPP Registers (AST Use) .IDENT /01/ .ENABL LC ;+ ; ; SAVFPP -- Save FPP registers for calling routine and ; restore registers as a coroutine. ; ; Usage: ; ; Call SAVFPP ; ; This routine must be called by Fortran AST service routines ; that perform floating point operations or call math library ; routines. ; ; When the routine calling SAVFPP issues a RETURN statement ; (Fortran or Macro), the previous contents of the FPP ; registers are restored. ; ; This routine requires 27 decimal words of stack space. ; ;- ; Define FPP registers F0=%0 F1=%1 F2=%2 F3=%3 F4=%4 F5=%5 SAVFPP:: STFPS -(SP) ; Save FPP status SETD ; Set double precision STD F0,-(SP) ; Save FPP registers STD F1,-(SP) STD F2,-(SP) STD F3,-(SP) LDD F4,F0 STD F0,-(SP) LDD F5,F0 STD F0,-(SP) MOV 62(SP),-(SP) ; Get caller's address CALL @(SP)+ ; Call the caller back SETD ; Restore - set double LDD (SP)+,F0 ; Pop off registers STD F0,F5 LDD (SP)+,F0 STD F0,F4 LDD (SP)+,F3 LDD (SP)+,F2 LDD (SP)+,F1 LDD (SP)+,F0 LDFPS (SP)+ ; Load FPP status TST (SP)+ ; Pop stale address RETURN ; Return to caller's caller .END Appendix B TTYATA -- Fortran Callable Unsolicited Character AST The following Macro listing contains the routines required to implement unsolicited character input ASTs from a Fortran program. The procedures can be generalized to other specialized Fortran AST applications. Note that the module can be assembled to use either the Fortran OTS register save routines or a manual set of register save/restore routines. .TITLE TTYATA .IDENT /V2.0/ .ENABL LC ; ;+ ; Subroutines TTYATA, TTYDET, ASTSV$. ; ; Subroutine TTYATA. ; ; TTYATA is a Fortran callable subroutine to link a Fortran ; completion routine to the unsolicited input character AST ; mechanism. ; ; Since AST service routines are serialized, the completion ; routine may not use any features that depend on the AST ; mechanism, such as I/O. ; ; Only one lun may be attached through this subroutine at a ; time, due to the nature of the Fortran linkage. To change ; TTY's, you must first call TTYDET (see below) to clean up ; for the next call to TTYATA. TTYATA marks itself "in use" ; if $DSW = IS.SUC, regardless of the I/O status returned. ; ; Call TTYATA (lun,subr,iosb,ierr) ; ; lun = Preassigned logical unit number for TTY attachment ; with unsolicited input character AST, and the event ; flag number used in the I/O calls. The high order ; byte may be used to specify any special subfunction ; bits to be set, in addition to TF.AST, such as ; TF.NOT for use with the full duplex terminal ; driver. See section 2.3.1 in the I/O Drivers ; Manual. ; ; subr = Name of the Fortran completion routine. It must be ; declared in an External specification statement, ; and will be called by the AST service routine with ; two arguments: the unsolicited character (except ; when left in the typeahead buffer) and the task's ; waitfor mask (see section 2.3.3 in the Executive ; Reference Manual), e.g., ; ; FORTRAN main program: ; ; External SUBR ; : ; Call TTYATA (5,SUBR,iosb,ierr) ; : ; Stop ; End ; ; FORTRAN AST service routine: ; ; Subroutine SUBR (char, mask) ; Logical*1 char ; Integer*2 mask ; : ; Return ; End ; ; Macro level AST service routine (ASTSV$): ; ; : ; Call SUBR (char,mask) ; : ; ASTX$S ; ; iosb = Two word I/O status block, filled in at the ; completion of the attach QIO. ; ierr = Error return code: ; 0 = No errors ; 1 = Subroutine already in use ; <0 = Directive status error code ; ; The QIO is issued synchronously with notification through ; event flag "lun". The issuing task must first check the ; directive status word (ierr) before checking the I/O status ; block for errors. ; ;- ; ; *** Assembly Instructions *** ; ; TTYATA may be assembled to produce two versions: ; ; 1) If the symbol NO$$F4 is defined, TTYATA performs its own ; register saves and restores. You must prefix the system ; configuration file to determine whether the floating ; point hardware is present: ; ; >MAC TTYATA,TTYATA=[200,200]RSXMC/PA:1,[G,M]TTYATA ; ; The completion routine will be called using the ; standard Fortran linkage. ; ; 2) If the symbol NO$$F4 is undefined, TTYATA assumes the ; Fortran OTS routines are available to save and restore ; all required registers: ; ; >MAC TTYATA,TTYATA=TTYATA ; .MCALL FILIO$,SPCIO$,IOERR$,QIOW$S ; ; Define I/O Functions and Error Codes ; FILIO$ SPCIO$ IOERR$ .PSECT CODE,RO,I ; Pure code .ENABL LSB TTYATA:: CLR @10(R5) ; ierr = 0 TST FORLUN ; Check if in use already BNE 10$ ; If ne yes, fatal MOV 2(R5),R2 ; Get addr of LUN MOVB (R2)+,R0 ; Pick up unit no. MOVB (R2),R1 ; Pick up any subfunction bits BIS #IO.ATA,R1 ; Set function code for AST QIOW$S R1,R0,R0,,6(R5),,<#ASTSV$> ; Issue Attach QIO BCS 20$ ; If CS QIO failed MOV R0,FORLUN ; Mark subroutine in use MOV 4(R5),FORSUB ; Save the address of the FORTRAN ; completion routine EXATA: RETURN ; Return to caller 10$: INC @10(R5) ; ierr = 1 BR EXATA 20$: MOV $DSW,@10(R5) ; ierr = BR EXATA .PSECT IDATA,RW,D ; Impure data FORLUN: .WORD 0 ; "in use" flag, contains LUN FORSUB: .WORD 0 ; Fortran completion routine .DSABL LSB ;+ ; ; Subroutine TTYDET. ; ; TTYDET detaches TTY from lun and marks subroutine TTYATA as ; available for use again. ; ; Call TTYDET(iosb,ierr) ; ; iosb = Same as above for TTYATA. ; ierr = Same as above for TTYATA, except ierr = 1 ; indicates that no lun is currently attached. ; ;- ; .PSECT CODE,RO,I ; Pure code .ENABL LSB TTYDET:: CLR @4(R5) ; ierr = 0 MOV FORLUN,R0 ; Get LUN in use BEQ 30$ ; If eq, fatal - not in use QIOW$S #IO.DET,R0,R0,,2(R5) ; Detach tty from FORLUN BCS 40$ ; If CS QIO failure CLR FORLUN ; Mark TTYATA available EXDET: RETURN ; Return to caller 30$: INC @4(R5) ; ierr = 1 BR EXDET 40$: MOV $DSW,@4(R5) ; ierr = BR EXDET .DSABL LSB ;+ ; ; Subroutine ASTSV$. ; ; ASTSV$ is the TTY unsolicited input character ; AST service routine. ; ; On entry, the stack contains: ; ; SP+10 Event flag mask word ; SP+06 PS of the task prior to AST ; SP+04 PC of the task prior to AST ; SP+02 Task's Directive status word ; SP+00 The unsolicited character in the low byte ; (if TF.NOT not set); optional parameter 2 ; in the high byte (not used) (See section ; 2.3.2.1 in the I/O Drivers Manual.) ; ; Before exit, the unsolicited character must be popped ; off the stack. ; ; To exit, invoke ASTX$S: ; ; TST (SP)+ ; ASTX$S ; ; The Fortran completion subroutine is called with the ; unsolicited input character as the first argument, and the ; event flag mask as the second argument (to replace the value ; on the stack before dismissing the AST). ; ; *** Fortran OTS Routines Used *** ; ; MODULE: FPPUTI ENTRY: $SVFPP FPP register save ; SAVRG $SAVP0 General register save ; ;- .MCALL ASTX$S .PSECT CODE,RO,I ; Pure code ASTSV$:: MOV SP,ARGLST+2 ; Move char ptr into arg list MOV SP,ARGLST+4 ; Move EF mask ptr into arg list ADD #10,ARGLST+4 ; Correct for stack offset .IF DF NO$$F4 ; Manual register save CALL F4PSR$ ; Save R0-R5, F0-F5 .IFF ; DF NO$$F4 ; Fortran OTS save MOV #EXAST,-(SP) ; Force coroutine return to us CALL $SAVP0 ; Save general registers CALL $SVFPP ; Save FPP registers (if present) .IFTF ; DF NO$$F4 MOV #ARGLST,R5 ; Point to arg list CALL @FORSUB ; Call user AST routine CALL @(SP)+ ; Coroutine call to restore regs .IFF ; DF NO$$F4 RETURN ; OTS coroutine restore of R0-R5 ; (Returns control to EXAST) .ENDC ; DF NO$$F4 EXAST: TST (SP)+ ; Pop character off stack ASTX$S ; Exit AST service routine .PSECT IDATA,RW,D ; Impure data ARGLST: .WORD 2 ; 2 arguments for Fortran .WORD 0 ; 1st points to unsolicited char .WORD 0 ; 2nd points to event flag mask .IF DF NO$$F4 ; Register save - no Fortran OTS .PSECT CODE,RO,I ; Pure code F4PSR$: MOV R0,-(SP) ; Save registers for Fortran use MOV R1,-(SP) MOV R2,-(SP) MOV R3,-(SP) MOV R4,-(SP) MOV R5,-(SP) STKDEP=14 .IF DF F$$LPP F0=%0 F1=%1 F2=%2 F3=%3 F4=%4 F5=%5 STFPS -(SP) ; Save FPP status and registers SETD STD F0,-(SP) STD F1,-(SP) STD F2,-(SP) STD F3,-(SP) LDD F4,F0 STD F0,-(SP) LDD F5,F0 STD F0,-(SP) STKDEP=STKDEP+62 .ENDC ; DF F$$LPP MOV STKDEP(SP),-(SP) ; Pick up callers return address CALL @(SP)+ ; Coroutine return to caller .IFT ; DF NO$$F4 MOV (SP)+,STKDEP(SP) ; Load return address in slot .IF DF F$$LPP SETD ; Restore FPP regs and status LDD (SP)+,F0 STD F0,F5 LDD (SP)+,F0 STD F0,F4 LDD (SP)+,F3 LDD (SP)+,F2 LDD (SP)+,F1 LDD (SP)+,F0 LDFPS (SP)+ .ENDC ; DF F$$LPP MOV (SP)+,R5 ; Restore general registers MOV (SP)+,R4 MOV (SP)+,R3 MOV (SP)+,R2 MOV (SP)+,R1 MOV (SP)+,R0 RETURN .ENDC ; DF NO$$F4 .END