technical: Understanding Re-Entrant Programming
Program long enough, and sooner or later youll 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 youre more familiar
with UNIX) is a program that doesnt 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 isnt 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 youre
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 didnt 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 its 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 youre programming in a high level language (HLL) like COBOL, the good
news is that its 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 youre writing a re-entrant program in HLASM, you need to do some extra
work yourself. Youre going to have to:
- Get some working storage. Youll need to Getmain your storage right
at the beginning of the program, and use a DSECT to map it. Dont 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:
- You specify the List form in the working storage - it saves storage
for the Execute form.
- 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.
Lets 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 lets 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:
- 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.
- The STORAGE macro doesnt 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.
- I use the List and Execute form of the WTO macro, simply because it has
them.
- 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.
- 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 hasnt changed. Otherwise it puts the
changed original value into R1, and sets the condition code to branch.
- 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
doesnt 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 modules
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 wont 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 youre 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 isnt hard. High level language programmers just need the
RENT option, Assembler programmers need to do a bit more.
If theres 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
|