HP OpenVMS Systems Documentation

Content starts here

OpenVMS Programming Concepts Manual


Previous Contents Index

7.6.2.2 Buffer Caching

To perform local buffer caching using blocking ASTs, processes do not convert their locks to null mode from protected read or exclusive mode when finished with the buffer. Instead, they receive blocking ASTs whenever another process attempts to lock the same resource in an incompatible mode. With this technique, processes are notified that their cached buffers are invalid as soon as a writer needs the buffer, rather than the next time the process tries to use the buffer.

7.6.3 Choosing a Buffer-Caching Technique

The choice between using either version numbers or blocking ASTs to perform local buffer caching depends on the characteristics of the application. An application that uses version numbers performs more lock conversions; whereas one that uses blocking ASTs delivers more ASTs. Note that these techniques are compatible; some processes can use one technique, and other processes can use the other at the same time. Generally, blocking ASTs are preferable in a low-contention environment; whereas version numbers are preferable in a high-contention environment. You can even invent combined or adaptive strategies.

In a combined strategy, the applications use specific techniques. If a process is expected to reuse the contents of a buffer in a short amount of time, the application uses blocking ASTs; if there is no reason to expect a quick reuse, the application uses version numbers.

In an adaptive strategy, an application makes evaluations based on the rate of blocking ASTs and conversions. If blocking ASTs arrive frequently, the application changes to using version numbers; if many conversions take place and the same cached copy remains valid, the application changes to using blocking ASTs.

For example, suppose one process continually displays the state of a database, while another occasionally updates it. If version numbers are used, the displaying process must always make sure that its copy of the database is valid (by performing a lock conversion); if blocking ASTs are used, the display process is informed every time the database is updated. On the other hand, if updates occur frequently, the use of version numbers is preferable to continually delivering blocking ASTs.

7.7 Example of Using Lock Management Services

The following program segment requests a null lock for the resource named TERMINAL. After the lock is granted, the program requests that the lock be converted to an exclusive lock. Note that, after SYS$ENQW returns, the program checks both the status of the system service and the condition value returned in the lock status block to ensure that the request completed successfully.


! Define lock modes
INCLUDE '($LCKDEF)'
! Define lock status block
INTEGER*2 LOCK_STATUS,
2         NULL
INTEGER LOCK_ID
COMMON /LOCK_BLOCK/ LOCK_STATUS,
2                   NULL,
2                   LOCK_ID
              .
              .
              .
! Request a null lock
STATUS = SYS$ENQW (,
2                  %VAL(LCK$K_NLMODE),
2                  LOCK_STATUS,
2                  ,
2                  'TERMINAL',
2                  ,,,,,)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL(STATUS))
IF (.NOT. LOCK_STATUS) CALL LIB$SIGNAL (%VAL(LOCK_STATUS))

! Convert the lock to an exclusive lock
STATUS = SYS$ENQW (,
2                  %VAL(LCK$K_EXMODE),
2                  LOCK_STATUS,
2                  %VAL(LCK$M_CONVERT),
2                  'TERMINAL',
2                  ,,,,,)
IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL(STATUS))
IF (.NOT. LOCK_STATUS) CALL LIB$SIGNAL (%VAL(LOCK_STATUS))

To share a terminal between a parent process and a subprocess, each process requests a null lock on a shared resource name. Then, each time one of the processes wants to perform terminal I/O, it requests an exclusive lock, performs the I/O, and requests a null lock.

Because the lock manager is effective only between cooperating programs, the program that created the subprocess should not exit until the subprocess has exited. To ensure that the parent does not exit before the subprocess, specify an event flag to be set when the subprocess exits (the num argument of LIB$SPAWN). Before exiting from the parent program, use SYS$WAITFR to ensure that the event flag has been set. (You can suppress the logout message from the subprocess by using the SYS$DELPRC system service to delete the subprocess instead of allowing the subprocess to exit.)

