XMS 3.0 Upper-Memory Blocks: Optional Functions 10h–12h
Use XMS 3.0's optional UMB calls without confusing segment ownership, DOS's UMB chain, allocation probing, and destructive shrink behavior.
An XMS driver can expose upper-memory blocks through a small API that is easy to overlook. XMS 3.0 assigns functions 10h, 11h, and 12h to request, release, and resize a UMB. These are not the same interface as DOS’s upper-memory allocation chain, and the XMS specification makes all three calls optional. A program must therefore distinguish three questions: is an XMS manager installed, what XMS revision does it report, and does this particular manager implement the optional UMB calls?
This is a contract-level guide for a FreeDOS or other DOS session whose installed manager actually supports those functions. It does not assume that a specific manager, emulator profile, or boot configuration does so. XMS defines a standard entry point and result convention, while each UMB’s size and location remain machine- and configuration-dependent.
Check the manager, then test the extension
DOS programs discover the XMS control function through INT 2Fh: AX=4300h checks for a manager, and AX=4310h returns its far entry point in ES:BX. The control function receives the requested XMS function in AH. Its common result convention is AX=0001h for success and AX=0000h for failure; on failure, BL may carry the documented error. The archived mirror of the XMS 3.0 specification explicitly says that UMB functions 10h–12h are optional, even for a fully compliant XMS 3.0 driver. A successful manager probe, or a reported 3.0 version, is not proof that these calls work.
In particular, do not confuse the XMS manager’s UMB support with DOS function INT 21h/AH=58h. The MS-DOS 5 Programmer’s Reference documents that DOS interface’s allocation-strategy controls and UMB-link subfunctions (5802h/5803h); DOS memory allocation searches the upper-memory chain only when that chain is linked. The XMS extension below instead asks the XMS manager directly for a UMB. They have different entry points, result conventions, and ownership paths. An XMS-allocated segment does not automatically become a DOS memory-chain block that LOADHIGH or DOS’s allocator can find. Treat the DOS behavior here as the MS-DOS 5 contract and test FreeDOS-compatible behavior against the selected FreeDOS release and boot configuration.
Request a block in paragraphs
Function 10h takes the requested size in DX, measured in 16-byte paragraphs. On success, BX is the segment of the allocated block and DX is the actual allocated size in paragraphs. On failure, DX reports the largest available UMB; BL=B0h means a smaller UMB is available, while BL=B1h means none is available. BL=80h means this optional function is not implemented.
; Pseudocode in MASM-style notation. XMSControl is the far entry point
; returned by INT 2Fh/AX=4310h. The caller owns the returned block only
; after AX reports success.
mov dx, paragraphs_needed
mov ah, 10h ; Request Upper Memory Block
call [XMSControl]
or ax, ax
jnz got_umb
mov saved_error, bl ; preserve before another XMS call
mov saved_largest, dx
; Handle B0h, B1h, or 80h deliberately; do not dereference BX.
Compute the requested paragraph count from the allocation’s actual byte requirement, rounding up rather than down. Keep the segment and the granted size together in your own state; do not later free or resize a block using a stale segment. UMBs are paragraph-aligned and lie below the 1 MiB boundary, so the specification says the A20 line need not be enabled to access an allocated UMB. That does not remove the need to stay within the granted range or to use a segment:offset that the program can represent safely.
The specification describes a tempting way to learn the largest free block: request FFFFh paragraphs. That is an intentionally oversized allocation attempt, not a harmless query. Because an XMS UMB lies below the 1 MiB boundary, a single valid UMB cannot satisfy a request for 65,535 paragraphs; expect failure, with B0h reporting a smaller block when one is available. Keep defensive handling for any success anyway: the caller would own the granted UMB and must either use it or release it with function 11h. On B0h, DX gives the largest smaller block then available. A later request can still fail if another component changes the pool, so treat the returned size as a snapshot, not a reservation.
Release the same segment you acquired
Function 11h takes the allocated UMB’s segment in DX. AX=1 means it was released; AX=0 with BL=80h means unsupported, and BL=B2h identifies an invalid segment. Once release succeeds, the specification says code and data stored in that UMB are invalid and should not be accessed. Unwind users of the memory first, then release it; do not keep a pointer in a callback, interrupt handler, or resident component after the owning program has returned the block.
A UMB’s segment is a manager-issued address, not a DOS handle. Keep it associated with the allocation record that also stores the size and current state. A defensive cleanup path should release only after a successful allocation, and only once. If a release fails, retain the diagnostic and stop treating the block as safely returned.
Reallocation can discard the high end
Function 12h takes the new size in paragraphs in BX and the UMB segment in DX. It returns the usual success flag in AX. On failure, BL=B0h means no available UMB can satisfy the requested size, and DX reports the largest UMB available; BL=B2h means the supplied segment is invalid. The specification is explicit about shrinking: data at the upper end of the old block is lost. That includes live structures or executable bytes, not just unused padding.
Before shrinking, identify the exact byte interval that will cease to belong to the block and move or discard its contents intentionally. Do not use a reallocation call as a speculative size query when those bytes matter. Before growing, handle failure without assuming a larger block was granted. Keep the caller’s allocation record authoritative and check every result before updating it.
Test the API independently from DOS’s UMB chain
Use a disposable DOS image and test one manager at a time. Record the manager and reported XMS version, the function number, input registers, AX, BL, BX, and DX. Cover an unsupported function, a request that fits, a request that is too large but has a smaller block available, and a system with no free UMB. If testing the FFFFh technique, verify that the successful test allocation is explicitly released. Exercise function 12h with a safe shrink buffer and compare bytes below and above the new boundary; never use a live code/data block as the first experiment.
Finally, test DOS UMB-chain behavior separately through DOS’s memory APIs and boot configuration. A UMB visible to an XMS-aware application is not, by itself, evidence that DOS has linked that region into its allocator chain. Keeping these ownership models distinct makes a failure diagnosable: unsupported XMS extension, unavailable UMB pool, stale segment, and unlinked DOS UMB are different conditions.
Related:
- FreeDOS INT 21h AH=58h: Allocation Strategy and UMB Routing
- XMS HMA Ownership and A20 Control: Balance Every Enable and Release
Sources: