HP OpenVMS Systems Documentation
Guide to Creating OpenVMS Modular Procedures
3.2.1 Initializing Storage
For a procedure to produce predictable results, all statically and dynamically allocated areas must be initialized to known values before they are read. Initialization of dynamically allocated stack and heap data involves writing the data after each allocation and before reading it.
If your procedure has static storage, it is usually initialized to zero. In some languages, you do not need to explicitly initialize static storage. These languages automatically initialize static storage to zero. To see if the language you are using initializes static storage implicitly, refer to your reference manual for that language.
There are three ways to explicitly initialize storage: you can use an initialization statement, test and set a first-time flag at run time, or use LIB$INITIALIZE. The method of testing and setting a first-time flag is explained in Section 126.96.36.199.
Figure 3-3 shows examples of how languages supported by the OpenVMS
operating system initialize a longword, DAT, in static storage using an
To do first-time initialization, your procedure can test and then set to one a statically allocated first-time flag each time it is called. This flag is initialized to zero at compile or link time.
Setting and testing the flag with the RTL procedure LIB$BBSSI, a Branch on Bit Set and Set (BBSS) VAX instruction, or a Branch on Bit Set and Set Interlocked (VAX BBSSI) instruction, ensures that initialization is executed exactly once. (Some high-level languages provide semantics for accessing these VAX instructions: for instance, the _BBSSI built-in for C.)
However, if your implementation language does not have access to VAX instructions and the procedure is to be AST (Asynchronous System Trap) reentrant, it must follow these steps:
For additional information, see Section 3.3.
Figure 3-3 How to Initialize Static Storage
Example 3-1 illustrates the use of a first-time flag in a Pascal program to allocate a resource.
3.2.3 Using LIB$INITIALIZE
To use LIB$INITIALIZE to initialize a value at run time, you must do the following:
Assuming that you have completed the main program, first you must write an initialization procedure. If, for example, you are going to use LIB$INITIALIZE to initialize a value for a random number generator, you might write an initialization procedure to set the seed equal to the current time. This would generate a different seed for each initialization because the time is constantly changing. One possible initialization procedure is shown in Example 3-2.
Once you have defined the initialization procedure, you must write the MACRO program to add the address of that initialization procedure to PSECT LIB$INITIALIZE. The format for this MACRO program is simple, as seen in Example 3-3.
To modify this MACRO program for use in your own procedures, substitute the name of your initialization procedure for MY_INIT_ROUTINE.
Once you have written the initialization procedure and the MACRO program to add the dispatch address to PSECT LIB$INITIALIZE, you can link and run your program. The sample program in Example 3-4 can be initialized in this manner.
To run LIB$INITIALIZE on the program in Example 3-4 and initialize the value of SEED at run time, enter the following commands:
The following is an example of the output generated by these steps:
If your procedure establishes a condition handler by calling
LIB$INITIALIZE before a main program, the action of this handler might
conflict with other condition handlers established by other procedures
before the main program.
All modular procedures should be AST reentrant so they can be called
from any program. If your procedure is not AST reentrant or calls any
procedure that is not, your program documentation should specify this
to warn others against using your procedure.
An asynchronous system trap (AST) is an OpenVMS mechanism for providing a software interrupt when an external event occurs. One example of this type of interrupt occurs when a user presses Ctrl/C. When the external event occurs, the OpenVMS operating system interrupts the execution of the current process and calls a procedure that you supply. This procedure is referred to as the AST handler.
Some OpenVMS system services let an external event interrupt a process. Because the interrupt occurs out of sequence with respect to process execution, the interrupt mechanism is called an asynchronous system trap. The AST interrupt transfers control to the AST handler that services the event. This AST handler can call other procedures, including library procedures.
The AST handler you provide and any procedures it calls are said to be executing at AST level. While at AST level, a process cannot be interrupted a second time at the same access mode. The process runs to completion at the AST level before the non-AST level procedure resumes.
A process is executing either at AST level or at non-AST level and thus consists of two threads of execution, one thread at each level. Keep in mind that these levels are threads of the same process and not separate processes.
When your AST handler finishes servicing the event, it returns control to its caller. The interrupted procedure continues execution from the point of interruption.
For example, you could call the Set Timer system service ($SETIMR) to specify the address of an AST-level procedure to be executed after a specified amount of time has elapsed. At the specified time, the system generates an AST interrupt by stopping the procedure that is currently executing and calling the specified AST handler.
For information about implementing AST interrupts with system services,
see the OpenVMS System Services Reference Manual.
A procedure is AST reentrant if it meets the following conditions:
Do not confuse the term AST-reentrant with the term fully reentrant. Full reentrancy refers to a more restrictive set of conditions.
In an AST-reentrant environment, the AST thread is expected to complete regardless of whether it encounters a locked resource. When the AST thread encounters a locked resource in an AST-reentrant environment, it expects to be given a new resource, or else it is expected to return an error message. It is never expected to wait for the resource that the non-AST level has locked.
In a fully reentrant environment, all threads are treated equally when they encounter a locked resource; they wait for the resource to be freed. In a fully reentrant environment, AST threads are not given any special treatment. The Compaq Ada environment is an example of a fully reentrant environment. In such a situation, there can be more than two threads of concurrent execution, and each thread can alternately progress toward an end.
Compaq POSIX Threads Library, the Compaq multithreading run-time
library, provides a portable interface for creating and controlling
multiple threads of execution within the address space provided by a
single process on Alpha or VAX processors.
To use AST interrupts, you must write an AST handler to take control at AST level. An AST handler can be written in any language. Because the particulars of writing an AST handler differ from one language to the next, see the reference manual for the language you are working in for more details.
In general, an AST handler must follow these guidelines:
3.3.4 How to Eliminate Race Conditions During Concurrent Access
When using AST interrupts, you might encounter two problems: race conditions and deadlocks. A race condition occurs when your AST handler attempts to use a nonshareable resource already in use by the non-AST thread of execution.
If you allow the AST handler to wait for the resource (for example, by waiting for an event flag to be set by the non-AST level code of the same access mode), you have caused a form of deadlock. A deadlock occurs because the non-AST level code cannot execute to free the resource until the AST-level code has finished executing. The AST level code cannot continue either, because the non-AST level code has effectively locked the resource.
A race condition occurs when you attempt to access or modify the same data in static storage by both the AST and non-AST levels of a process. For example, if an AST begins executing while the non-AST level is modifying data in static storage, that data may be left in a nonstable state while the AST handler executes. To prevent a race condition, you should allow only one thread at a time to modify data. Use atomic modify operations provided by your HLL, which correctly interlock such access.
If a procedure does not modify any static storage, it is both AST reentrant and fully reentrant. Your procedure can eliminate conflict when accessing and modifying data in static storage by:
188.8.131.52 Performing All Accesses in One Instruction
All data modification in static storage can be performed in a single uninterruptible instruction for some applications. However, this method applies only to the VAX MACRO assembly language, and even then does not apply to emulated instructions.
For example, you can use queue instructions to maintain a linked list in a single instruction instead of modifying the forward and backward fields of the list in several instructions. You can use a single queue instruction at the beginning of your procedure to remove one section, and another queue instruction at the end to insert the section back in the queue.
While a section is removed from the queue, your procedure can modify data in it. If an AST interrupt occurs while the section is removed, a different section of data is used instead, thus avoiding conflicts with the interrupted procedure.
Example 3-5 shows an AST-reentrant procedure that uses queue instructions to control allocation of quadword blocks.
184.108.40.206 Using Test and Set Instructions
One method of eliminating the possibility of a race condition or deadlock is to use test and set instructions to detect concurrent access. You can detect concurrent access of static storage at both AST and non-AST levels by adding the following steps to your procedures:
The BBSS instruction detects that a concurrency conflict is about to take place before static storage has been accessed. If the storage is being accessed by multiple processors, you must use BBSSI and BBCCI.
There are two alternate techniques for resolving concurrency conflicts detected by the BBSS and BBCC instructions:
Example 3-6 shows the latter technique. This MACRO procedure, LIB_GET_INUM, allocates and deallocates identifying numbers.
220.127.116.11 Keeping a Call-in-Progress Count
If the database is to be kept separate between calls, you can keep track of when your procedure is called by using a call-in-progress count. Before database access, the count is incremented and used as an index for an address table of the separate databases. You should check for a count that exceeds the table length. After the database has been accessed, the count is decremented.
This technique has an advantage over the BBxx technique because it can handle more than two levels of reentrance. However, it is less reliable because an exception can cause the count never to be decremented, leading to an eventual procedure malfunction. You can avoid this by establishing a condition handler in your procedure.