After the parent process exits, a created process cannot synchronize access to the terminal and should use the SYS$BRKTHRU system service to write to the terminal.


Part 2
Interrupts and Condition Handling

This part describes the use of asynchronous system traps (ASTs), and the use of condition-handling routines and services.


Chapter 8
Using Asynchronous System Traps

This chapter describes the use of asynchronous system traps (ASTs). It contains the following sections:

Section 8.1 provides an overview of AST routines.

Section 8.2 provides information about declaring and queuing ASTs.

Section 8.3 describes common asynchronous programming mistakes.

Section 8.4 provides information about using system services for AST event and time delivery.

Section 8.5 describes access modes for ASTs.

Section 8.6 provides information about calling ASTs.

Section 8.7 provides information about delivering ASTs.

Section 8.8 describes ASTs and process wait states.

Section 8.9 presents code examples of how to use AST services.

8.1 Overview of AST Routines

Asynchronous system traps (ASTs) are interrupts that occur asynchronously (out of sequence) with respect to the process's execution. ASTs are activated asynchronously to the mainline code in response to an event, usually either as a timer expiration or an I/O completion. An AST provides a transfer of control to a user-specified procedure that handles the event. For example, you can use ASTs to signal a program to execute a routine whenever a certain condition occurs.

The routine executed upon delivery of an AST is called an AST routine. AST routines are coded and referenced like any other routine; they are compiled and linked in the normal fashion. An AST routine's code must be reentrant. When the AST routine is finished, the routine that was interrupted resumes execution from the point of interruption.

ASTs provide a powerful programming technique. By using ASTs, you allow other processing to continue pending the occurrence of one or more events. Polling and blocking techniques, on the other hand, can use resoures inefficiently. A polling technique employs a looping that polls for an event, which has to wait for an indication that an event has occured. Therefore, depending on the frequency of the polling, polling techniques waste resources. If you use less frequent intervals, polling can then be slow to react to the occurrence of the event.

Blocking techniques force all processing to wait for the completion of a particular event. Blocking techniques can also be wasteful, for there could well be other activities the process could be performing while waiting for the occurrence of a specific event.

To deliver an AST, you use system services that specify the address of the AST routine. Then the system delivers the AST (that is, transfers control to your AST routine) at a particular time or in response to a particular event.

Some system services allow a process to request that it be interrupted when a particular event occurs. Table 8-1 shows the system services that are AST services.

Table 8-1 AST System Services
System Service Task Performed
SYS$SETAST Enable or disable reception of AST requests
SYS$DCLAST Declare AST

The system services that use the AST mechanism accept as an argument the address of an AST service routine, that is, a routine to be given control when the event occurs.

Table 8-2 shows some of the services that use ASTs.

Table 8-2 System Services That Use ASTs
System Service Task Performed
SYS$DCLAST Declare AST
SYS$ENQ Enqueue Lock Request
SYS$GETDVI Get Device/Volume Information
SYS$GETJPI Get Job/Process Information
SYS$GETSYI Get Systemwide Information
SYS$QIO Queue I/O Request
SYS$SETIMR Set Timer
SYS$SETPRA Set Power Recovery AST
SYS$UPDSEC Update Section File on Disk

The following sections describe in more detail how ASTs work and how to use them.

8.2 Declaring and Queuing ASTs

Most ASTs occur as the result of the completion of an asynchronous event that is initiated by a system service (for example, a SYS$QIO or SYS$SETIMR request) when the process requests notification by means of an AST.

The Declare AST (SYS$DCLAST) system service can be called to invoke a subroutine as an AST. With this service, a process can declare an AST only for the same or for a less privileged access mode.

You may find occasional use for the SYS$DCLAST system service in your programming applications; you may also find the SYS$DCLAST service useful when you want to test an AST service routine.

The following sections present programming information about declaring and using ASTs.

8.2.1 Reentrant Code and ASTs

