Longpela Expertise logo
Longpela Expertise Consulting
Longpela Expertise
Home | Press Room | Contact Us | Site Map
FAQ


LongEx Mainframe Quarterly - August 2010
 

technical: Understanding Re-Entrant Programming

Program long enough, and sooner or later you’ll need to write a re-entrant program. But what is a re-entrant program, and how do you make one?

What is a Re-Entrant Program?

A re-entrant program (or a thread-safe program, if you’re more familiar with UNIX) is a program that doesn’t change itself. I know what you're thinking: “how can a program change itself?” Well there are actually three parts to a computer program:

  • Program code – the instructions that are executed when a program runs.
  • Constants – constants used in a program. These are set when the program is written, and never change.
  • Storage area – or variables. This area changes as the program runs.

Take a look at the diagram below of Program 1. Program 1 isn’t re-entrant so the code, constants and storage area are all together. When a task runs Program 1, it loads the code, constants and storage area into memory from disk, and then executes the code. So when Program 1 runs, it actually changes itself, as it changes its storage area.

If a second task wants to run Program 1 at the same time, it can't run the copy that's already in memory. If it did it would overwrite the storage area still being used by the first task. So a completely new copy must be loaded into memory from disk.

Compare this with Program 2. Program 2 is re-entrant, which means that only the code and constants are together. When a task runs Program 2, it is loaded into memory like Program 1. However Program 2 then gets and uses its own storage area, and frees it at the end. This means that many task can use Program 2 at the same time (without loading the program from disk every time) - each time it runs it gets a new storage area.

Why Write Re-Entrant Programs?

There are lots of reasons why your program may need to be re-entrant. If you’re writing in High Level Assembler (HLASM), your program must be re-entrant if it:

  • Will be loaded in LPA
  • Is an SVC.
  • Is an exit that requires re-entrant code (which is most of them).
  • Is loaded in memory and can be used by more than one task (or TCB) at the same time.
  • Will be bound as a DLL.
  • Is a CICS program or exit that will use the Open Transaction Environment (OTE) – or in other words, run on a TCB other than the CICS Quasi-Reentrant TCB. You can find out more about CICS and threadsafe from the references at the end of this article.

In fact, I write all my HLASM code as re-entrant. This way if my program is later used in a way that I didn’t originally consider, there's no chance of any re-entrancy problems. Trying to debug a program with an error caused by a re-entrancy issue can be a real nightmare. I've also found that with a bit of practice, coding a re-entrant routine doesn't take any more work or time than a non-reentrant routine.

But it’s not only HLASM programs that may need to be re-entrant. Enterprise COBOL programs must be re-entrant if they:

  • Run in CICS
  • Are preloaded in IMS
  • Will be used as DB2 stored procedures
  • Run in UNIX Systems Services
  • Need DLL support
  • Use object-oriented syntax

Similar issues apply to PL/1 and C programs.

How to Write Re-Entrant Programs in HLLs

If you’re programming in a high level language (HLL) like COBOL, the good news is that it’s easy to write a re-entrant program. Just compile the program using the RENT option. Nothing simpler.

How to Write Re-Entrant Programs in HLASM

If you’re writing a re-entrant program in HLASM, you need to do some extra work yourself. You’re going to have to:

  • Get some working storage. You’ll need to Getmain your storage right at the beginning of the program, and use a DSECT to map it. Don’t forget to free this storage at the end of the program.
  • Use that working storage. ALL variables have to be in the DSECT that maps to our working storage. If there's any chance that it can change, use your Getmained storage.
  • Use List and Execute Forms of macros. Some HLASM macros will have two special formats for re-entrant programmers: a List and Execute form. This is because many macros aren't suitable for re-entrant programs by default. Or in other words, they change their own storage when expanded out. You use the List and Execute forms like this:
    1. You specify the List form in the working storage - it saves storage for the Execute form.
    2. You use the Execute form, pointing to the List form. This forces the macro to use your working storage, rather than changing storage inline.
    Whenever you use a macro in a re-entrant program, check to see if it has separate List and Execute forms. If it does, use them. Our example below shows how.
  • Serialise shared resources. You're writing a re-entrant program for a reason: so more than one task can run it at the same time. This means you have to make sure you serialise access to any shared resources.

    Let’s say that you have a word in memory that is shared between several different tasks. In this case, you need to do something so two different tasks won't try to update it at the same time – a way to serialise access.

    You can do this using the Compare and Swap (CS) instruction, or ENQ/DEQ macros (using the List and Execute forms, of course). The example below shows how to use Compare and Swap.

