How MS-DOS 6.22 Runs TSR Programs in the Background
This article explains how MS-DOS 6.22, fundamentally a single-tasking operating system, achieves the illusion of background execution through Terminate and Stay Resident (TSR) programs. Because MS-DOS lacks a preemptive multitasking scheduler or native thread management, TSRs rely on retaining memory allocations, intercepting hardware and software interrupt vectors, and carefully navigating DOS reentrancy limitations to execute code alongside foreground tasks.
The TSR Memory Mechanism
When a standard executable finishes running in MS-DOS, the operating system frees its allocated memory block and reclaims the Transient Program Area (TPA). A TSR bypasses this process using specific DOS system calls:
- Interrupt 21h, Function 31h (Keep Process): The program calls this service, passing a return code and the amount of memory (specified in 16-byte paragraphs) that must remain allocated.
- Interrupt 27h (Terminate and Stay Resident): An older method limited to programs smaller than 64 KB, retained for backward compatibility.
Upon receiving the termination call, MS-DOS 6.22 updates its Memory
Control Blocks (MCBs). It frees the portion of memory the program
relinquished, marks the remaining reserved memory block as protected,
and returns execution control back to the command interpreter
(COMMAND.COM) or calling application. The TSR code remains
stationary in conventional memory or Upper Memory Blocks (UMBs) via
memory managers like HIMEM.SYS and
EMM386.EXE.
Interrupt Vector Hooking
Because MS-DOS does not have a task scheduler to grant CPU cycles to
resident programs, a TSR must attach itself to hardware or software
events. It does this by modifying the Interrupt Vector Table (IVT),
which occupies the first 1,024 bytes of memory (addresses
0000:0000 to 0000:03FF):
- Reading Current Handlers: During initialization,
the TSR calls
INT 21h, Function 35h(Get Interrupt Vector) to find the address of the current interrupt service routine (ISR) and saves it in its own memory. - Redirecting Vectors: The TSR calls
INT 21h, Function 25h(Set Interrupt Vector) to point the target interrupt vector to the TSR's internal handler. - Chaining Execution: When the interrupt triggers,
the TSR's handler runs first. It inspects the hardware state or
registers, performs any necessary work, and then either terminates the
interrupt with an
IRETinstruction or jumps to the original ISR address to maintain normal system operations.
Common vectors intercepted by TSRs include:
- INT 08h (IRQ 0 - System Timer): Generated approximately 18.2 times per second by the Intel 8253/8254 timer chip. Background clocks, performance monitors, and scheduler-like tools hook this to execute time-sliced logic.
- INT 09h (IRQ 1 - Keyboard): Fired whenever a key is pressed or released. Hotkey-activated utilities (such as Borland SideKick) monitor scan codes directly from the keyboard controller to pop up over running programs.
- INT 13h (Disk BIOS Services): Used by disk caches
(such as
SMARTDRV.EXE) to intercept read and write requests before passing them to the hardware.
Overcoming DOS Reentrancy and the InDOS Flag
The primary obstacle to executing background tasks in MS-DOS 6.22 is
that the operating system is non-reentrant. MS-DOS uses static,
non-thread-safe internal data buffers and internal stacks for its
INT 21h services. If an interrupt activates a TSR while an
active application is already inside an INT 21h call,
making another INT 21h call from the TSR will corrupt the
operating system’s state and crash the machine.
To run safely, a TSR employs specific synchronization techniques:
- The InDOS Flag
(
INT 21h, Function 34h): During initialization, a TSR uses this undocumented DOS function to retrieve a pointer to internal byte counters called theInDOSflag and the Critical Error flag. When MS-DOS begins handling anINT 21hservice, it increments theInDOSflag, and decrements it upon completion. If a timer interrupt (INT 08h) fires and the TSR discoversInDOS > 0, the TSR must immediately yield and wait for another interrupt cycle. - The Idle Interrupt (
INT 28h): When MS-DOS is sitting idle at the command prompt waiting for keyboard input viaINT 21h, Function 0Ah, theInDOSflag remains set, which would normally block background tasks. To resolve this, DOS repeatedly issuesINT 28hinside its wait loop. A TSR hooked intoINT 28hknows it can safely invoke low-priority DOS calls (Functions01hthrough0Ch) even thoughInDOSis non-zero.
Context and Stack Switching
When a TSR activates in response to an interrupt, it runs on the CPU
stack of the interrupted program. Because many DOS programs allocate
very small stacks, a complex TSR risks causing a stack overflow. To
maintain stability, the TSR immediately saves the current
SS (Stack Segment) and SP (Stack Pointer)
registers, switches the CPU to its own pre-allocated private stack,
completes its tasks, and restores the original stack before returning
control to the interrupted foreground process.