Document revision date: 30 March 2001
[Compaq] [Go to the documentation home page] [How to order documentation] [Help on this site] [How to contact us]
[OpenVMS documentation]

Guide to Creating OpenVMS Modular Procedures


Previous Contents Index

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 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 (Asynchronous System Trap) reentrant, it must follow these steps:

  1. Test the first-time flag.
  2. If the first-time flag is set, initialization is complete.
  3. If the first-time flag is not set, disable ASTs. Remember the previous state of AST enable, and retest the flag.
  4. If the first-time flag is now set, initialization was performed by an AST that occurred between the first test and the AST disable; enable ASTs if remembered state of ASTs was enable. Initialization is now complete.
  5. If the first-time flag is not set, perform the initialization.
  6. Set the flag.
  7. Enable ASTs if remembered state of ASTs was enable. Initialization is complete.

For additional information, see Section 3.3.

Note

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. 

3.2.3 Using LIB$INITIALIZE

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:

  1. Write the main program.
  2. Write an initialization procedure.
  3. Write a MACRO or BLISS program to add the address of that initialization procedure to PSECT LIB$INITIALIZE.
  4. Compile the initialization procedure, main program, and MACRO program.
  5. Link the initialization procedure, main program, and MACRO program.
  6. Run the main program.

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.

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 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.

Note

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.

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.

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:

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:

3.3.4.1 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.

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 
 

  1. This example could be recoded using REMQHI and INSQHI to avoid the need for a first-time flag.

3.3.4.2 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:

  1. Place a Branch on Bit Set and Set (BBSS or BBSSI) instruction immediately before your procedure accesses static storage. Or, use LIB$BBSSI or semantics provided by your compiler.
  2. Access or modify static storage, or both.
  3. Place a Branch on Bit Clear and Clear (BBCC or BBCCI) instruction immediately after your procedure has completed access to static storage. Or, use LIB$BBSSI or semantics provided by your compiler.

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 

3.3.4.3 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.


Previous Next Contents Index

  [Go to the documentation home page] [How to order documentation] [Help on this site] [How to contact us]  
  privacy and legal statement  
4518PRO_004.HTML