How Virtual Memory Works
You know that your computer doesn't really have hundreds of gigabytes of RAM. And it doesn't have hundreds of gigabytes of disk space either. Windows uses some smoke and mirrors here.First of all, a process's 4 GB address space is used sparsely. Various programs and data elements will be scattered throughout the 4 GB address space in 4 KB units, starting on 4 KB boundaries. Each 4 KB unit, called a page, can hold either code or data. When a page is being used, it occupies physical memory, but you never see its physical memory address. The Intel microprocessor chip efficiently maps a 32-bit virtual address to both a physical page and an offset within the page, using two levels of 4 KB page tables, as shown in Figure 10-2. Note that individual pages can be flagged as either read-only or read/ write. Also note that each process has its own set of page tables. The chip's CR3 register holds a pointer to the directory page, so when Windows switches from one process to another, it simply updates CR3.

Figure 10-2: Win32 virtual memory management (Intel).
So now our process is down from 4 GB to maybe 5 MB—a definite improvement. But if we're running several programs, along with Windows itself, we'll still run out of RAM. In Figure 10-2, you'll notice that the page table entry has a "present" bit that indicates whether the 4 KB page is currently in RAM. If we try to access a page that's not in RAM, an interrupt will fire and Windows will analyze the situation by checking its internal tables. If the memory reference was bogus, we'll get the dreaded "page fault" message and the program will exit. Otherwise, Windows will read the page from a disk file into RAM and update the page table by loading the physical address and setting the present bit. This is the essence of Win32 virtual memory.
The Windows virtual memory manager figures out how to read and write 4 KB pages so that it optimizes performance. If one process hasn't used a page for a while and another process needs memory, the first page will be swapped out or discarded and the RAM will be used for the new process's page. Your program won't normally be aware that this is going on. The more disk I/O that happens, however, the worse your program's performance will be, so it stands to reason that more RAM is better.I've mentioned the disk, but I haven't talked about files yet. All processes share a big system-wide swap file that's used for all read/write data and some read-only data. (Windows NT/2000/XP supports multiple swap files.) Windows determines the swap file size based on available RAM and free disk space, but there are ways to fine-tune the swap file's size and specify its physical location on disk.
The swap file isn't the only file used by the virtual memory manager, however. It wouldn't make sense to write code pages back to the swap file, so instead of using the swap file, Windows maps EXE and DLL files directly to their files on disk. Because the code pages are marked read-only, there's never a need to write them back to disk.If two processes use the same EXE file, that file is mapped into each process's address space. The code and constants never change during program execution, so the same physical memory can be mapped for each process. The two processes cannot share global data, however, and Windows 95/98 and Windows NT/2000/XP handle this situation differently. Windows 95/98 maps separate copies of the global data to each process. In Windows NT/2000/XP, both processes use the same copy of each page of global data until one process attempts to write to that page. At that point, the page is copied; as a result, each process has its own private copy stored at the same virtual address.
Note | A DLL can be mapped directly to its DLL file only if the DLL can be loaded at its designated base address. If a DLL were statically linked to load at, say, 0x10000000 but that address range were already occupied by another DLL, Windows would have to "fix up" the addresses within the DLL code. Windows NT/2000/XP copies the altered pages to the swap file when the DLL is first loaded, but Windows 95/98 can do the fix-up "on the fly" when the pages are brought into RAM. Needless to say, it's important to build your DLLs with nonoverlapping address ranges. If you're using the MFC DLLs, set the base address of your own DLLs outside the range 0x5F400000 through 0x5FFFFFFF. You'll see more on writing DLLs in Chapter 20. |
Memory-mapped files are also mapped directly. These can be flagged as read/write and made available for sharing among processes.
If you've experimented with the Registers window in Visual Studio, you might have noticed the segment registers, particularly CS, DS, and SS. (To display the segment registers in Visual Studio .NET, you might need to right-click in the Registers window and select the CPU Segments group.) These 16-bit relics haven't gone away, but you can mostly ignore them. In 32-bit mode, the Intel microprocessor still uses segment registers, which are 16 bits long, to translate addresses before sending them through the virtual memory system. A table in RAM called the descriptor table has entries that contain the virtual memory base address and block size for code, data, and stack segments. In 32-bit mode, these segments can be up to 4 GB in size and can be flagged as read-only or read/write. For every memory reference, the chip uses the selector, the contents of a segment register, to look up the descriptor table entry for the purpose of translating the address.Under Win32, each process has two segments—one for code and one for data and the stack. You can assume that both have a base value of 0 and a size of 4 GB, so they overlap. The net result is no translation at all, but Windows uses some tricks that exclude the bottom 16 KB from the data segment. If you try to access memory down there, you get a protection fault instead of a page fault, which is useful for debugging null pointers.Some future operating system might someday use segments to get around that annoying 4 GB size limitation.