Windows 98: 16-Bit Legacy vs 32-Bit Applications
Microsoft Windows 98 operated as a hybrid operating system, balancing the modern demands of 32-bit computing with extensive backward compatibility for legacy 16-bit software. To achieve this, the OS maintained two distinct execution environments: a modern Win32 subsystem featuring preemptive multitasking and isolated memory spaces, and a legacy Win16/DOS subsystem relying on cooperative multitasking and shared memory. This dual architecture ensured that while users gained access to the stability and performance of contemporary software, older MS-DOS and Windows 3.1 programs could still run without modification.
The Win32 Subsystem: Modern Isolation and Stability
Windows 98 handled native 32-bit applications using architecture adapted from the Win32 API introduced in Windows 95 and Windows NT. Key characteristics of how 32-bit applications ran include:
- Preemptive Multitasking: The operating system’s kernel scheduler controlled the CPU, allocating execution time slices to 32-bit processes. An application could not monopolize the processor, ensuring that background tasks and the user interface remained responsive even if a program became unresponsive.
- Separate Address Spaces: Each 32-bit process received its own private 4 GB virtual address space. While the upper regions (from 2 GB to 4 GB) were shared with core operating system components and shared DLLs, the private lower 2 GB prevented individual 32-bit programs from overwriting one another's memory, significantly reducing system-wide crashes.
- Multithreading: Win32 applications could spawn multiple threads of execution within a single process, enabling simultaneous operations such as rendering graphics while processing network traffic.
The Win16 Subsystem: Shared Resources and Cooperative Control
To run 16-bit legacy software written for Windows 3.x, Windows 98 provided a specialized compatibility layer that preserved the quirks and expectations of the older architecture:
- Cooperative Multitasking: Unlike 32-bit processes,
16-bit applications relied on cooperative multitasking. The operating
system could not force a 16-bit program to yield the CPU; instead, the
application had to voluntarily surrender control by checking its message
queue (e.g., calling functions like
GetMessageorPeekMessage). - Shared Memory Space: All active 16-bit Windows applications ran within a single, shared address space. If one 16-bit program wrote to an unallocated or invalid memory pointer, it could corrupt the data of other 16-bit applications or system components located in that shared space.
- Shared Message Queue: 16-bit applications shared a single input queue. If one legacy program froze while waiting for an event or processing a loop, the entire input queue stalled, freezing every other running 16-bit application and frequently locking the graphical interface.
MS-DOS Applications
For legacy MS-DOS software, Windows 98 utilized the Virtual 8086 mode provided by the x86 processor. Each DOS application ran inside an isolated Virtual DOS Machine (VDM). This provided a simulated 1 MB real-mode environment complete with virtualized hardware interrupts, memory mapping (EMS and XMS), and display drivers. If a DOS program required direct, low-level hardware access that Windows could not virtualize, the operating system allowed users to restart the computer in pure "MS-DOS Mode."
The Thunking Mechanism
Because core Windows 98 components—specifically
USER.EXE, GDI.EXE, and
KERNEL32.DLL—contained a mix of 16-bit and 32-bit code, the
operating system relied on a translation layer known as "thunking."
- Flat Thunks: Allowed 32-bit code to call into legacy 16-bit dynamically linked libraries (DLLs).
- Generic Thunks: Allowed 16-bit applications to make calls to 32-bit routines.
Thunking translated pointers, adjusted stack frames, and converted 16-bit segmented addresses into 32-bit flat addresses. While this interoperability allowed legacy and modern code to run concurrently, it also meant that a poorly behaved 16-bit application holding a system-level lock (such as the Win16Mutex) could temporarily halt the execution of 32-bit applications waiting on core user interface routines.