Compiled code that is generated by Compaq compilers is reentrant. Furthermore, Compaq compilers normally generate AST routine local data that is reentrant. Data that is shared static, shared external data, Fortran COMMON, and group or system global section data are not inherently reentrant, and usually require explicit synchronization.

Because the queuing mechanism for an AST does not provide for returning a function value or passing more than one argument, you should write an AST routine as a subroutine. This subroutine should use nonvolatile storage that is valid over the life of the AST. To establish nonvolatile storage, you can use the LIB$GET_VM run-time routine. You can also use a high-level language's storage keywords to create permanent nonvolatile storage. For instance, you can use the C language's keywords as follows:


     extern
     static
     routine malloc().

In some cases, a system service that queues an AST (for example, SYS$GETJPI) allows you to specify an argument for the AST routine . If you choose to pass the argument, the AST routine must be written to accept the argument.

8.2.1.1 The Call Frame

When a routine is active under OpenVMS, it has available to it temporary storage on a stack, in a construct known as a stack frame, or call frame. Each time a subroutine call is made, another call frame is pushed onto the stack and storage is made available to that subroutine. Each time a subroutine returns to its caller, the subroutine's call frame is pulled off the stack, and the storage is made available for reuse by other subroutines. Call frames therefore are nested. Outer call frames remain active longer, and the outermost call frame, the call frame associated with the main routine, is normally always available.

A primary exception to this call frame condition is when an exit handler runs. With an exit handler running, only static data is available. The exit handler effectively has its own call frame. Exit handlers are declared with the SYS$DCLEXH system service.

The use of call frames for storage means that all routine-local data is reentrant; that is, each subroutine has its own storage for the routine-local data.

The allocation of storage that is known to the AST must be in memory that is not volatile over the possible interval the AST might be pending. This means you must be familiar with how the compilers allocate routine-local storage using the stack pointer and the frame pointer. This storage is valid only while the stack frame is active. Should the routine that is associated with the stack frame return, the AST cannot write to this storage without having the potential for some severe application data corruptions.

8.2.2 Shared Data Access with Readers and Writers

The following are two types of shared data access:

  • Multiple readers with one writer
  • Multiple readers with multiple writers

If there is shared data access with multiple readers, your application must be able to tolerate having a stale counter that allows frequent looping back and picking up a new value from the code.

With multiple writers, often the AST is the writer, and the mainline code is the reader or updater. That is, the mainline processes all available work until it cannot dequeue any more requests, releasing each work request to the free queue as appropriate, and then hibernates when no more work is available. The AST then activates, pulls free blocks off the free queue, fills entries into the pending work queue, and then wakes the mainline code. In this situation, you should use a scheduled wakeup call for the mainline code in case work gets into the queue and no wakeup is pending.

Having multiple writers is possibly the most difficult to code, because you cannot always be sure where the mainline code is in its processing when the AST is activated. A suggestion is to use a work queue and a free queue at a known shared location, and to use entries in the queue to pass the work or data between the AST and the mainline code. Interlocked queue routines, such as LIB$INSQHI and LIB$REMQTI, are available in the Run-Time Library.

8.2.3 Shared Data Access and AST Synchronization

An AST routine might invoke subroutines that are also invoked by another routine. To prevent conflicts, a program unit can use the SYS$SETAST system service to disable AST interrupts before calling a routine that might be invoked by an AST. You use the SYS$SETAST service typically only if there is noninterlocked (nonreentrant) variables, or if the code itself is nonreentrant. Once the shared routine has executed, the program unit can use the same service to reenable AST interrupts. In general you should avoid using the SYS$SETAST call because of implications for application performance.

Implicit synchronization can be achieved for data that is shared for write by using only AST routines to write the data, since only one AST can be running at any one time. You can also use the SYS$DCLAST system service to call a subroutine in AST mode.

