XMS HMA Ownership and A20 Control: Balance Every Enable and Release
Request the XMS High Memory Area safely by checking the manager, observing ownership rules, balancing A20 calls, and releasing the HMA.
The XMS High Memory Area (HMA) is the first 65,520 bytes above the 1 MiB boundary that can be addressed by real-mode software when the A20 gate is enabled. It is not ordinary conventional memory and is not a general-purpose block that several programs can use concurrently. The XMS manager arbitrates HMA ownership and provides the A20-control API; applications must request the HMA, enable the appropriate state, and release it correctly.
The most dangerous mistake is to manipulate the A20 gate directly through an assumed keyboard-controller or port-92h sequence while an XMS manager is active. The XMS specification distinguishes global control for the program that owns the HMA from local control for programs that need direct access to extended memory. Respect that distinction and use the installed XMS manager as the ownership authority.
Locate the XMS control entry point
Applications discover an XMS manager through the standard INT 2Fh multiplex interface. Function AX=4300h checks whether an XMS driver is installed; the conventional success response is AL=80h. If present, AX=4310h returns the far entry point in ES:BX. The application then calls that control entry with an XMS function number in AH and arguments in the registers described by the XMS specification.
Treat the returned pointer as a far procedure address, not an INT 2Fh service for every later request. Save it in a correctly sized segment:offset representation, preserve registers required by your compiler or assembler ABI, and validate results after every XMS call. An XMS manager may be absent, incompatible, or implement a different specification level than expected.
An assembly probe has this basic shape:
mov ax, 4300h
int 2Fh
cmp al, 80h
jne no_xms_manager
mov ax, 4310h
int 2Fh
; ES:BX now contains the XMS control entry point
This probe only discovers the entry point. It does not allocate the HMA or prove that later functions are supported. Query the returned XMS version and HMA-existence flag before requesting ownership. If the caller runs as a TSR or device driver, the HMA request size rules differ from the ordinary application request; follow the correct calling convention for that program type.
HMA ownership is exclusive
XMS function AH=01h requests the HMA. For an application, the XMS 2.0 specification uses DX=FFFFh; a TSR or device driver supplies the amount of HMA space it needs. The request succeeds only if the HMA exists, is free, and the manager accepts the request. A second program should not assume it can share an HMA allocation or carve up the same address range independently.
After ownership succeeds, the program can place its own code or data in the HMA according to its memory model and the space it requested. The manager’s HMA allocation is a cooperative resource contract. Do not access HMA memory before the request succeeds, and do not use it after releasing ownership. A return value of zero is failure; inspect the documented error byte rather than guessing from the HMA existence query.
HMA allocation and ordinary XMS extended-memory blocks are separate mechanisms. An XMS handle identifies an extended-memory block operated on through XMS calls; HMA bytes are addressed using real-mode segment:offset arithmetic after proper ownership and A20 state. Do not pass an HMA pointer as if it were an extended-memory handle or use a block-move function with the wrong address descriptor.
Global and local A20 calls have different ownership rules
The XMS specification provides global enable/disable functions and local enable/disable functions. The global pair should be used only by software that owns the HMA. The local pair is for software that needs direct access to extended memory but does not own the HMA. Local enables are reference-counted by the manager; a program must balance its own local enable with a local disable before returning control.
The important operational rule is not “turn A20 on when needed, then turn it off.” It is “use the ownership-correct XMS call and restore the state according to the API.” Another component may already depend on A20 being enabled. Directly disabling the gate after your code finishes can break the XMS manager, DOS kernel, TSRs, or another application. Conversely, enabling it without balancing local ownership can leave the platform in an inconsistent state.
The XMS query function reports the physical A20 state, but a single query does not establish who owns the HMA or whether a matching enable count exists. Query state for diagnostics, not as a replacement for the manager’s enable/disable bookkeeping. Never infer A20 state from a BIOS version, CPU model, or port value alone.
Use a structured request and release sequence
A careful application follows a lifecycle:
- Probe the XMS manager and obtain the control entry point.
- Query the XMS version and whether the HMA exists.
- Request HMA ownership using the correct DX argument for an application or resident component.
- Check success and the error code before touching HMA memory.
- Use the global A20 functions only as allowed for the HMA owner; balance local calls when accessing extended memory outside HMA ownership.
- Stop using HMA data, restore required state, and release the HMA before normal program termination.
The XMS API uses AH as the function number. For functions that fail, AX=0 indicates failure and BL returns an error code in the documented conventions. Preserve the error code before making another XMS call. A typical HMA-request failure can mean the manager lacks the function, the HMA does not exist, the HMA is already in use, or the requested size does not meet a manager-configured minimum. These cases should produce different diagnostics and fallback behavior.
The XMS 2.0 specification says programs using XMS API functions should ensure at least 256 bytes of stack space is available before calls. This is a real-mode calling constraint worth verifying in small assembly programs and TSRs. Compiler-generated stack usage, interrupt nesting, and far-call frames can consume more than the visible source suggests.
Failure cases and safe fallback
If the XMS manager is absent, do not call an uninitialized far pointer. If the manager reports that the HMA is in use, do not retry by writing to it anyway. If the HMA does not exist, use an alternate memory strategy such as conventional memory or a supported XMS block. If an enable call fails, do not proceed under the assumption that addresses above 1 MiB are accessible. If a local disable reports the line remains enabled, preserve that error and follow the XMS manager’s state model rather than issuing raw I/O writes.
Always design cleanup before writing the first HMA byte. Centralize release logic and track separate flags for successful HMA allocation, global enable, and local enable counts. On an ordinary error path, unwind only the calls that succeeded and in reverse order. A program that can be aborted by Ctrl-C, a critical error, or a TSR termination needs an explicit strategy so resources are not left allocated.
Never test HMA behavior on the only bootable installation. Use an emulator snapshot or disposable disk image, and test with at least two cooperating programs: one that acquires the HMA and one that attempts a second allocation. Confirm the second request fails cleanly and that the first program’s release allows a later request. Also test an absent-XMS profile and a manager with different configuration options.
Verify memory state without overclaiming
Record the XMS manager name and version, XMS version returned by the manager, HMA existence, allocation request, AX/BL results for each operation, A20 query state, and cleanup result. Test that bytes written to the HMA remain readable while the program owns it, and avoid reading them after release. Do not report “A20 fixed” merely because the application ran; prove the ownership and cleanup sequence under the target memory manager.
Different FreeDOS memory managers may expose different options and compatibility behavior. HIMEMX and JEMM-family managers are implementation choices with their own documentation; do not assume a Microsoft HIMEM.SYS switch applies. This article describes the XMS contract, not a promise that every installed manager implements every extension. Inspect the actual boot configuration and installed help before deployment.
The HMA is valuable precisely because it is shared, scarce real-mode address space. Treat it like a leased resource: ask the manager, check the grant, use only the range you own, balance A20 state through the API, release on every path, and preserve a fallback configuration. That discipline keeps one application’s optimization from becoming another application’s memory-corruption incident.
Related:
- XMS, EMS, and the Many Kinds of DOS Memory Beyond 640K
- Fixing HIMEM and EMM386 Conflicts in a FreeDOS Memory Configuration
Sources: