Chapter Eighteen (Part 2)
|Table of Content||
Chapter Eighteen (Part 4)
RESIDENT PROGRAMS (Part 3)
18.3.1 - Reentrancy Problems with DOS
18.3.2 - Reentrancy Problems with BIOS
18.3.3 - Reentrancy Problems with Other Code
18.4 - The Multiplex Interrupt (INT 2Fh)
One big problem with active TSRs is that their invocation is asynchronous. They can activate at the touch of a keystroke timer interrupt or via an incoming character on the serial port just to name a few. Since they activate on a hardware interrupt the PC could have been executing just about any code when the interrupt came along. This isn't a problem unless the TSR itself decides to call some foreign code such as DOS a BIOS routine or some other TSR. For example the main application may be making a DOS call when a timer interrupt activates a TSR interrupting the call to DOS while the CPU is still executing code inside DOS. If the TSR attempts to make a call to DOS at this point then this will reenter DOS. Of course DOS is not reentrant so this creates all kinds of problems (usually it hangs the system). When writing active TSRs that call other routines besides those provided directly in the TSR you must be aware of possible reentrancy problems.
Note that passive TSRs never suffer from this problem. Indeed any TSR routine you call passively will execute in the caller's environment. Unless some other hardware ISR or active TSR makes the call to your routine you do not need to worry about reentrancy with passive routines. However reentrancy is an issue for active TSR routines and passive routines that active TSRs call.
18.3.1 Reentrancy Problems with DOS
DOS is probably the biggest sore point to TSR developers. DOS is not reentrant yet DOS contains many services a TSR might use. Realizing this Microsoft has added some support to DOS to allow TSRs to see if DOS is currently active. After all reentrancy is only a problem if you call DOS while it is already active. If it isn't already active you can certainly call it from a TSR with no ill effects.
MS-DOS provides a special one-byte flag ( InDOS) that
contains a zero if DOS is currently active and a non-zero value if DOS is already
processing an application request. By testing the InDOS flag your TSR can determine if it
can safely make a DOS call. If this flag is zero
you can always make the DOS call. If
this flag contains one
you may not be able to make the DOS call. MS-DOS provides a
Get InDOS Flag Address
that returns the address of the InDOS flag. To use
ah with 34h and call DOS. DOS will return the address of
the InDOS flag in
es:bx. If you save this address
your resident programs
will be able to test the InDOS flag to see if DOS is active.
Actually there are two flags you should test the InDOS flag and the critical error flag (criterr). Both of these flags should contain zero before you call DOS from a TSR. In DOS version 3.1 and later the critical error flag appears in the byte just before the InDOS flag.
So what should you do if these flags aren't both zero? It's easy enough to say "hey come back and do this stuff later when MS-DOS returns back to the user program." But how do you do this? For example if a keyboard interrupt activates your TSR and you pass control on to the real keyboard handler because DOS is busy you can't expect your TSR to be magically restarted later on when DOS is no longer active.
The trick is to patch your TSR into the timer interrupt as well as the keyboard interrupt. When the keystroke interrupt wakes your TSR and you discover that DOS is busy the keyboard ISR can simply set a flag to tell itself to try again later; then it passes control to the original keyboard handler. In the meantime a timer ISR you've written is constantly checking this flag you've created. If the flag is clear it simply passes control on to the original timer interrupt handler if the flag is set then the code checks the InDOS and CritErr flags. If these guys say that DOS is busy the timer ISR passes control on to the original timer handler. Shortly after DOS finishes whatever it was doing a timer interrupt will come along and detect that DOS is no longer active. Now your ISR can take over and make any necessary calls to DOS that it wants. Of course once your timer code determines that DOS is not busy it should clear the "I want service" flag so that future timer interrupts don't inadvertently restart the TSR.
There is only one problem with this approach. There are
certain DOS calls that can take an indefinite amount of time to execute. For example
you call DOS to read a key from the keyboard (or call the Standard Library's
routine that calls DOS to read a key)
it could be hours
or even longer before
somebody actually bothers to press a key. Inside DOS there is a loop that waits until the
user actually presses a key. And until the user presses some key
the InDOS flag is going
to remain non-zero. If you've written a timer-based TSR that is buffering data every few
seconds and needs to write the results to disk every now and then
you will overflow your
buffer with new data if you wait for the user
who just went to lunch
to press a key in
DOS' command.com program.
MS-DOS provides a solution to this problem as well
- the idle interrupt. While MS-DOS is in an indefinite loop wait for an I/O device
continually executes an
int 28h instruction. By patching into the int 28h
your TSR can determine when DOS is sitting in such a loop. When DOS executes the
int 28h instruction
it is safe to make any DOS call whose function number (the value in
is greater than 0Ch.
So if DOS is busy when your TSR wants to make a DOS call you must use either a timer interrupt or the idle interrupt (int 28h) to activate the portion of your TSR that must make DOS calls. One final thing to keep in mind is that whenever you test or modify any of the above mentioned flags you are in a critical section. Make sure the interrupts are off. If not your TSR make activate two copies of itself or you may wind up entering DOS at the same time some other TSR enters DOS.
An example of a TSR using these techniques will appear a little later but there are some additional reentrancy problems we need to discuss first.
18.3.2 Reentrancy Problems with BIOS
DOS isn't the only non-reentrant code a TSR might want to call. The PC's BIOS routines also fall into this category. Unfortunately BIOS doesn't provide an "InBIOS" flag or a multiplex interrupt. You will have to supply such functionality yourself.
The key to preventing reentering a BIOS routine you want to call is to use a wrapper. A wrapper is a short ISR that patches into an existing BIOS interrupt specifically to manipulate an InUse flag. For example suppose you need to make an int 10h (video services) call from within your TSR. You could use the following code to provide an "Int10InUse" flag that your TSR could test:
MyInt10 proc far inc cs:Int10InUse pushf call cs:OldInt10 dec cs:Int10InUse iret MyInt10 endp
Assuming you've initialized the Int10InUse variable to zero the in use flag will contain zero when it is safe to execute an int 10h instruction in your TSR it will contain a non-zero value when the interrupt 10h handler is busy. You can use this flag like the InDOS flag to defer the execution of your TSR code.
Like DOS there are certain BIOS routines that may take an indefinite amount of time to complete. Reading a key from the keyboard buffer reading or writing characters on the serial port or printing characters to the printer are some examples. While in some cases it is possible to create a wrapper that lets your TSR activate itself while a BIOS routine is executing one of these polling loops there is probably no benefit to doing so. For example if an application program is waiting for the printer to take a character before it sends another to printer having your TSR preempt this and attempt to send a character to the printer won't accomplish much (other than scramble the data sent to the print). Therefore BIOS wrappers generally don't worry about indefinite postponement in a BIOS routine.
5 8 9 D E 10 13 16 17 21 28
If you run into problems with your TSR code and certain application programs you may want to place wrappers around the following interrupts to see if this solves your problem: int 5 int 8 int 9 int B int C int D int E int 10 int 13 int 14 int 16 or int 17. These are common culprits when TSR problems develop.
18.3.3 Reentrancy Problems with Other Code
Reentrancy problems occur in other code you might call as well. For example consider the UCR Standard Library. The UCR Standard Library is not reentrant. This usually isn't much of a problem for a couple of reasons. First most TSRs do not call Standard Library subroutines. Instead they provide results that normal applications can use; those applications use the Standard Library routines to manipulate such results. A second reason is that were you to include some Standard Library routines in a TSR the application would have a separate copy of the library routines. The TSR might execute an strcmp instruction while the application is in the middle of an strcmp routine but these are not the same routines! The TSR is not reentering the application's code it is executing a separate routine.
However many of the Standard Library functions make DOS or BIOS calls. Such calls do not check to see if DOS or BIOS is already active. Therefore calling many Standard Library routines from within a TSR may cause you to reenter DOS or BIOS.
One situation does exist where a TSR could reenter a Standard Library routine. Suppose your TSR has both passive and active components. If the main application makes a call to a passive routine in your TSR and that routine call a Standard Library routine there is the possibility that a system interrupt could interrupt the Standard Library routine and the active portion of the TSR reenter that same code. Although such a situation would be extremely rare you should be aware of this possibility.
Of course the best solution is to avoid using the Standard Library within your TSRs. If for no other reason the Standard Library routines are quite large and TSRs should be as small as possible.
When installing a passive TSR or an active TSR with passive components you will need to choose some interrupt vector to patch so other programs can communicate with your passive routines. You could pick an interrupt vector almost at random say int 84h but this could lead to some compatibility problems. What happens if someone else is already using that interrupt vector? Sometimes the choice of interrupt vector is clear. For example if your passive TSR is extended the int 16h keyboard services it makes sense to patch in to the int 16h vector and add additional functions above and beyond those already provided by the BIOS. On the other hand if you are creating a driver for some brand new device for the PC you probably would not want to piggyback the support functions for this device on some other interrupt. Yet arbitrarily picking an unused interrupt vector is risky; how many other programs out there decided to do the same thing? Fortunately MS-DOS provides a solution: the multiplex interrupt. Int 2Fh provides a general mechanism for installing testing the presence of and communicating with a TSR.
To use the multiplex interrupt
an application places an
identification value in
ah and a function number in
al and then
int 2Fh instruction. Each TSR in the int 2Fh chain compares the
ah against its own unique identifier value. If the values match
TSR process the command specified by the value in the
al register. If the
identification values do not match
the TSR passes control to the next int 2Fh handler in
this only reduces the problem somewhat
doesn't eliminate it. Sure
we don't have to guess an interrupt vector number at random
but we still have to choose a random identification number. After all
it seems reasonable
that we must choose this number before designing the TSR and any applications that call
how will the applications know what value to load into
we dynamically assign this value when the TSR goes resident?
there is a little trick we can play to dynamically
assign TSR identifiers and let any interested applications determine the TSR's ID. By
function zero is the "Are you there?" call. An application should
always execute this function to determine if the TSR is actually present in memory before
making any service requests. Normally
function zero returns a zero in al if the TSR is
it returns 0FFh if it is present. However
when this function returns 0FFh it
only tells you that some TSR has responded to your query; it does not guarantee that the
TSR you are interested in is actually present in memory. However
by extending the
it is very easy to verify the presence of the desired TSR. Suppose
the function zero call also returns a pointer to a unique identification string in the
registers. Then the code testing for the presence of a specific TSR could test this
string when the int 2Fh call detects the presence of a TSR. the following code segment
demonstrates how a TSR could determine if a TSR identified as "Randy's INT 10h
Extension" is present in memory; this code will also determine the unique
identification code for that TSR
for future reference:
; Scan through all the possible TSR IDs. If one is installed see if ; it's the TSR we're interested in. mov cx 0FFh ;This will be the ID number. IDLoop: mov ah cl ;ID -> AH. push cx ;Preserve CX across call mov al 0 ;Test presence function code. int 2Fh ;Call multiplex interrupt. pop cx ;Restore CX. cmp al 0 ;Installed TSR? je TryNext ;Returns zero if none there. strcmpl ;See if it's the one we want. byte "Randy's INT " byte "10h Extension" 0 je Success ;Branch off if it is ours. TryNext: loop IDLoop ;Otherwise try the next one. jmp NotInstalled ;Failure if we get to this point. Success: mov FuncID cl ;Save function result. . . .
If this code succeeds the variable FuncId contains the identification value for resident TSR. If it fails the application program probably needs to abort or otherwise ensure that it never calls the missing TSR.
The code above lets an application easily detect the presence of and determine the ID number for a specific TSR. The next question is "How do we pick the ID number for the TSR in the first place?" The next section will address that issue as well as how the TSR must respond to the multiplex interrupt.
Chapter Eighteen: Resident Programs
29 SEP 1996