A Re-Entrant HLASM Program Example

So let’s look at an example:

*=====================================           
* Main Program                                   
*=====================================           
TST1  AMODE 31                                   
TST1  RMODE ANY                                  
TST1  RSECT                                      
* --- Save Callers Environment ---------------   
         BAKR  R14,0                             
* --- Setup Program Addressability -----------   
         LR    R12,R15                           
         USING TST1,R12                          
* --- Get and Address Workarea ---------------   
         USING WORK,R13                          
         LHI   R2,@WORKL                         
         STORAGE OBTAIN,LENGTH=(R2),LOC=BELOW    
         LR    R13,R1             R13 -> Workarea
* --- Write Message to Console  --------------   
         LA    R2,#MSG                           
*        WTO   TEXT=(R2)                         
         MVC   WWTO(@WTOL),#WTO   Move WTO model 
         XR    R0,R0                             
         WTO   TEXT=(R2),MF=(E,WWTO)   (note 3)  
* --- Example of an OPEN and CLOSE Macro  ----   
         MVC   WDCB(@MODELL),#DCB Move model over
*                                 (note 6)       
         LA    R3,WDCB                                 
         OPEN  ((3),INPUT),MF=(E,WOPEN)  Open DD1      
         CLOSE ((3)),MF=(E,WCLOSE) Close DD1           
* --- Add one to original value in block  ---- (note 5)
         LA    R5,WWORD                                
         L     R1,0(R5)           R1 = value           
ADDVAL   DS    0H                                      
         LA    R2,1(R1)           R2 = value+1         
         L     R1,0(R5)           R1 = value           
         CS    R1,R2,0(R5)        Insert value+1       
         BC    2,ADDVAL           (unless it changed)  
* --- Free Working Storage and Return --------         
         LA    R2,@WORKL                               
         STORAGE RELEASE,ADDR=(R13),LENGTH=(R2)        
         PR                       Return               
*=====================================                 
* Constants                                            
*=====================================                 
* --- Message Constants ----------------------         
#MSG     DC    AL2(L'#MSGTXT) (note 4)                 
#MSGTXT  DC    C'PGM 1 MESSAGE.'                       
#WTO     WTO   TEXT=,MF=L         List form of WTO     
@WTOL    EQU   *-#WTO             Length model area    
* --- I/O Macro Model Area --------------------    
#DCB     DCB   DDNAME=DD1,DSORG=PS,MACRF=GM        
#OPEN    OPEN  (,INPUT),MF=L                       
#CLOSE   CLOSE (),MF=L                             
@MODELL  EQU   *-#DCB             Length model area
         LTORG                                     
*=====================================             
* Map Working storage                              
*=====================================             
WORK     DSECT                                     
SAVE1    DS    18F                Savearea         
WWORD    DS    F                  Word             
WWTO     WTO   TEXT=,MF=L         List form        
WDCB     DCB   DDNAME=DD1,DSORG=PS,MACRF=GM        
WOPEN    OPEN  (,INPUT),MF=L                       
WCLOSE   CLOSE (),MF=L                             
@WORKL   EQU   *-SAVE1            Length           
         YREGS ,                                   
         END