Explicit synchronization can be achieved through a lack of read-modify cells, in cases of where there is one writer with one or more readers. However, if there are multiple writers, you must consider explicit synchronization of access to the data cells. This can be achieved using bitlocks (LIB$BBCCI), hardware interlocked queues (LIB$INSQHI), interlocked add and subtract (LIB$ADAWI) routines, or by other techniques. These routines are available directly in assembler by language keywords in C and other languages, and by OpenVMS RTL routines from all languages. On Alpha systems, you can use PALcode calls such as load-locked (LDx_L) and store-conditional (STx_C) instructions to manage synchronization.

For details of synchronization, see Chapter 6. Also see processor architecture manuals about the necessary synchronization techniques and for common synchronization considerations.

8.2.4 User ASTs and Asynchronous Completions

OpenVMS asynchronous completions usually activate an inner-mode, a privileged mode AST to copy any results read into a user buffer, if this is a read operation, and to update the IO status block (IOSB) and set the event flag. If a use-mode AST has been specified, it is activated once all data is available and the event flag and IOSB, if requested, have been updated.

8.3 Common Mistakes in Asynchronous Programming

The following lists common asynchronous programming mistakes and suggests how to avoid them:

  • Allocating the IOSB in a routine's call frame and returning before completion of the asynchronous request that then exits. When the asynchronous operation completes, the IOSB is written, and if the call frame is no longer valid, then a data corruption of 8 bytes, the size of the IOSB, occurs.
  • Failure to specify both an event flag and an IOSB. These are, in essence, required arguments.
  • Failure to use SYS$SYNCH, or to check both for an event flag that has been set and for a nonzero IOSB. If both conditions do not hold, the operation is not yet complete.
  • Incorrect sharing of an IOSB among multiple operations that are pending in parallel, or the allocation of an IOSB in storage that is volatile while the operation is pending.
  • Failure to acquire and synchronize the use of event flags using one or more calls to the LIB$GET_EF and LIB$FREE_EF routines.
  • Attempting to access the terminal with language I/O statements using SYS$INPUT or SYS$OUTPUT may cause a redundant I/O error. You must establish another channel to the terminal by explicitly opening the terminal, or by using the SMG$ routines.

8.4 Using System Services for AST Event and Time Delivery

The following list presents system services and routines that are used to queue the AST routine that determines whether an AST is delivered after a specified event or time. Note that the system service (W) calls are synchronous. Synchronous system services can have ASTs, but the code blocks pending completion, when the AST is activated.

  • Event---The following system routines allow you to specify an AST routine to be delivered when the system routine completes:
    • LIB$SPAWN---Signals when the subprocess has been created.
    • SYS$ENQ and SYS$ENQW---Signals when the resource lock is blocking a request from another process.
    • SYS$GETDVI and SYS$GETDVIW---Indicate that device information has been received.
    • SYS$GETJPI and SYS$GETJPIW---Indicate that process information has been received.
    • SYS$GETSYI and SYS$GETSYIW---Indicate that system information has been received.
    • SYS$QIO and SYS$QIOW---Signal when the requested I/O is completed.
    • SYS$UPDSEC---Signals when the section file has been updated.
    • SYS$ABORT_TRANS and SYS$ABORT_TRANSW---Signal when a transaction is aborted.
    • SYS$AUDIT_EVENT and SYS$AUDIT_EVENTW---Signal when an event message is appended to the system security audit log file or send an alarm to a security operator terminal.
    • SYS$BRKTHRU and SYS$BRKTHRU(W)---Signal when a message is sent to one or more terminals.
    • SYS$CHECK_PRIVILEGE and SYS$CHECK_PRIVILEGEW---Signal when the caller has the specified privileges or identifier.
    • SYS$DNS and SYS$DNSW---On VAX systems, signal when client applications are allowed to store resource names and addresses.
    • SYS$END_TRANS and SYS$END_TRANSW---Signal an end to a transaction by attempting to commit it.
    • SYS$GETQUI and SYS$GETQUIW---Signal when information is returned about queues and the jobs initiated from those queues.
    • SYS$START_TRANS and SYS$START_TRANSW---Signal the start of a new transaction.
    • SYS$SETCLUEVT and SYS$SETCLUEVTW---On Alpha systems, signal a request for notification when a VMScluster configuration event occurs.
  • Event---The SYS$SETPRA system service allows you to specify an AST to be delivered when the system detects a power recovery.
  • Time---The SYS$SETIMR system service allows you to specify a time for the AST to be delivered.
  • Time---The SYS$DCLAST system service delivers a specified AST immediately. This makes it an ideal tool for debugging AST routines.

