How MS-DOS 6.22 Handles TSR Memory Release
This article examines how MS-DOS 6.22 manages dynamic memory deallocation when a Terminate and Stay Resident (TSR) program unloads itself from conventional or upper memory. It covers the role of Memory Control Blocks (MCBs), the execution of Interrupt 21h Function 49h, the requirement for manual interrupt vector restoration, and the operating system's limitations regarding memory fragmentation when non-sequential TSRs terminate.
The Role of Memory Control Blocks (MCBs)
MS-DOS 6.22 tracks memory using a linked list of Memory Control Blocks (MCBs), which precede every allocated block in conventional memory and Upper Memory Blocks (UMBs). Each 16-byte MCB contains:
- A block type signature: ASCII
'M'(0x4D) for intermediate blocks or'Z'(0x5A) for the final block in the chain. - The Program Segment Prefix (PSP) address of the owning process (a
value of
0000hdenotes free memory). - The size of the contiguous memory block measured in 16-byte paragraphs.
When a TSR terminates and intends to stay resident, it initially invokes INT 21h, Function 31h (Keep Process), which modifies the MCB to truncate the allocation to a designated size while preserving ownership.
Freeing Memory via INT 21h, Function 49h
When a TSR is subsequently unloaded dynamically, MS-DOS does not have a native, automated "uninstall" service specifically dedicated to TSRs. Instead, the program or an associated unloader utility must explicitly invoke standard DOS memory management functions:
- The target segment address of the TSR's allocated memory (or its
environment block) is placed into the
ESregister. - The program executes
INT 21hwithAH = 49h(Free Allocated Memory Block).
Upon receiving this request, MS-DOS 6.22 validates the MCB
immediately preceding the specified segment. If the block contains a
valid 'M' or 'Z' signature, DOS writes
0000h into the owner field of the MCB, marking the block as
free. If adjacent blocks are already marked as free, DOS coalesces them
into a single larger free block to reduce fragmentation.
Releasing the Environment Block
TSRs are typically allocated two distinct memory blocks upon
execution: the main program block (including the PSP and code/data
segments) and a separate environment block containing system variables.
A terminating TSR must issue INT 21h, AH=49h twice: once
for the environment block (the segment address located at offset
002Ch in its PSP) and once for the primary program block.
Failing to release the environment block leaves orphaned memory
allocations.
Interrupt Vector Restoration
MS-DOS 6.22 relies on an in-memory Interrupt Vector Table (IVT)
located at 0000:0000h. Because MS-DOS does not track which
TSR hooked which interrupt vector, the operating system cannot
automatically clean up pointers when memory is freed.
Before issuing the release request through Function 49h, the terminating TSR must:
- Read the current vector for any hijacked interrupts using
INT 21h, AH=35h(Get Interrupt Vector). - Verify that the vector still points to its own code segment. If another TSR has hooked the vector subsequently, safe deallocation is typically blocked unless chained unhooking techniques are supported.
- Restore the original vector using
INT 21h, AH=25h(Set Interrupt Vector).
If a TSR releases its memory while its handlers remain inside the IVT, subsequent interrupts will jump into deallocated space, causing system lockups or memory corruption once that space is reassigned.
Memory Fragmentation and the "Last-In, First-Out" Constraint
MS-DOS 6.22 allocates memory in a simple linear fashion. When a TSR is freed, the memory is returned to the MCB pool, but MS-DOS cannot dynamically relocate other resident programs to defragment space.
If multiple TSRs are loaded in sequence (A, then B, then C), unloading TSR C allows the top of the memory pool to lower cleanly. However, if TSR B unloads while A and C remain active, MS-DOS creates an isolated "hole" of free memory equal to B's size. This space can only be reused by future allocations that fit entirely within that specific paragraph count, leaving the remaining conventional memory fragmented.