How We Fit 16 MB of RAM Into 640 KB: The MS-DOS Memory Maze
MS-DOS's 640 KB barrier forced three incompatible memory schemes—EMS, XMS, and HMA/UMBs. This blog walks through a real DOS 6.22 optimization, showing how driver order and switch choices reclaim conventional memory for DPMI apps like Doom.
16 Oct 2026, 08:35 UTC

The Problem: 640 KB Is Not Enough for Anyone
In 1981, the IBM PC reserved the upper 384 KB of the 1 MB 8086 address space for video BIOS, option ROMs, and memory-mapped hardware. That left 640 KB of “conventional memory” for DOS, drivers, TSRs, and your application. By the late 1980s, a typical stack—mouse driver, CD-ROM extension, network redirector, ANSI.SYS—could consume 150 KB before your program even started. Large apps (CAD, databases, early games) hit the ceiling daily.
The takeaway: MS-DOS never broke the 640 KB barrier; it worked around it with three incompatible schemes (EMS, XMS, HMA/UMB) that required careful driver ordering and hardware-specific tuning. Understanding the layers lets you diagnose “out of memory” errors on vintage hardware or in a VM today.
Conventional Memory and the 384 KB Hole
The 8086 uses 16-bit segment:offset addressing, capping physical addresses at 1 MB (FFFF:FFFF = 1,048,575 bytes). IBM’s memory map:
- 0x00000–0x9FFFF (640 KB): conventional RAM—the only region DOS can manage natively.
- 0xA0000–0xBFFFF (128 KB): video RAM (CGA/EGA/VGA frame buffers).
- 0xC0000–0xDFFFF (128 KB): option ROMs (video BIOS, network boot ROMs, SCSI BIOS).
- 0xE0000–0xFFFFF (128 KB): system BIOS and motherboard ROM.
DOS loads low (near 0x00000) and grows upward. Every DEVICE= and TSR in CONFIG.SYS/AUTOEXEC.BAT claims a chunk. Fragmentation is inevitable: a 4 KB driver loaded early can leave a 12 KB hole that nothing later fits into.
EMS vs. XMS: Two Incompatible Escape Hatches
Expanded Memory (EMS / LIM 4.0)
EMS adds a hardware mapper that switches 16 KB pages into a 64 KB “page frame” usually placed at 0xD0000–0xDFFFF (inside the upper memory hole). Applications call INT 67h to request, map, and release pages. Up to 32 MB is addressable, but only data lives there—code cannot execute from EMS pages. The mapper is a physical board (AST RAMpage, Intel Above Board) or, on 386+, emulated by EMM386.EXE using V86 mode.
Extended Memory (XMS)
The 80286 added 24-bit addressing (16 MB). XMS (HIMEM.SYS) exposes memory above 1 MB via INT 2Fh/4300h. You allocate blocks, then copy data between conventional memory and XMS with a driver call—no direct execution, no paging. XMS is simpler for bulk storage (disk caches, RAM disks) but useless for random-access data structures unless you copy constantly.
Key conflict: EMM386 with the RAM switch (UMBs only) disables the EMS page frame unless you omit NOEMS and a free 64 KB window exists. Many configs choose one or the other.
HMA and UMBs: The 386’s Gift to Conventional Memory
On 286+, the first 64 KB minus 16 bytes above 1 MB (segment FFFF:0010–FFFF:FFFF) becomes accessible by toggling the A20 line. DOS 5.0+ loads its kernel there with DOS=HIGH, reclaiming ~46 KB of conventional memory.
On 386+, V86 mode lets a memory manager (EMM386, QEMM, 386MAX) map unused upper memory regions (e.g., 0xC8000–0xCFFFF if no network ROM) as Upper Memory Blocks (UMBs). DEVICEHIGH= and LOADHIGH (LH) place drivers/TSRs into UMBs instead of conventional memory.
Fragility: UMB scanning (I=B000-B7FF) can corrupt hardware if a reserved region is misidentified. Load order matters—early drivers claim the largest UMBs, leaving holes for later ones.
Worked Example: Optimizing a DOS 6.22 Boot
Target: a 486 DX2-66 with 16 MB RAM, running DOS 6.22 in VirtualBox. Goal: maximize conventional memory for a DOS/4GW game (Doom 1.9 shareware).
Baseline (no memory managers)
C:\> MEM /C
Conventional Memory: 640 KB total, 412 KB free (largest block 412 KB)
Upper Memory: 0 KB
Extended (XMS): 15,360 KB
Expanded (EMS): 0 KB
Optimized CONFIG.SYS
DEVICE=C:\DOS\HIMEM.SYS /TESTMEM:OFF
DEVICE=C:\DOS\EMM386.EXE RAM I=B000-B7FF X=C800-CFFF
DOS=HIGH,UMB
DEVICEHIGH=C:\DOS\ANSI.SYS
DEVICEHIGH=C:\MOUSE\MOUSE.SYS
DEVICEHIGH=C:\CDROM\MSCDEX.EXE /D:MSCD001
Optimized AUTOEXEC.BAT
@ECHO OFF
SET PATH=C:\DOS;C:\UTIL
LH C:\DOS\SMARTDRV.EXE 2048 256
LH C:\MOUSE\MOUSE.COM
LH C:\NET\NET START
Result
C:\> MEM /C
Conventional Memory: 640 KB total, 618 KB free (largest block 618 KB)
Upper Memory: 86 KB total, 12 KB free
Extended (XMS): 15,360 KB
Expanded (EMS): 0 KB
Conventional free jumped from 412 KB to 618 KB—enough for Doom’s DPMI host (CWSDPMI.EXE) plus the game’s real-mode stub. The I=B000-B7FF includes monochrome video RAM as UMB (safe on VGA-only systems); X=C800-CFFF excludes the network ROM region. /TESTMEM:OFF skips HIMEM’s slow extended-memory test.
Verification Steps
- Run
MEM /C /Pbefore and after changes to compare conventional/UMB/XMS totals. - Run
EMM386.EXE /DEBUGonce to log included/excluded ranges; cross-check withMEM /U. - Confirm A20 is enabled: in DEBUG,
MOV AX,2401h; INT 15hreturns AX=0. - Launch Doom with and without
CWSDPMI.EXEin the path; the stub will fail with “DPMI host not found” if missing.
Trade-offs and Limitations
- EMS/XMS mutual exclusion: Many boards can’t provide both a page frame and UMBs simultaneously. Choose EMS for apps that demand it (Lotus 1-2-3, some CAD); choose UMBs for general driver loading.
- Fragmentation persists: A small TSR loaded late (e.g., a key macro utility) can occupy a UMB that a larger earlier driver needed, forcing the larger driver back into conventional memory.
- DPMI dependency: DOS extenders (DOS/4GW, Phar Lap, CauseWay) require a DPMI host (Windows 3.x, QEMM, CWSDPMI). Real-mode callbacks must fit 16-bit stack assumptions; exceeding them crashes silently.
- Hardware variance: A20 control methods differ (keyboard controller, Fast A20 Gate, port 92h). HIMEM.SYS auto-detects but hangs on buggy BIOSes—use
/MACHINE:XXXXfor known chipsets.
Closing: How to Check Your Own Config
Boot a DOS 6.22 VM (VirtualBox, DOSBox-X, or PCem) with 16 MB RAM. Capture MEM /C /P output, then add HIMEM.SYS, EMM386.EXE with RAM, DOS=HIGH,UMB, and DEVICEHIGH lines one at a time, re-running MEM after each. Note which driver order yields the largest conventional free block. If an app still fails, run EMM386.EXE /DEBUG to see excluded ranges and adjust I=/X= switches. The 640 KB barrier never disappeared—we just learned to stack the workarounds in the right order.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.