Updated: 11 December 1998 |
Guide to Creating OpenVMS Modular Procedures
Previous | Contents | Index |
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 3.3.4.2.
Figure 3-3 shows examples of how languages supported by the OpenVMS
operating system initialize a longword, DAT, in static storage using an
initialization statement.
3.2.2 Testing and Setting a First-Time Flag
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 reentrant, it must follow these steps:
For additional information, see Section 3.3.
ASTs should be enabled in Step 4 or Step 7 only if they were enabled before Step 3. The $SETAST system service, used to disable ASTs, indicates whether ASTs were enabled when the procedure was called. |
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.
Example 3-1 Pascal Program That Uses a First-Time Flag |
---|
{+} { Program to demonstrate the use of a first-time flag when allocating { a resource. This technique is AST reentrant, but is NOT multithread { reentrant. {-} PROGRAM ALLOCATE; CONST VM_SIZE = 512; VAR INITIALIZED : BOOLEAN := FALSE; VM_ADDRESS : INTEGER := 0; AST_STATUS : INTEGER := 0; VM_STATUS : INTEGER := 0; DISABLE : INTEGER := 0; FUNCTION LIB$GET_VM (SIZE : INTEGER; VAR ADDR : INTEGER) : INTEGER; EXTERNAL; FUNCTION SYS$SETAST (VAR STATUS : INTEGER) : INTEGER; EXTERNAL; BEGIN {+} { Check the first-time flag. If set, initialization has been { performed already. {-} IF NOT (INITIALIZED) THEN BEGIN {+} { Disable ASTs, and remember the previous state. {-} AST_STATUS := SYS$SETAST (DISABLE); {+} { Now, recheck the flag. If it is now set, initialization was { performed by another invocation of this procedure between when { the flag was first tested and now. Otherwise, initialization { is performed here. {-} IF NOT (INITIALIZED) THEN BEGIN {+} { Perform the initialization. {-} VM_STATUS := LIB$GET_VM (VM_SIZE, VM_ADDRESS); {+} { Set the first-time flag, indicating initialization complete. {-} INITIALIZED := TRUE; END; {+} { Restore ASTs to the previous state. {-} AST_STATUS := SYS$SETAST (AST_STATUS); END; END. |
One way to initialize a value at run time is by using the PSECT LIB$INITIALIZE. An example of a value that you may need to initialize at run time is a seed for a random number generator.
To use LIB$INITIALIZE to initialize a value at run time, you must do the following:
Assuming that you have completed the main program, the first thing you must do is to 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.
Example 3-2 BASIC Initialization Procedure for LIB$INITIALIZE |
---|
100 !+ ! Initialization routine. A common piece of data, called SEED, ! is initialized based on the number of CPU seconds used by ! this process so far. !- SUB MY_INIT_ROUTINE(ONE,TWO,THREE,FOUR,FIVE,SIX) COMMON (MY_DATA) LONG SEED PRINT "Now in initialization routine." CURRENT_TIME = TIME(1) SEED = CURRENT_TIME END SUB |
Example 3-3 Program to Add Address to PSECT LIB$INITIALIZE |
---|
;+ ; Make references to external routines used. ;- .EXTRN LIB$INITIALIZE .EXTRN MY_INIT_ROUTINE ;+ ; Make a contribution to the PSECT LIB$INITIALIZE. ;- .PSECT LIB$INITIALIZE USR,GBL,NOEXE,NOWRT,LONG .ADDRESS MY_INIT_ROUTINE .END |
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.
Example 3-4 BASIC Main Program |
---|
10 !+ ! Mainline. The value of SEED is printed. ! The linker initializes this value to zero, but because ! LIB$INITIALIZE is used, an initialization routine is run ! before control is transferred ! here, and the value of SEED is changed to a ! somewhat random value. !- COMMON (MY_DATA) LONG SEED PRINT "Now in mainline. The seed is initialized to: ";SEED 32767 END |
To run LIB$INITIALIZE on the program in Example 3-4 and initialize the value of SEED at run time, enter the following commands:
$ BASIC MAIN $ BASIC INIT $ MACRO INIT_SECTION $ LINK MAIN,INIT,LIBRARY $ RUN MAIN |
The following is an example of the output generated by these steps:
Now in initialization routine. Now in mainline. The seed is initialized to: 4099 |
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.
3.3 Writing AST-Reentrant Code
This section describes coding techniques for modular procedures that use the asynchronous system trap (AST) interrupt mechanism or that permit calling programs to use it.
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.
3.3.1 What Is an AST?
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.
3.3.2 AST Reentrancy Versus Full Reentrancy
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 DEC 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.
It is highly desirable that future code satisfy the more stringent requirement of being fully reentrant. Full reentrancy is important for procedures that will be called from multithread environments, such as Ada tasks. For more information, refer to the Ada documentation. |
DECthreads, the Digital 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.
3.3.3 Writing AST-Reentrant Modular Procedures
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:
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:
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.
Example 3-5 VAX MACRO Program Showing Use of Queue Instructions to Perform All Accesses in a Single Instruction |
---|
.PSECT _LIB_DATA PIC,USR,CON,REL,LCL,NOSHR,NOEXE,RD,WRT FLAG: .LONG 0 ; First-time flag (1) Q_HED .LONG 0,0 .PSECT _LIB_CODE PIC,USR,CON,REL,LCL,SHR,EXE,RD,NOWRT .ENTRY LIB_GET_X,^M<> BBC FLAG, FIRST ; Branch on 1st call only TRY: REMQUE @Q_HED, R0 ; R0 = address of queue BVS 10$ ; Branch if empty and fill RET 10$: BSBB FILL ; Fill queues BRB TRY ; Try again ;+ ; Here on first call only ;- FIRST: $SETAST #0 ; Disable ASTs, R0=old setting BBSS FLAG, 20$ ; Branch if already set MOVAL Q_HED, Q_HED ; Make queue empty MOVAL Q_HED, Q_HED+4 ; Back pointer too BSBB FILL ; Fill queues 20$: CMPL #SS$_WASSET, R0 ; were ASTs enabled before? BNEQ TRY ; No, leave disabled, retry $SETAST 1 ; Yes, enable ASTs BRB TRY ; Try again FILL: get space for 10 quadwords by calling LIB$GET_VM and insert in queue using INSQUE RSB |
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.
Example 3-6 MACRO Program Showing Use of Test and Set Instructions |
---|
.TITLE LIB_GET_INUM -- Allocate and deallocate id. nos. 1 - 10 TAB: .WORD 0 ; Bitmap for flags .ENTRY LIB_GET_INUM, ^M<> 10$: FFC #1, #10,TAB, R0 ; Find first free id, no. BEQ 20$ ; Branch if none free BBSS R0, TAB, 10$ ; Indicate id. no. in use MOVL R0, @4(AP) ; Return id. no. found MOVL #1, R0 ; Indicate success RET 20$: CLRL @4(AP) ; Return 0 CLRL R0 ; Indicate failure RET .END |
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.
Previous | Next | Contents | Index |
Copyright © Compaq Computer Corporation 1998. All rights reserved. Legal |
4518PRO_004.HTML
|