The following sections outline in detail how SNAP works. Each part of the outline covers a specific portion of SNAP's code. Headings refer to earlier sections of this chapter, providing cross-references to SNAP's key procedures. For example, the part of the outline that describes how SNAP searches for its start-up signal refers to Section 19.2.1, “Auditing Hardware Events for TSR Requests.”
Figures 19.2 through 19.4 are flow charts of the SNAP program. Each chart illustrates a separate phase of SNAP's operation, from installation through memory-residency to deinstallation.
As you read through the following outline, you may wish to refer to the flow charts. They will help you maintain a larger perspective while exploring the details of SNAP's operation. Discussions in the outline cross-reference the charts.
Note that information in both the outline and the flow charts is generic. Except for references to the SNAP procedure, all descriptions in the outline and the flow charts apply to any TSR created with the HANDLERS and INSTALL modules.
Auditing Hardware Events for TSR Requests
To search for its start-up signal, SNAP audits the keyboard with an interrupt handler for either Interrupt 09 (keyboard) or Interrupt 15h (Miscellaneous System Services). See Section 19.2.1 for information on this topic. The Install procedure determines which of the two interrupts to handle based on the following code:
.IF HotScan == 0 ; If valid scan code given:
mov ah, HotShift ; AH = hour to activate
mov al, HotMask ; AL = minute to activate
call GetTimeToElapse ; Get number of 5-second intervals
mov CountDown, ax ; to elapse before activation
.ELSE ; Force use of KeybrdMonitor as
; keyboard handler
cmp Version, 031Eh ; DOS Version 3.3 or higher?
jb setup ; No? Skip next step
; Test for IBM PS/2 series. If not PS/2, use Keybrd and
; SkipMiscServ as handlers for Interrupts 09 and 15h
; respectively. If PS/2 system, set up KeybrdMonitor as the
; Interrupt 09 handler. Audit keystrokes with MiscServ
; handler, which searches for the hot key by handling calls
; to Interrupt 15h (Miscellaneous System Services). Refer to
; Section 19.2.1 for more information about keyboard handlers.
mov ax, 0C00h ; Function 0Ch (Get System
int 15h ; Configuration Parameters)
sti ; Compaq ROM may leave disabled
jc setup ; If carry set,
or ah, ah ; or if AH not 0,
jnz setup ; services are not supported
; Test bit 4 to see if Intercept is implemented
test BYTE PTR es:[bx+5], 00010000y
jz setup
; If so, set up MiscServ as Interrupt 15h handler
mov ax, OFFSET MiscServ
mov WORD PTR intMisc.NewHand, ax
.ENDIF
; Set up KeybrdMonitor as Interrupt 09 handler
mov ax, OFFSET KeybrdMonitor
mov WORD PTR intKeybrd.NewHand, ax
This is the code's logic:
If the program is running under DOS version 3.3 or higher and if Interrupt 15h supports Function 4Fh, set up handler MiscServ to search for the hot key. Handle Interrupt 09 with KeybrdMonitor only to maintain the keyboard active flag.
Otherwise, set up a handler for Interrupt 09 to search for the hot key. Handle calls to Interrupt 15h with the routine SkipMiscServ, which contains this single instruction:
jmp cs:intMisc.OldHand
The jump immediately passes control to the original Interrupt 15h routine; thus, SkipMiscServ has no effect. It serves only to simplify coding in other parts of the program.
At each keystroke, the keyboard interrupt handler (either Keybrd or MiscServ) calls the procedure CheckHotKey with the scan code of the current key. CheckHotKey compares the scan code and shift status with the bytes HotScan and HotShift. If the current key matches, CheckHotKey returns the carry flag clear to indicate that the user has pressed the hot key.
If the keyboard handler finds the carry flag clear, it sets the flag TsrRequestFlag and exits. Otherwise, the handler transfers control to the original interrupt routine to service the interrupt.
The timer handler Clock reads the request flag at every occurrence of the timer interrupt. Clock takes no action if it finds a zero value in TsrRequestFlag. Figures 19.1 and 19.3 depict the relationship between the keyboard and timer handlers.
Monitoring System Status
Because SNAP produces output to both video and disk, it avoids interrupting either video or disk operations. The program uses interrupt handlers Video and DiskIO to monitor Interrupts 10h (video) and 13h (disk). SNAP also avoids interrupting keyboard use. The instructions at the far label KeybrdMonitor serve as the monitor handler for Interrupt 09 (keyboard). See Section 19.2.2 for information on this topic.
The three handlers perform similar functions. Each sets an active flag and then calls the original routine to service the interrupt. When the service routine returns, the handler clears the active flag to indicate that the device is no longer in use.
The BIOS Interrupt 13h routine clears or sets the carry flag to indicate the operation's success or failure. DiskIO therefore preserves the flags register when returning, as shown here:
DiskIO PROC FAR
mov cs:intDiskIO.Flag, TRUE ; Set active flag
; Simulate interrupt by pushing flags and far-calling old
; Int 13h routine
pushf
call cs:intDiskIO.OldHand
; Clear active flag without disturbing flags register
mov cs:intDiskIO.Flag, FALSE
sti ; Enable interrupts
; Simulate IRET without popping flags (since services use
; carry flag)
ret 2
DiskIO ENDP
The terminating RET 2 instruction discards the original flags from the stack when the handler returns.
Determining Whether to Invoke the TSR
The procedure CheckRequest determines if the TSR
Has been requested
Can safely interrupt the system
Each time it executes, the timer handler Clock calls CheckRequest to read the flag TsrRequestFlag. If CheckRequest finds the flag set, it scans other flags maintained by the TSR's interrupt handlers and by DOS. These flags indicate the current system status. As the flow chart in Figure 19.3 shows, CheckRequest calls CheckDos (described below) to determine the status of the operating system. CheckRequest then calls CheckHardware to check hardware status. See Section 19.2.2 for information on this topic.
CheckHardware queries the interrupt controller to determine if any device is currently being serviced. It also reads the active flags maintained by the KeybrdMonitor, Video, and DiskIO handlers. If the controller, keyboard, video, and disk are all inactive, CheckHardware clears the carry flag and returns.
CheckRequest indicates system status with the carry flag. If the procedure returns the carry flag set, the caller exits without invoking the TSR. A clear carry signals that the caller can safely execute the TSR.
Determining DOS Activity
As Figure 19.2 shows, the procedure GetDosFlags locates the InDos flag during SNAP's installation phase. GetDosFlags calls Function 34h (Get Address of InDos Flag) and then stores the flag's address in the far pointer InDosAddr. See Section 19.4.2 for information on this topic.
When called from the CheckRequest procedure, CheckDos reads InDos to determine if the operating system is active. Note that CheckDos reads the flag directly from the address in InDosAddr. It does not call Function 34h to locate the flag, since it has not yet established whether DOS is active. This follows from the general rule that interrupt handlers must not call any DOS function.
The next two sections describe the procedure CheckDos more fully.
Interrupting DOS Functions
Figure 19.3 shows that the call to CheckDos can initiate either from Clock (timer handler) or Idle (Interrupt 28h handler). If CheckDos finds the InDos flag set, it reacts in different ways depending on the caller:
If called from Clock, CheckDos cannot know which DOS function is active. In this case, it returns the carry flag set, indicating that Clock must deny the request for the TSR.
If called from Idle, CheckDos assumes that one of the low-order polling functions is active. It therefore clears the carry flag to let the caller know the TSR can safely interrupt the function.
See Section 19.4.3 for information on this topic.
Monitoring the Critical Error Flag
The procedure GetDosFlags (Figure 19.2) determines the address of the Critical Error flag. The procedure stores the flag's address in the far pointer CritErrAddr. See Section 19.4.4 for information on this topic.
When called from either the Clock or Idle handlers, CheckDos reads the Critical Error flag. A nonzero value in the flag indicates that the Critical Error Handler (Interrupt 24h) is processing a critical error and the TSR must not interrupt. In this case, CheckDos sets the carry flag and returns, causing the caller to exit without executing the TSR.
Trapping Errors
As Figure 19.3 shows, Clock and Idle invoke the TSR by calling the procedure Activate. See Section 19.5.1 for information on this topic. Before calling the main body of the TSR, Activate sets up the following handlers:
Handler Name | For Interrupt | Receives Control When |
CtrlBreak | 1Bh (CTRL+BREAK Handler) | CTRL+BREAK sequence entered at keyboard |
CtrlC | 23h (CTRL+C Handler) | DOS detects a CTRL+C sequence from the keyboard or input stream |
CritError | 24h (Critical Error Handler) | DOS encounters a critical error |
These handlers trap keyboard break signals and critical errors that would otherwise trigger the original handler routines. The CtrlBreak and CtrlC handlers contain a single IRET instruction, thus rendering a keyboard break ineffective. The CritError handler contains the following instructions:
CritError PROC FAR
sti
sub al, al ; Assume DOS 2.x
; Set AL = 0 for ignore error
.IF cs:major != 2 ; If DOS 3.x, set AL = 3
mov al, 3 ; DOS call fails
.ENDIF
iret
CritError ENDP
The return code in AL forces DOS to take no further action when it encounters a critical error.
As an added precaution, Activate also calls Function 33h (Get or Set CTRL+BREAK Flag) to determine the current setting of the checking flag. Activate stores the setting, then calls Function 33h again to turn off break checking.
When the TSR's main procedure finishes its work, it returns to Activate, which then restores the original setting for the checking flag. It also replaces the original vectors for Interrupts 1Bh, 23h, and 24h.
SNAP's error-trapping safeguards enable the TSR to retain control in the event of an error. Pressing CTRL+BREAK or CTRL+C at SNAP's prompt has no effect. If the user specifies a nonexistent drive—a critical error—SNAP merely beeps the speaker and returns normally.
Preserving an Existing Condition
Activate records the stack pointer SS:SP in the doubleword OldStackAddr. The procedure then resets the pointer to the address of a new stack before calling the TSR. Switching stacks ensures that SNAP has adequate stack depth while it executes. See Section 19.5.2 for information on this topic.
The label NewStack points to the top of the new stack buffer, located in the code segment of the HANDLERS.ASM module. The equate constant STACK_SIZ determines the size of the stack. The include file TSR.INC contains the declaration for STACK_SIZ.
Activate preserves the values in all registers by pushing them onto the new stack. It does not push DS, since that register is already preserved in the Clock or Idle handler.
SNAP does not alter the application's video configuration other than by moving the cursor. Figure 19.3 shows that Activate calls the procedure Snap, which executes Interrupt 10h to determine the current cursor position. Snap stores the row and column in the word OldPos. The procedure restores the cursor to its original location before returning to Activate.
Preserving Existing Data
Because SNAP does not call a DOS function that writes to the DTA, it does not need to preserve the DTA belonging to the interrupted process. However, the code for switching and restoring the DTA is included within IFDEF blocks in the procedure Activate. The equate constant DTA_SIZ, declared in the TSR.INC file, governs the assembly of the blocks as well as the size of the new DTA. See Section 19.5.3 for information on this topic.
SNAP can potentially overwrite existing extended error information by committing a file error. The program does not attempt to preserve the original information by calling Functions 59h and 5Dh. In certain rare instances, this may confuse the interrupted process after SNAP returns.
Communicating through the Multiplex Interrupt
The program uses the Multiplex interrupt (Interrupt 2Fh) to
Verify that SNAP is installed
Select a unique multiplex identity number
Locate resident data
See Section 19.6 for information on this topic.
SNAP accesses Interrupt 2Fh through the procedure CallMultiplex, as shown in Figures 19.2 and 19.4. By searching for a prior installation, CallMultiplex ensures that SNAP is not installed more than once. During deinstallation, CallMultiplex locates data required to deinstall the resi-dent TSR.
The procedure Multiplex serves as SNAP's multiplex handler. When it recognizes its identity number in AH, Multiplex determines its tasks from the function number in the AL register. The handler responds to Function 0 by returning AL equalling 0FFh and ES:DI pointing to an identifier string unique to SNAP.
CallMultiplex searches for the handler by invoking Interrupt 2Fh in a loop, beginning with a trial identity number of 192 in AH. At the start of each iteration of the loop, the procedure sets AL to zero to request presence verification from the multiplex handler. If the handler returns 0FFh in AL, CallMultiplex compares its copy of SNAP's identifier string with the text at memory location ES:DI. A failed match indicates that the multiplex handler servicing the call is not SNAP's handler. In this case, CallMultiplex increments AH and cycles back to the beginning of the loop.
The process repeats until the call to Interrupt 2Fh returns a matching identifier string at ES:DI or until AL returns as zero. A matching string verifies that SNAP is installed, since its multiplex handler has serviced the call. A return value of zero indicates that SNAP is not installed and that no multiplex handler claims the trial identity number in AH. In this case, SNAP assigns the number to its own handler.
Deinstalling TSRs
During deinstallation, CallMultiplex locates SNAP's multiplex handler as described above. The handler Multiplex receives the verification request and returns in ES the code segment of the resident program. See Section 19.7 for information on this topic.
Deinstall reads the addresses of the following interrupt handlers from the data structure in the resident code segment:
Handler Name | Description | |
Clock | Timer handler | |
Keybrd | Keyboard handler (non-PS/2) | |
KeybrdMonitor | Keyboard monitor handler (PS/2) | |
Video | Video monitor handler | |
DiskIO | Disk monitor handler | |
SkipMiscServ | Miscellaneous Systems Services handler (non-PS/2) | |
MiscServ | Miscellaneous Systems Services handler (PS/2) | |
Idle | DOS Idle handler | |
Multiplex | Multiplex handler |
Deinstall calls DOS Function 35h (Get Interrupt Vector) to retrieve the current vectors for each of the listed interrupts. By comparing each handler address with the corresponding vector, Deinstall ensures that SNAP can be safely deinstalled. Failure in any of the comparisons indicates that another TSR has been installed after SNAP and has set up a handler for the same interrupt. In this case, Deinstall returns an error code, causing the program to abort with the following message:
Can't deinstall TSR
If all addresses match, Deinstall calls Interrupt 2Fh with SNAP's identity number in AH and AL set to 1. The handler Multiplex responds by returning in ES the address of the resident code's PSP. Deinstall then calls DOS Function 25h (Set Interrupt Vector) to restore the vectors for the original service routines. This is called “unhooking” or “unchaining” the interrupt handlers.
After unhooking all of SNAP's interrupt handlers, Deinstall returns with AX pointing to the resident code's PSP. The procedure FreeTsr then calls DOS Function 49h (Release Memory) to return SNAP's memory to the operating system. The program terminates with the message
TSR deinstalled
to indicate a successful deinstallation.
Deinstalling SNAP does not guarantee more available memory space for the next program. If another TSR loads after SNAP but handles interrupts other than 08, 09, 10h, 13h, 15h, 28h, or 2Fh, SNAP still deinstalls properly. The result is a harmless gap of deallocated memory formerly occupied by SNAP. DOS can use the free memory to store the next program's environment block. However, DOS loads the program itself above the still-resident TSR.