XMS Function 0Bh on FreeDOS: Move Blocks Without Owning A20
Use XMS Function 0Bh to copy between conventional and extended memory with handles, packed far pointers, even-length limits, and safe overlap rules.
An XMS handle is not a real-mode pointer. It names an extended-memory block managed by an XMS provider, and a DOS program cannot safely dereference it with an ordinary MOV instruction. XMS Function 0Bh is the manager-mediated way to copy bytes between conventional memory and an extended-memory block, or between two XMS blocks, without taking over the A20 gate or depending on a particular machine’s extended-memory layout.
That contract is useful in a FreeDOS program that stages assets, disk buffers, or large records outside conventional memory. It is also easy to misuse: the request is a packed 16-byte structure, a conventional-memory pointer is encoded inside a 32-bit field, lengths must be even, and overlapping moves have a directional guarantee. Treat the call as a checked transfer request, not as a flat-memory memcpy with a different name.
Find the XMS entry point first
FreeDOS commonly uses HIMEMX as its XMS manager. Its documentation says to load HIMEMX.EXE from FDCONFIG.SYS or CONFIG.SYS before drivers that need XMS; the manager is not the same thing as the DOS kernel’s conventional-memory allocator. A program should still detect the service at runtime instead of assuming that every FreeDOS boot profile loaded it.
The XMS discovery sequence is through INT 2Fh. Set AX=4300h and call the interrupt; AL=80h means an XMS driver is installed. Then call with AX=4310h to receive the manager’s far entry point in ES:BX. Save that pointer while the provider remains installed, and call it with AH set to the XMS function number. Function 0Bh is called through this far pointer, not by issuing INT 21h or INT 2Fh with AH=0Bh.
Each XMS function reports success in AX: 0001h means success and 0000h means failure, with an error code in BL. Preserve the error before making another XMS call, since later calls reuse registers. The specification also recommends at least 256 bytes of stack space for an XMS API call. On FreeDOS, keep HIMEMX ahead of consumers in the boot configuration, but do not infer from that setting alone that a requested function is supported by every possible XMS provider.
Read the move structure as bytes
Function 0Bh expects DS:SI to point at this structure. Its fields are consecutive and word-sized except for the three 32-bit values:
| Byte offset | Field | Meaning |
|---|---|---|
00h |
Length (DWORD) |
Number of bytes to copy |
04h |
SourceHandle (WORD) |
XMS block handle, or zero for conventional memory |
06h |
SourceOffset (DWORD) |
Byte offset in that block, or packed segment:offset when the handle is zero |
0Ah |
DestHandle (WORD) |
XMS block handle, or zero for conventional memory |
0Ch |
DestOffset (DWORD) |
Byte offset in that block, or packed segment:offset when the handle is zero |
The structure is 16 bytes, not 14: each memory location has its own 32-bit offset even when its handle is zero. For a conventional-memory endpoint, the 16-bit segment and offset are stored in Intel DWORD form: the offset word comes first in memory, followed by the segment word. Do not store a C compiler’s near pointer there and assume it is a valid far pointer. For an XMS endpoint, the value is a byte offset from the start of the block named by that handle.
For example, a copy from a 1 KiB conventional buffer into the beginning of an allocated XMS block can be described as:
Length = 1024
SourceHandle = 0000h
SourceOffset = (segment of buffer : offset of buffer), Intel DWORD order
DestHandle = handle returned by XMS Allocate Extended Memory Block
DestOffset = 00000000h
DS:SI -> this request
AH = 0Bh
CALL XMS entry point
AX = 0001h means copied; AX = 0000h means inspect BL
The source handle value zero is a selector for a directly addressable conventional-memory endpoint; it is not a null buffer. Conversely, a nonzero handle does not make the extended-memory bytes directly addressable. The manager performs the transfer through its own mapping logic.
Keep the request inside its allocations
The specification requires Length to be even. Word alignment is recommended for speed, and a DWORD-aligned transfer may be faster on 80386-class processors, but alignment does not change the even-length requirement. If an application has an odd number of useful bytes, stage the tail byte in a separate conventional buffer or include a valid padding byte in both allocated regions. Never round a request up and then read or overwrite beyond the caller’s data.
Validate each range before the call using wider arithmetic than the request fields: offset + length must not wrap and must not exceed the XMS block size. A FFFFh-sized allocation limit or 32-bit offset field is not a promise that the handle owns that many bytes. Check the size returned by allocation, preserve the handle, and maintain the requested length alongside it. The move service reports invalid source handle/offset, invalid destination handle/offset, invalid length, an A20 error, and other defined failures through BL; it cannot replace application-level bounds checking.
Function 0Bh does not require an XMS block to be locked. Locking is for a caller that needs a temporary 32-bit physical address, for example to pass memory to a device or another interface with a physical-address contract. A locked address is valid only while the block remains locked. For a normal XMS-to-XMS or XMS-to-conventional copy, use the handles and offsets directly; adding a lock/unlock cycle does not make the move safer.
The move function preserves the A20 state. Do not manually enable A20 before calling it, and do not disable A20 as cleanup afterward. The manager owns that boundary. This is especially important when HIMEMX, a DPMI host, or another resident component is sharing memory-management responsibilities. Directly manipulating the keyboard controller or port 92h can break a manager’s accounting even if a quick test appears to work.
Treat overlap as a separate case
The XMS specification permits a move request to describe overlapping regions, but only a forward move, where the source base is lower than the destination base, is guaranteed to work properly. Do not assume that Function 0Bh behaves like C memmove in both directions. If a higher-to-lower overlapping copy is needed, copy through a second non-overlapping staging block or implement a carefully bounded transfer plan that never relies on unspecified overlap behavior. Error A8h is the defined invalid-overlap result for requests the manager rejects.
Large moves may take time, but the XMS contract requires a reasonable number of interrupt windows during long transfers. That is not a hard real-time deadline and does not make an arbitrarily large one-shot operation harmless inside latency-sensitive code. Keep transfers out of hardware interrupt handlers, use bounded chunks where responsiveness matters, and verify each call’s result before treating the destination as initialized.
Test the bytes and release the handle
Allocate an XMS block with Function 09h, which takes its size in kilobytes and returns a handle in DX. Build a deterministic test pattern in a conventional buffer, move it into the block, move it back to a second conventional buffer, and compare every byte. Test the minimum legal even length, a larger aligned transfer, nonzero offsets, and the last valid range in a deliberately oversized test block. Keep canaries around the conventional buffers so an incorrect packed pointer or rounded-up length becomes visible.
Run separate negative tests with a deliberately invalid handle and an odd length, using disposable buffers and a test harness that expects failure; do not use production data to test invalid requests. Record AX and BL, the provider/version, the requested ranges, and the comparison result. Repeat under the emulator and physical hardware that matter to the application, because an emulator success does not establish identical timing or provider behavior on every machine.
When finished, free the block with Function 0Ah and check its return value. After a successful free, the handle and stored data are invalid. Keep the handle in one owner, avoid freeing it from one code path while another still uses it, and make every allocation failure and transfer failure unwind without using partially copied contents. If a program needs to retain a physical address instead, use the lock lifecycle deliberately and unlock before freeing.
XMS Function 0Bh is a small but complete ownership protocol: discover the provider, construct the exact request, bound the ranges, let the manager handle A20, check both success and error codes, verify the copied bytes, and release the handle. That discipline makes extended memory useful to real-mode software without pretending that it is ordinary DOS memory.
Related:
- XMS HMA Ownership and A20 Control: Balance Every Enable and Release
- BIOS INT 15h AH=87h: The Protected-Mode Block-Move Service
Sources: