The XMS specification is still accurate: functions 0x10 and 0x11 provide access to UMBs. However, the specification doesn’t decide where those functions are implemented.
On its own, HIMEM.SYS
does indeed only provide access to memory above 1MiB, i.e. the HMA (so it also controls the A20 line) and extended memory (which it makes available as XMS). If you only load HIMEM.SYS
, setting DOS=UMB
(in MS-DOS 5 and later) won’t do anything useful; you’ll only be able to use DOS=HIGH
.
In typical setups on 386+ systems, EMM386.EXE
implements the UMB functions 0x10 and 0x11, and fulfills them by mapping extended memory to addresses in the UMA. To do this, it needs to run the system in V86 mode, which causes compatibility problems with some programs which expect to run in real mode (and take over ring 0).
Other tools also implement those functions. For example, UMBPCI can provide UMBs by activating the corresponding shadow RAM, using chipset-specific features. This was common on 286s, where chipset-specific drivers could activate shadow RAM to provide memory between 640K and 1M. The Last Byte Memory Manager was a shareware memory manager which included lots of chipset-specific drivers to do this. Another approach, on 8086s and 286s, was to use EMS 4.0 features to map expanded memory into the UMA; QRAM can do this (as well as use shadow RAM on C&T-based 286 systems). All these memory managers are capable of providing UMBs without leaving real mode... 386-specific memory managers such as QEMM386 use the same techniques as EMM386
(and the grandfather of them all, Compaq’s CEMM
).
In all cases, memory managers which work with HIMEM.SYS
need to piggy-back on top of it, which is why you always load HIMEM.SYS
first. On its own, HIMEM.SYS
responds to interrupt 0x2F functions 0x4300 and 0x4310. Subsequent memory managers call those functions, find HIMEM.SYS
, then hook interrupt 0x2F themselves, and either take over function 0x4310 in particular to point to their own entry point, or overwrite the code at the start of the existing entry point with a far jump to their own entry point (see the XMS specification for details). Having stored HIMEM.SYS
’s entry point somewhere, they can then pass on appropriate calls to HIMEM.SYS
. QEMM386 and other “high-end” memory managers replace HIMEM.SYS
entirely so it shouldn’t be loaded at all.
As far as programs running on top of DOS are concerned, UMBs are managed by DOS. If a program wants to allocate memory in the UMA, it should try to do so using the usual DOS memory allocation functions, after setting the allocation strategy using interrupt 0x21 function 0x58.
Microsoft’s knowledge base entry 95555, “Overview of Memory-Management Functionality in MS-DOS”, provides a good introduction to the whole topic.
(This answer describes MS-DOS; I can’t remember the details for DR DOS and Novell DOS, but at least on OpenDOS, HIMEM.SYS
also manages the UMA in some cases.)