Memory Management in Windows - Part 1: Virtual Memory

Windows implements virtual memory via pages. A page is (typically) a 4 kilobyte [https://devblogs.microsoft.com/oldnewthing/20210510-00/?p=105200] chunk of memory, though on some architectures it is larger [https://devblogs.microsoft.com/oldnewthing/20040908-00/?p=37923]. Pages in a virtual address space can be in one of four states at any given time: free, reserved, committed, or shareable.

Free, as the name suggests, means that the page is not currently in use and is available for reservation and committal. Reserved pages have been earmarked for use, but are not yet backed by physical memory or the page file (together referred to as the “commit charge”, more on that when we discuss physical memory). Committed, often called private, pages are private to their process and allocated in physical memory or the page file. Finally, shareable pages are also allocated to physical memory or the page file, but these may be shared between processes. It is important to note that they are are not so by default, however. This is often noted by the distinction between shareable and shared.

Windows works on a demand-paged basis, which effectively means it isn’t going to give anything (even itself) any committed or shareable pages until it absolutely must do so. There are a few ways it finds out when this must be done, such as special guard pages present in a thread’s stack. Initially, a thread stack is just one page, with the next page being reserved and marked as a guard page. The number of pages allocated to the stack grows as this guard page is accessed. Likewise, when reserved memory is first accessed, a demand-zero fault is triggered, signaling that a reserved page is now in use and must be committed. When a page is committed, it is always in a zeroed state for anything in user mode. Occasionally, a non-zeroed page is given to something in kernel mode so as not to reduce the number of zeroed physical pages (more on this later).

While free, reserved, and committed pages are somewhat self-explanatory once one is familiar with the terminology, shareable pages are more complex. While it’s fairly easy to envision a programmer designating some portion of their virtual address space as a shared scratch space for multiple processes, much of shared memory is devoted to section objects. Section objects, or file mapping objects as they are called in Win32, are shareable portions of memory that either reside in physical memory, the page file, or in a file on disk. It’s important to note that a section object is not necessarily shared, as it may only be mapped into one process’ address space. Section objects backed by files on disk are called mapped files, and those backed by memory are called page-file-backed sections (even if there is no page file enabled). Pieces of files can be mapped selectively using a view, specified via a range.

This mapped file mechanism is obviously powerful, and quite heavily used. It allows many applications to take advantage of shared resources (DLL files for example), in addition to accessing their own image. However, it has an interesting side effect when it comes to getting a sense for the “true” memory usage of a process. Measuring private memory is often sufficient, but it may not tell the full story. If a process is the only process using a DLL, is that not part of the “memory usage” of that process? That shareable memory will go unaccounted for in the general “memory usage” section of most tools (task manager, for example). No doubt the easily obtainable measurement of memory private to a process is sufficient in most cases.

“Large” pages are also available to processes which support them. A large page is 2 megabytes on x64. Large pages have a few restrictions that normal pages do not: They must be reserved and committed in one operation, are always present in physical memory (there’s no file system support for paging large pages to disk), and are not considered part of the working set. They are, however, accounted for as part of the process’ private bytes. There are also huge pages, 1 gigabyte in size, available with newer versions of Windows 10. Per Windows Internals, huge pages are given automatically when more than 1 gigabyte of large pages is requested. It also appears to be possible to request huge pages explicitly via VirtualAlloc2.

Sources:
Mark Russinovich’s talk on virtual memory: https://www.youtube.com/watch?v=TrFEgHr72Yg.
Windows Internals 7th Edition, Part 1