Notes:

  1. I've used an RSECT instead of a CSECT, which tells the assembler that this is a re-entrant module. The assembler will check for some (but not all) re-entrancy errors. Using the RENT assembler option does the same thing.
  2. The STORAGE macro doesn’t have a List and Execute form - it is re-entrant in itself. How do we know this? Because there's no separate List and Execute form (check it out for yourself in the z/OS Assembler Services Reference manual). In our program, we get storage below the 24 bit line, as our storage includes a DCB.
  3. I use the List and Execute form of the WTO macro, simply because it has them.
  4. The #MSG label is a constant, so it can be left in the program code. However if it was a variable (ie. updated by the program in any way), it would have to be moved into the WORK DSECT.
  5. This program shows how to use the Compare and Swap instruction to serialise access to a control block. The address of a field is passed to the program by the caller. Our program increments this value like this:
    • It gets the original value from the control block, and puts it into R1.
    • It increments this value and puts it in R2.
    • It gets the original value from the control block again, and puts it in R1.
    • In one instruction (Compare and Swap), it inserts R2 into the control block if the original value hasn’t changed. Otherwise it puts the changed original value into R1, and sets the condition code to branch.
  6. Some older z/OS macros include constants in their List form. In my example program, DCB, OPEN and CLOSE fall into this category.

    Using the List form of these macros in my WORK DSECT loses these constants. I've worked around this by defining a model area in the program for these macros, and copied this over an equivalent area in my storage. This is a very standard way of dealing with this problem.

    Let's look at my WOPEN label. If you look at the macro expansion (from the assembler output), you would see something like:

      WOPEN OPEN 0,MF=L
      +WOPEN DC 0F'0'
      + DC AL1(128)
      + DC AL3(0)
      + DC A(0)

    You can see z/OS defining the byte constant ‘128.’ However this doesn’t work with my storage – I reset it to zeroes at the beginning. So I define a second OPEN macro in my constants area (#OPEN - with the ‘128’ constant). I then copy this over the WOPEN in my storage so I have that constant.

    So the big question is “How do you know if you need to do this?” The only way is to look at the assembler expansion of the List form of the macro. If you see a constant, then you'll need a macro model.

Binding and Running Re-Entrant Programs

When writing a re-entrant program, you must bind it as a re-entrant module using the RENT binder option. Without this option, a fresh copy of the entire module will be loaded into memory every time a programs LOADs (or LINKs) it. Some things to remember:

  • RENT is not the same as REUS (re-usable) or REFR (refreshable). A reusable module does not have to be re-entrant, and a re-entrant module does not have to be refreshable.
  • RENT applies to the entire module. You cannot mix re-entrant and non-re-entrant programs in one module.

Checking Your Program is Re-Entrant

Ok, so you've finished your re-entrant program. But is it really re-entrant, or are you writing over part of your program's storage without knowing? Using the assembler RENT option or RSECT statement will pick up many, but not all of your re-entrancy problems. There are a couple of ways to really make sure your program is running re-entrant:

  • If your program is APF authorised, then this is already done. z/OS will load your module into subpool 252. Any program (that is not running in key 0 or supervisor state - including yours) that tries to change your module’s memory will abend with a S0C4.
  • Use the (undocumented) CsvRentSp252 option in your DIAGxx parmlib member. This forces all re-entrant programs (APF authorised or not) to be loaded into subpool 252.
    Warning: Use this with caution, and do NOT set this value in your production environment. Some of your software (including some program products) may not be truly re-entrant. Using this option could abend your environment.
  • Use the (undocumented) CsvRentProtect option in your DIAGxx parmlib member. The previous two examples won’t detect problems with programs that run in key 0 or supervisor state. Using the CSVRENTPROTECT option will. With this option, any program that attempts to write over your program storage will S0C4 abend.
    Warning: Again, use this with caution, and do NOT set this value in your production environment. Some of your software (including some program products) may not be truly re-entrant. Using this option could abend your environment.
  • If you’re running in CICS, use the RENTPGM=PROTECT SIT option. In this case, your program will get a DFHSR0622 message and protection exception/ S0C4 if it tries to overwrite its storage. Use this options with caution in your production CICS environments.

Wrap Up

There are many times when you need to write a re-entrant program. But the good news is that it isn’t hard. High level language programmers just need the RENT option, Assembler programmers need to do a bit more.

If there’s any chance that more than one task can execute a common copy of your program at the same time, you must make your program re-entrant. In fact many programmers make all their programs re-entrant as a matter of course, including me.

References

(04-Nov-2015) Thanks Michael Sinitsky for pointing out some errors in our sample program

David Stephens



LongEx Quarterly is a quarterly eZine produced by Longpela Expertise. It provides Mainframe articles for management and technical experts. It is published every November, February, May and August.

The opinions in this article are solely those of the author, and do not necessarily represent the opinions of any other person or organisation. All trademarks, trade names, service marks and logos referenced in these articles belong to their respective companies.

Although Longpela Expertise may be paid by organisations reprinting our articles, all articles are independent. Longpela Expertise has not been paid money by any vendor or company to write any articles appearing in our e-zine.

Inside This Month

Printer Friendly Version

Read Previous Articles


Longpela Expertise can develop, debug and support assembler applications, exits and systems routines. Contact us to get your own assembler expert.
© Copyright 2010 Longpela Expertise  |  ABN 55 072 652 147
Legal Disclaimer | Privacy Policy Australia
Website Design: Hecate Jay