Windows 95 Memory Management: 16-Bit vs 32-Bit DLLs

Windows 95 handled memory allocation for Dynamic Link Libraries (DLLs) by partitioning its 4-gigabyte (GB) virtual address space into dedicated zones designed to bridge the gap between legacy software and modern protected architectures. To ensure backward compatibility with Windows 3.1 while enabling preemption and stability for new software, Windows 95 isolated 32-bit DLLs into a flat, private virtual memory space while forcing 16-bit DLLs to share a single, segmented subsystem located in a global memory arena.

The Windows 95 Address Space Layout

Windows 95 implemented a 32-bit flat memory model utilizing the paging hardware of the Intel 80386 processor and its successors. The 4 GB linear address space was partitioned into four primary regions:

  • 0 MB to 4 MB: Reserved for MS-DOS compatibility and real-mode device drivers.
  • 4 MB to 2 GB: The private address space assigned to each 32-bit process.
  • 2 GB to 3 GB: A shared arena housing 16-bit applications, the Win16 subsystem, shared system components, and 32-bit system DLLs.
  • 3 GB to 4 GB: The Ring 0 operating system kernel, Virtual Device Drivers (VxDs), and core file systems.

Allocation for 16-Bit DLLs

16-bit DLLs adhered to the legacy segmented architecture inherited from Windows 3.x. Because these libraries relied on 16-bit selectors and offsets rather than flat 32-bit linear pointers, Windows 95 executed them within a single shared compatibility layer known as the Win16 subsystem.

When a 16-bit DLL loaded, the operating system allocated its code and data segments within the shared 2 GB to 3 GB arena. Memory for these libraries was governed by the GlobalAlloc and LocalAlloc routines:

  • Shared Address Visibility: All 16-bit DLLs were mapped globally across all processes. A 16-bit DLL had no private instance data per application; its data segment was shared globally among every process calling it.
  • Segment Descriptors: The memory manager created Local Descriptor Table (LDT) entries pointing to segments allocated in the shared arena.
  • Lack of Memory Protection: Because 16-bit DLLs resided in a globally writeable address space, any 16-bit application could overwrite the code or data segments of a 16-bit DLL, posing a risk of system-wide instability.
  • The Win16 Mutex: To prevent concurrency issues arising from non-reentrant legacy code, all execution inside 16-bit DLLs was serialized using a single system-wide lock known as the Win16Mutex.

Allocation for 32-Bit DLLs

32-bit DLLs (Win32 DLLs) functioned under modern virtual memory principles, using a flat 32-bit pointer model where memory was managed in 4-kilobyte (KB) pages. The allocation process depended on whether the DLL was a standard application library or a core system library.

  • Private Address Space (4 MB to 2 GB): Standard user-mode 32-bit DLLs loaded into the private virtual address space of the process that requested them. The operating system mapped the Portable Executable (PE) sections of the DLL into available pages within this range.
  • Instance Data Isolation: Unlike 16-bit DLLs, the data section (.data) of a 32-bit DLL was entirely private to the process that loaded it. Two processes loading the same 32-bit DLL received distinct, isolated physical pages for their respective variable states.
  • Copy-on-Write Sharing: Windows 95 optimized physical RAM usage for 32-bit DLLs via the CPU’s paging mechanism. The read-only code sections (.text) of a 32-bit DLL were mapped to the same physical memory frames across multiple processes. If a process attempted to modify a page marked for sharing, the virtual memory manager intercepted the fault and allocated a unique physical page for that process using a Copy-on-Write (COW) strategy.
  • System DLL Optimization (2 GB to 3 GB): Core Win32 system libraries (such as KERNEL32.DLL, USER32.DLL, and GDI32.DLL) were loaded into the shared 2 GB to 3 GB region. This ensured that essential system code was mapped at the exact same base address across all running Win32 processes, reducing base address relocations and conserving physical system RAM.

Summary of Differences

Windows 95 managed memory for 16-bit and 32-bit DLLs through two conflicting strategies: compatibility and isolation. 16-bit DLLs were allocated in a shared, segmented, and globally accessible 2 GB to 3 GB arena, sharing a single data instance and exposing the entire system to memory corruption. In contrast, 32-bit DLLs utilized flat-model, page-based allocations isolated within the calling process's private 4 MB to 2 GB space, using Copy-on-Write pages to ensure stability, process isolation, and efficient RAM management.