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):

  1. 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.
  2. Redirecting Vectors: The TSR calls INT 21h, Function 25h (Set Interrupt Vector) to point the target interrupt vector to the TSR's internal handler.
  3. 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 IRET instruction 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 the InDOS flag and the Critical Error flag. When MS-DOS begins handling an INT 21h service, it increments the InDOS flag, and decrements it upon completion. If a timer interrupt (INT 08h) fires and the TSR discovers InDOS > 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 via INT 21h, Function 0Ah, the InDOS flag remains set, which would normally block background tasks. To resolve this, DOS repeatedly issues INT 28h inside its wait loop. A TSR hooked into INT 28h knows it can safely invoke low-priority DOS calls (Functions 01h through 0Ch) even though InDOS is 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.