If a program queues an AST and then exits before the AST is delivered, the AST is deleted before execution. If a process is hibernating when an AST is delivered, the AST executes, and the process then resumes hibernating.

If a suspended process receives an AST, the execution of the AST depends on the AST mode and the mode at which the process was suspended, as follows:

  • If the process was suspended from a SYS$SUSPEND call at supervisor mode, user-mode ASTs are executed as soon as the process is resumed. If more than one AST is delivered, they are executed in the order in which they were delivered. Supervisor-, executive-, and kernel-mode ASTs are executed upon delivery.
  • If the process was suspended from a SYS$SUSPEND call at kernel mode, all ASTs are blocked and are executed as soon as the process is resumed.

Generally, AST routines are used with the SYS$QIO or SYS$QIOW system service for handling Ctrl/C, Ctrl/Y, and unsolicited input.

8.5 Access Modes for AST Execution

Each request for an AST is associated with the access mode from which the AST is requested. Thus, if an image executing in user mode requests notification of an event by means of an AST, the AST service routine executes in user mode.

Because the ASTs you use almost always execute in user mode, you do not need to be concerned with access modes. However, you should be aware of some system considerations for AST delivery. These considerations are described in Section 8.7.

8.6 Calling an AST

This section shows the use of the Set Time (SYS$SETIMER) system service as an example of calling an AST. When you call the Set Timer (SYS$SETIMR) system service, you can specify the address of a routine to be executed when a time interval expires or at a particular time of day. The service schedules the execution of the routine and returns; the program image continues executing. When the requested timer event occurs, the system "delivers" an AST by interrupting the process and calling the specified routine.

Example 8-1 shows a typical program that calls the SYS$SETIMR system service with a request for an AST when a timer event occurs.

Example 8-1 Calling the SYS$SETIMR System Service

#include <stdio.h>
#include <stdlib.h>
#include <ssdef.h>
#include <descrip.h>
#include <starlet.h>
#include <lib$routines.h>

struct {
        unsigned int lower, upper;
}daytim;

/* AST routine */
void time_ast(void);

main() {
        unsigned int status;
        $DESCRIPTOR(timbuf,"0 ::10.00"); /* 10-second delta */


/* Convert ASCII format time to binary format */

        status = SYS$BINTIM(&timbuf,    /* buffer containing ASCII time */
                            &daytim);   /* timadr (buffer to receive  */
                                        /* binary time) */
        if ((status & 1) != 1)
                LIB$SIGNAL(status);
        else
                printf("Converting time to binary format...\n");

/* Set the timer */

        status = SYS$SETIMR(0,           /* efn (event flag) */       (1)
                            &daytim,     /* expiration time */
                            &time_ast,   /* astadr (AST routine) */
                            0,           /* reqidt (timer request id) */
                            0);          /* flags */
        if ((status & 1) != 1)
                LIB$SIGNAL(status);
        else
                printf("Setting the timer to expire in 10 secs...\n"); (2)

/* Hibernate the process until the timer expires */

        status = SYS$HIBER();
        if ((status & 1) != 1)
                LIB$SIGNAL(status);

}

void time_ast (void) {

        unsigned int status;

        status = SYS$WAKE(0,    /* process id */
                        0);     /* process name */

        if ((status & 1) != 1)
                LIB$SIGNAL(status);

        printf("Executing AST routine to perform wake up...\n");  (3)

        return;
}


Previous Next Contents Index