Terminate-and-stay-resident program
A terminate-and-stay-resident (TSR) program is a type of software designed for MS-DOS operating systems that loads into memory, executes an initial setup, and then returns control to the DOS command interpreter while keeping a portion of its code resident in RAM to provide ongoing background services, such as intercepting hardware interrupts (e.g., keyboard or timer events) or handling utility tasks like printing without interrupting the foreground application.[1] Introduced with MS-DOS 2.0 in March 1983, TSRs addressed the limitations of DOS's single-tasking architecture by enabling pseudo-multitasking, allowing multiple programs to share system resources through techniques like interrupt hooking and time-slicing (e.g., responding to the timer interrupt at approximately 18.2 Hz).[2][1] This feature quickly gained popularity for utilities such as print spoolers (e.g., the built-in PRINT command, which queues up to 32 files in a FIFO manner), graphics loaders (GRAFTABL for character sets or GRAPHICS for printer dumps), drive redirectors (ASSIGN from MS-DOS 3.0), and file path managers (APPEND from MS-DOS 3.2), often automatically loaded at boot via the AUTOEXEC.BAT file.[1] Third-party developers extended TSRs to include keyboard enhancers (e.g., KEYBxx utilities occupying about 2 KB of RAM), screen savers, and even early network drivers, fostering an ecosystem of memory-resident tools that competed for the era's constrained 640 KB conventional memory limit.[1] Despite their utility, TSRs posed challenges, including permanent memory allocation that reduced space for primary applications (requiring manual unloading where possible, such as via parameterless invocation of commands like ASSIGN) and potential conflicts from overlapping interrupt handlers, which could cause system instability in the absence of robust memory management tools like those introduced in later DOS versions (e.g., upper memory blocks in MS-DOS 5.0).[1] By the mid-1990s, as graphical operating systems like Windows 95 superseded DOS with native multitasking and protected memory, TSRs became obsolete, though their legacy persists in emulators and retro computing contexts.Definition and History
Core Concept
A terminate-and-stay-resident (TSR) program is a utility designed for MS-DOS environments that executes its primary initialization code before returning control to the operating system, while retaining a portion of its memory allocation in RAM to enable ongoing background services.[3] This approach circumvents the inherent single-tasking limitations of DOS, which normally allows only one foreground program to run at a time, by permitting the TSR to respond to system events without full reloading.[4] The basic workflow of a TSR begins with the program loading into memory and completing setup tasks, such as installing handlers for specific interrupts. It then calls either INT 27h for smaller implementations or INT 21h with AH set to 31h for more flexible ones to "terminate" execution while preserving the designated memory block. With INT 27h, the DX register holds the offset of the last byte plus one relative to the program segment prefix (PSP), enforcing an initial size limit of 64 KB for the resident portion.[5] In contrast, INT 21h function 31h allows specification of the resident size in DX as paragraphs (16-byte units), with a minimum of 6 paragraphs (96 bytes) and expansion to full segment allocation, accommodating larger utilities.[6] Upon invocation, DOS deallocates any non-resident memory, restores relevant interrupt vectors from the PSP, and resumes the calling process or command prompt.[4] The primary benefit of this mechanism is the facilitation of interrupt-driven reactivation, allowing the TSR to perform tasks like utility functions or system extensions on demand, thereby mimicking multitasking behavior in a non-preemptive DOS system.[3] Multiple TSRs can coexist through interrupt sharing, where each new handler links to the prior one in a chain.[4]Historical Development
Terminate-and-stay-resident (TSR) programs emerged in the early 1980s alongside the development of MS-DOS for the IBM PC, with formal support introduced in DOS 2.0, released in 1983. This version of the operating system, developed entirely by Microsoft and adapted for the IBM PC's x86 architecture, built upon conceptual influences from CP/M—such as modular system calls and resident system components—but innovated TSR functionality to enable programs to remain in memory after termination, addressing the single-tasking limitations of early DOS while ensuring compatibility with IBM hardware standards.[7][8] A key innovation in DOS 2.0 was the introduction of interrupt 27h, which allowed programs to terminate while keeping up to 64 KB of code resident in memory, effectively turning them into background extensions of the OS. Both interrupt 27h and interrupt 21h function 31h, which permitted larger memory blocks, were available from DOS 2.0.[9] Early examples included Borland Sidekick in 1984, a personal information manager that popularized TSRs for productivity by offering hotkey-activated tools like calculators and notepads, eventually selling over 1 million copies in its first three years, followed by SuperKey in 1985 for keystroke macros and automation.[10][11][12] TSR usage grew rapidly through the 1980s and 1990s, commonly loaded via AUTOEXEC.BAT at boot for essential extensions such as mouse drivers (e.g., MOUSE.COM), print spoolers (e.g., PRINT.COM), and desk accessories, enabling pseudo-multitasking on resource-constrained systems. The technology reached its peak during the MS-DOS 5.0 (1991) and 6.0 (1993) era, when memory optimization tools like EMM386.EXE allowed TSRs and drivers to load into upper memory blocks (UMBs) within the unused 384 KB above conventional memory, maximizing available RAM for applications. This evolution was inextricably tied to the IBM PC/XT/AT architectures' 640 KB conventional memory constraint, a hardware design choice reserving the upper 384 KB of the 1 MB address space for system ROMs and adapters, which made efficient TSR memory management critical to avoid fragmenting the limited user-accessible space.[13][14]Technical Implementation
Loading and Memory Management
A terminate-and-stay-resident (TSR) program begins loading by executing as a standard DOS program, typically in the form of a .COM or .EXE file. During initialization, it allocates the necessary memory block using DOS interrupt 21h function 48h to request paragraphs (16 bytes each) from the available conventional memory pool. The program then installs its interrupt handlers to hook into system events, after which it invokes the termination call to remain resident: either interrupt 27h, which sets the memory to retain by providing in DX the offset of the last byte plus one from the program segment prefix (PSP), limiting allocation to up to 64 KB, or interrupt 21h with AH=31h, which specifies in DX the number of paragraphs to keep (minimum 6 paragraphs or 96 bytes) along with an exit code in AL for more flexible control.[3][15] TSR programs occupy the high end of conventional RAM, starting from the upper boundary of the 640 KB limit, to minimize interference with transient programs loaded at lower addresses. Each TSR owns its allocated block via the PSP, a 256-byte structure at the segment base that tracks process details, interrupt vectors, and the memory arena entry for deallocation prevention. Multiple TSRs risk memory fragmentation by carving out non-contiguous blocks from the conventional memory chain, potentially stranding small unusable segments between allocations.[3][16] Configuration for loading TSRs occurs primarily through system files: device drivers via the DEVICE= or DEVICEHIGH= directive in CONFIG.SYS during boot, or utility programs via direct execution in AUTOEXEC.BAT, often with command-line parameters to specify the resident size in kilobytes (e.g., "tsr.exe 8" for 8 KB). Manual invocation from the command prompt follows the same process, allowing dynamic loading but requiring size specification to override defaults. This setup integrates interrupt handling during the loading phase by updating the interrupt vector table through the PSP before the termination call.[3] Interrupt 27h was available in DOS versions 1.x through 6.x, providing a simple but limited TSR mechanism implemented initially in COMMAND.COM. From DOS 2.0 onward, interrupt 21h function 31h became the preferred method, supporting larger theoretical allocations up to 1 MB (though constrained by the 640 KB conventional memory limit in real mode) and proper return codes to the parent process or DOS.[15][3]Interrupt Handling
Terminate-and-stay-resident (TSR) programs interact with the DOS operating system primarily through interrupt handling, enabling them to remain responsive after termination by hooking into specific software and hardware interrupts. The core mechanism involves modifying entries in the Interrupt Vector Table (IVT), a data structure located at the base of memory (starting at address 0x0000) that stores 256 four-byte pointers (segment:offset) to interrupt service routines (ISRs). When a TSR loads, it uses DOS interrupt 21h function 35h to retrieve the current IVT entry for the target interrupt, saves the original handler's address, and then installs its own ISR address using function 25h, effectively redirecting the interrupt to its code. Common interrupts hooked by TSRs include INT 21h for DOS services (such as file I/O or program execution), INT 08h for the system timer (generating periodic ticks, approximately 18.2 per second), and INT 09h for keyboard input, allowing TSRs to monitor or intercept system events without constant polling.[17][18][19] TSRs typically employ chaining to integrate with existing handlers rather than fully replacing them, preserving system functionality while adding their own processing. In chaining, the TSR's ISR first saves the processor state (using instructions like PUSHF and PUSHA to preserve flags and registers), executes its custom logic, and then invokes the previous handler via a far call to the saved IVT address before restoring the state and returning with IRET. For instance, a timer-based TSR might chain on INT 08h to perform background tasks like updating a clock after each tick, ensuring the original BIOS timer routine continues to maintain system timing. Full hooking, where the TSR overwrites the IVT without calling the prior handler, is less common due to the risk of breaking dependent software or BIOS functions, potentially causing system instability or loss of core services. Reactivation occurs seamlessly when the hooked interrupt triggers; the TSR's code executes in response, enabling features such as pop-up menus activated by hotkeys (e.g., via INT 09h detection of key combinations) or input buffering that captures keystrokes without user intervention, all while minimizing interference with the foreground application.[17][18][19] When multiple TSRs hook the same interrupt, they form a chain ordered by load sequence, with each subsequent TSR saving and calling the prior one's handler, creating a linked list of ISRs. The first-loaded TSR (at the chain's end) has the lowest priority for processing, as later ones intercept the interrupt first; this load-order dependency can lead to unpredictable behavior if not managed carefully. To mitigate conflicts, well-designed TSRs check for existing installations (e.g., via a unique identifier in memory) before hooking and support uninstallation by restoring the original IVT entry. However, deep chains from numerous TSRs pose risks, particularly stack overflow, as each ISR consumes stack space for saving state and nested calls; if the chain exceeds available stack depth (typically 128-256 bytes in DOS applications), it can corrupt the call stack, leading to crashes or data loss. TSRs address this by switching to private stacks during execution, ensuring the application's stack remains intact.[17][18][19]Applications
Utility Software
Terminate-and-stay-resident (TSR) programs served as versatile utility software in MS-DOS environments, providing on-demand access to productivity tools without disrupting primary applications. These utilities typically activated through hotkey combinations, allowing users to pause their main program and invoke secondary functions such as calculations or note-taking, before returning seamlessly to the original task.[20][21] Common TSR utilities included pop-up calculators, notepads, and calendars, which enabled quick reference and data entry during computing sessions. For instance, Borland Sidekick offered an integrated suite featuring a calculator for basic arithmetic, a notepad for jotting temporary notes, a calendar for scheduling, and a phone/address book for contact management, all accessible via predefined hotkeys. Print spoolers, such as the built-in DOS PRINT.COM, functioned as TSRs to buffer print output in memory, freeing the user to continue working while documents queued for the printer.[20][22][23] In peripheral support, TSRs extended hardware functionality for end-user convenience. Mouse drivers, exemplified by the Microsoft Mouse TSR (MOUSE.COM), remained resident to interpret serial mouse inputs and provide cursor control across DOS applications lacking native support. RAM disks implemented as TSRs, such as certain shareware variants, allocated conventional memory as virtual high-speed storage for temporary files, accelerating access times compared to floppy or hard disks. Screen savers operated as TSRs to prevent phosphor burn-in on CRT monitors by activating idle-time animations or blanking after inactivity periods.[24][25][26] Productivity-focused TSRs acted as desk accessories, enhancing workflow through specialized hotkey-activated features. On-screen clocks displayed time overlays, while tools like pop-up thesauruses provided synonym lookups during writing tasks. Clipboard enhancements, such as MouseClip, allowed users to select and copy text from DOS screens for pasting into word processors, bridging limitations in text transfer between applications. These integrations proved particularly useful with word processors like WordPerfect, where TSRs supplemented native features for smoother editing.[27][21] Users commonly loaded TSR utilities by stacking their executables in the AUTOEXEC.BAT file during system boot, ensuring session-long availability without manual invocation. Developers optimized TSR sizes—often under 50 KB each—to conserve the 640 KB conventional memory limit, prioritizing essential code to minimize impact on available RAM for primary programs.[28][29]Device Drivers and System Extensions
Terminate-and-stay-resident (TSR) programs played a significant role in extending MS-DOS hardware support by functioning as non-device drivers for peripherals such as CD-ROM drives, network cards, and sound cards, often loaded via the AUTOEXEC.BAT file, after the corresponding device drivers in CONFIG.SYS, to enable access once the DOS shell is initialized.[30] For instance, MSCDEX.EXE served as a TSR that provided CD-ROM access by installing a hardware-dependent device driver and redirecting file system calls through interrupt multiplexing on INT 2Fh, allowing applications to treat CD-ROMs as standard drives without native OS support.[31] Similarly, early Sound Blaster configurations relied on TSRs like the Sound Blaster Pro Volume Control to handle audio output and MIDI interfacing, hooking into relevant interrupts for real-time sound processing in DOS environments.[32] Beyond hardware abstraction, TSRs facilitated core system enhancements, such as memory management and security monitoring, by remaining resident to intercept system events. HIMEM.SYS, while implemented as a device driver rather than a pure TSR, operated in a TSR-like manner to manage extended memory (XMS) above 1 MB, providing allocation services through INT 2Fh multiplexing for DOS programs and enabling upper memory block (UMB) utilization.[33] Early antivirus solutions, like those from vendors responding to boot sector threats in the late 1980s, used TSRs to perform resident scans on file access, intercepting INT 21h calls to detect and quarantine viruses in real time without disrupting normal operations.[34] TSRs also supported rudimentary system-level multitasking, with programs like DESQview leveraging TSR mechanisms to enable task switching among DOS applications by capturing keyboard interrupts and managing memory contexts, effectively turning non-cooperative programs into background tasks.[35] This interrupt multiplexing allowed multiple TSRs to share hardware access efficiently, such as coordinating CD-ROM or sound operations across applications. In contrast to true MS-DOS device drivers, which are categorized as block (for storage like disks) or character (for serial devices) and loaded statically in CONFIG.SYS for permanent integration, TSRs follow a terminate-style model, dynamically loaded post-DOS initialization via AUTOEXEC.BAT, offering flexibility but reduced permanence as they could potentially conflict with subsequent loads.[33] This distinction made TSRs ideal for extensible hardware and system features where full driver permanence was unnecessary, though it required careful interrupt chaining to avoid system instability.Limitations
Operational Faults
Terminate-and-stay-resident (TSR) programs in MS-DOS often encountered memory conflicts due to their persistent allocation of conventional memory, leading to fragmentation as partial blocks were reserved without full release upon termination. This fragmentation arose from DOS's first-fit allocation strategy, where TSRs claimed contiguous segments in the transient program area (TPA), leaving smaller, non-contiguous remnants that subsequent allocations could not utilize effectively, even if total free memory appeared sufficient.[36] As a result, systems frequently reported "out of memory" errors during program launches, regardless of installed RAM, because no single block met the minimum requirements for new executables.[36] In DOS 5.0 and later, upper memory blocks (UMBs) managed by EMM386.EXE and HIMEM.SYS provided relief by allowing TSRs to load into the 384 KB high memory area, but overcrowding occurred when multiple TSRs and device drivers exceeded available UMB space, forcing fallback to conventional memory and exacerbating fragmentation.[23] Compatibility faults were prevalent in TSR operations, particularly from interrupt chain overflows that disrupted the linked list of handlers for key interrupts like INT 21h. Each TSR typically hooked interrupts by saving the previous vector and inserting its own handler, forming a chain; however, excessive chaining—common with multiple TSRs—could overflow limited stack space during recursive calls, causing stack crashes or system lockups.[36] Conflicts intensified with DOS extenders enabling protected-mode execution, such as DOS/4GW used in games like Doom, where TSRs operating in real mode interfered with mode switches or memory mappings, resulting in fatal crashes unless the TSR was properly unloaded prior to launch.[37] Performance degradation stemmed from TSRs introducing latency in critical system calls, notably through extended INT 21h chains that processed each invocation sequentially across all hooked handlers. This added overhead to frequent operations like file I/O or console input, slowing overall system responsiveness, especially in environments with numerous TSRs intercepting the same interrupt.[3] In low-RAM configurations, such as 256 KB machines typical of early PCs, TSRs further hogged scarce resources by retaining buffers and stacks, reducing available conventional memory for foreground applications and amplifying slowdowns during multitasking attempts.[23] Debugging TSR faults posed significant challenges in DOS, lacking standardized introspection tools to trace resident code execution or memory states post-installation. Faults often manifested as general protection faults (GPFs) in emulated environments or unexplained hangs in native DOS, with no built-in mechanisms to attribute issues to specific TSRs amid intertwined interrupt chains and allocated blocks.[38] Developers relied on rudimentary utilities like INTRSPY for interrupt tracing or manual vector inspection via DEBUG.COM, but these required preemptive loading and offered limited visibility into runtime conflicts.[36] Unloading via custom hooks provided partial mitigation for some faults, though unreliable chaining often left residual issues.[36]Unloading Mechanisms
Terminate-and-stay-resident (TSR) programs typically invoke DOS interrupt 21h with function 31h to terminate execution while remaining resident in memory, but unloading requires custom code to restore the interrupt vector table (IVT) to its original state and deallocate the program's segment prefix (PSP) using DOS function 49h.[19] This process involves verifying that the TSR is safe to remove by comparing current interrupt vectors—such as those for INT 9h (keyboard) or INT 2Fh (multiplex)—against the TSR's own handlers; if they match, the vectors are restored from saved originals, and memory is freed, often via a check on the multiplex interrupt INT 2Fh with function 01h returning success (AL=0).[19] Without such code, standard termination leaves the TSR's hooks intact, preventing proper resource release. Manual unloading tools exist for specific TSRs, often scanning memory for program signatures to identify and remove them. For instance, UNLOAD.COM from Central Point Software's PC-Tools utility suite allows users to unload supported TSRs by detecting their memory blocks and executing deallocation routines.[39] Similarly, the MS-DOS SHARE.EXE utility, which installs as a TSR for file-sharing support, can be unloaded with the /U switch, freeing its allocated memory and restoring affected system structures.[40] These tools typically require the TSR to support unloading or use heuristics like memory pattern matching, but they are limited to compatible programs. For chained TSR installations, unloading must occur in reverse load order to avoid breaking interrupt chains established by earlier programs. Each TSR, if designed with an exit routine, can be invoked to release its hooks—such as calling a provided deinstallation function—and then the next in the chain is addressed similarly.[19] Hotkey-based TSRs may register via INT 2Dh (the re-vector interrupt) during installation, allowing coordinated release of hotkeys during unloading to prevent conflicts. This approach relies on protocols like IBM's Interrupt-Sharing for orderly deinstallation, checking if other TSRs depend on the same interrupts before proceeding.[41] Incomplete unloading poses significant risks, often leaving "ghost" interrupt handlers that cause system instability, such as crashes or unresponsive input, due to un-restored vectors pointing to deallocated memory.[19] The absence of a universal API in DOS exacerbates this, forcing reliance on vendor-specific solutions or custom implementations, which may fail if the TSR lacks built-in support for safe removal.[41]Decline and Legacy
Factors Leading to Decline
The decline of terminate-and-stay-resident (TSR) programs began in the early 1990s, driven primarily by hardware advancements that alleviated the severe memory constraints of the MS-DOS era. MS-DOS was fundamentally limited to 640 KB of conventional memory due to its real-mode addressing, which forced TSRs to compete aggressively for scarce resources through techniques like memory compression and relocation.[42] The release of Windows 3.0 in 1990 introduced enhanced mode operation on 386 processors, enabling access to extended memory beyond 1 MB via protected mode, thus reducing the necessity for TSRs to employ convoluted memory management tricks to coexist in low-memory environments.[43] This shift allowed applications to utilize larger address spaces without relying on TSR chaining, diminishing the appeal of TSRs for utility functions that previously required tight integration into conventional memory. Parallel to hardware progress, the evolution of operating systems toward true multitasking further eroded TSR viability. Windows NT 3.1, launched in 1993, implemented preemptive multitasking in a fully 32-bit protected-mode environment, allowing multiple applications to run concurrently without the interrupt hooking and cooperative yielding that TSRs demanded from single-tasking DOS.[44] Similarly, Windows 95 in 1995 combined a 32-bit kernel with preemptive multitasking for Windows applications, while virtualizing the DOS layer to handle legacy TSRs in isolated sessions; this design replaced many TSR roles with integrated system services, such as background printing and clipboard management, that no longer needed resident memory occupation.[45] DOS extenders, popularized in the late 1980s and early 1990s (e.g., for games like Doom), further bypassed TSR limitations by switching to protected mode to access extended and expanded memory (EMS/XMS), enabling larger programs without fragmenting conventional memory for resident utilities.[46] Standardization of driver models in graphical user interface (GUI) operating systems sealed the obsolescence of TSRs by mid-decade. In Windows 9x, virtual device drivers (VxDs) provided a protected-mode API for system extensions, offering reliable interrupt handling and resource sharing without the instability of TSR chaining in real mode; this made traditional TSRs insecure and incompatible in multitasking contexts, as they could disrupt kernel-level operations.[45] TSRs peaked in usage during the late 1980s amid DOS dominance for productivity and gaming, but their relevance sharply declined by the mid-1990s as consumer adoption of GUI OSes like Windows 95 surged, confining TSRs to niche DOS-based gaming and emulation scenarios.[47]Modern Equivalents
In contemporary Windows systems, background services managed by the Service Control Manager (SCM) offer persistent execution akin to TSR programs, allowing processes to run continuously without a user interface or logged-in session, starting automatically at boot or on demand. These services handle tasks like system monitoring and extensions, replacing the memory-resident nature of TSRs with more structured, isolated operation. For user-mode event monitoring, applications can employ Win32 hooks or register system-wide hotkeys via theRegisterHotKey API to intercept inputs and respond in real time, providing TSR-like interception without direct kernel access. In Windows 9x, Virtual Device Drivers (VxDs) functioned as 32-bit protected-mode extensions, managing hardware, software, and even emulating TSR behaviors for compatibility, such as handling DOS device drivers or resident programs within the virtual machine manager. Kernel-mode drivers in NT-based Windows further extend this for low-level system integration, ensuring stability through ring-0 execution with enforced boundaries.
In Unix-like systems such as Linux, daemon processes serve as the primary equivalent to TSRs, operating as detached background programs that persist indefinitely to perform system tasks without terminal attachment or user intervention. Traditional daemons follow initialization rituals like forking and creating PID files for supervision, while modern implementations leverage service managers like systemd for streamlined persistence and resource control. Examples include cron daemons for scheduled job execution and inotify-based watchers for file system event monitoring, enabling reactive behaviors similar to TSR interrupt handling but within a preemptive multitasking environment. Loadable Kernel Modules (LKMs) provide another analog for kernel-level extensions, dynamically inserting code to enhance functionality without rebooting, much like TSR memory hooks but with modular loading and unloading.
On macOS and iOS, the launchd system daemon orchestrates persistent services through launch agents (user-specific) and launch daemons (system-wide), loading processes at boot or login to run scripts and applications in the background without full termination. This framework supports scheduled or event-driven execution, replacing TSR-style residency with plist-configured jobs managed via launchctl for loading and control. Features like Background App Refresh extend this to mobile contexts, allowing apps to periodically update content while minimizing resource use, though constrained by battery and privacy policies.
While modern operating systems lack direct TSR equivalents due to preemptive multitasking, memory protection, and process isolation—which prevent the cooperative, shared-memory model of DOS—TSR functionality persists in emulation environments. DOSBox supports loading and executing TSR programs for compatibility with legacy DOS software, enabling interrupt-based hooks within the emulator's virtualized DOS session. Similarly, FreeDOS, as an open-source DOS-compatible OS, fully accommodates TSRs for running classic applications and utilities on contemporary hardware.
Security paradigms in these systems have evolved to address TSR vulnerabilities like memory conflicts and privilege escalation; sandboxing confines processes to limited resources, while privilege separation divides tasks into minimal-authority components, reducing the systemic risks of resident code that could destabilize or compromise the entire environment.