An Introduction to Windows Processes and Memory Management

Dive into Windows internals. This guide demystifies how processes work and how memory is managed. Explore virtual address space, VADs, page states (free, reserved, committed), and the crucial split between user and kernel space. A clear look at the OS core.

Windows
Kernel
Operating Systems
Banner image

While this blog aims to make these ideas accessible, I highly recommend reading the original "Windows Kernel Programming" book for more information.

Processes

A process in Windows is not what executes the code it is a container that manages and holds everything necessary for execution. The actual execution happens inside threads, which run inside the process. The process itself is more like an environment that provides resources and context for those threads.

a process includes the following important components:

  1. Executable Program, The process is typically created from an executable file (like .exe or .dll). This file provides the machine code and initial data that the process will use. Some system processes, however, may not have a corresponding executable image and can be created directly by the Windows kernel (for example, certain internal processes or pseudo-processes).
  2. Private virtual address space , Every process has its own isolated memory space. This means no other process can directly access it, ensuring stability and security. Within this space, the process can allocate memory for its code, data, stack, and heap.
  3. Access token (Primary Token), This token defines the security context of the process. It contains details like the user’s identity, group memberships, and privileges. Threads inside the process use this token when accessing resources. A thread can temporarily change its security context by impersonating another token, but by default, all threads inherit the process’s primary token.
  4. Handle table, A process maintains its own table of handles to system objects (such as files, events, mutexes, semaphores, or registry keys). Handles are references that allow the process to interact with kernel objects safely without direct memory access.
  5. Threads,Threads are the actual units that run the code. When a process is first created, it typically starts with one thread (executing the `main` or `WinMain` function). More threads can be created to handle different tasks concurrently. A process with no threads cannot execute any code; if it loses all its threads, the kernel usually terminates it automatically.

a process is uniquely identified using Process ID, which remain unique as long as kernel process object exits.Once it’s destroyed, the same ID may be reused for new processes. It’s important to realize that the executable file itself is not a unique identifier of a process

Virtual Memory

Every process in Windows gets its own private virtual address space, which means the addresses it sees are isolated from all other processes. This ensures that one process cannot directly read or modify another process’s memory a fundamental part of process protection and stability.

when a process start it's virtual address space is mostly empty, with only a few component mapped:

  • the main executable exe file.
  • the NTDLL.dll a key user-mode library providing system call interfaces
  • additional system DLLs like kernel32.dll

when the process runs, more regions are added like heap memory, more stack space for each thread. more loaded DLLs, memory-mapped files, and so on. these regions are managed via virtual address space or VADs which record parts of the space that are reserved, commuted, and what protection they have.

The layout and total size of the virtual address space depend on the process and operating system architecture:

On a 32-bit System

For a standard 32-bit process running on 32-bit Windows, the 4 GB of total virtual address space is split evenly:

  1. User Space (Lower 2 GB): This region is private to the process. It's where the application's code, data, heap, and thread stacks reside.
  2. Kernel Space (Upper 2 GB): This region is reserved for the operating system kernel, device drivers, and other system-level components. It is shared and mapped into every process's address space, though it's only accessible from kernel mode.

On a 64-bit System

For a 64-bit process on 64-bit Windows, the address space is astronomically larger, providing a massive canvas for both user and kernel code:

  1. User Space:On Windows 8 and earlier, this space was 8 TB.
    On Windows 8.1 and later, this was expanded to 128 TB.
  2. Kernel Space:Similarly, the kernel also gets its own massive region (8 TB or 128 TB)

Virtual Address Descriptor (VAD)

Every process in Windows operates within its own private virtual address space, a vast and seemingly empty landscape of memory addresses. But how does the operating system keep track of which parts of this space are being used, what they're used for, and what rules apply to them?

This is where the Virtual Address Descriptor (VAD) comes in.
A VAD is a kernel data structure that acts like a blueprint for a specific, continuous range of virtual addresses within a process. Think of it as a deed to a plot of land in the process's memory map. Instead of just one VAD, the Windows Memory Manager maintains a whole collection of them for each process, organized in a highly efficient, self-balancing tree structure (similar to an AVL tree). This allows the kernel to quickly find, add, or remove memory regions.


When a thread tries to access a virtual address, the kernel swiftly searches the VAD tree to answer critical questions:

  • Does this address belong to a valid memory region?
  • Is this region reserved for code from the main .exe, a loaded .dll, or something else?
  • What are the permissions? Can the thread read, write, or execute code here?
  • Is the memory actually in RAM (committed), or does it need to be loaded from the disk?

Each VAD node stores essential information to manage its memory block, including:

  • Starting and Ending Virtual Addresses: The exact boundaries of the memory region.
  • Allocation Type: Whether the memory is private, mapped from a file, reserved, or committed.
  • Protection Flags: The access rights (read, write, execute).
  • Backing Information: If it's a memory-mapped file, the VAD links to the underlying file object.
  • Tree Links: Pointers to parent and child nodes to maintain the balanced tree structure for fast lookups.

Example: The Initial Memory Map of a 64-bit Process

When a 64-bit process starts, its virtual address space is populated with a few key components:

  1. 0x0000000000000000 to 0x000000000000FFFF: This initial 64 KB region is reserved and made inaccessible to catch NULL pointer errors.
  2. 0x0000000000010000: The main executable (.exe) is mapped here.
  3. 0x00007FF800000000: The core system library, ntdll.dll, is mapped into a high address.
  4. 0x00007FFA00000000: Other essential DLLs, like kernel32.dll, are also mapped nearby.

As the process allocates memory (malloc, VirtualAlloc, etc.), new VADs appear in this virtual space. However, physical RAM is not necessarily used until those regions are accessed virtual memory allows on-demand commitment of page

memory address has three characteristics we need to understand:

  1. Virtual means the addresses your program uses (e.g. 0x20000) don’t directly correspond to physical RAM addresses. The OS and hardware translate them behind the scenes.
  2. Private means each process has its own view of addresses. Address 0x20000 in Process A is different from 0x20000 in Process B.
  3. Linear means the address space is a continuous range (from the lowest possible virtual address to the highest for that process).

When you refer to address 0x20000, that number alone doesn’t tell you what memory is there; you must also know which process you’re in. Because each process maps its own virtual addresses differently, the same virtual address can map to different physical memory or none at all in different processes.


How Virtual Addresses Are Translated

When your code tries to read or write memory via a virtual address, the CPU/OS perform several steps:

  1. The CPU (via its memory management unit, MMU) attempts to translate the virtual address into a physical address, using page tables (we will see it later on).
  2. If there is a valid mapping (the page is “resident” in RAM and permissions are okay), it proceeds and reads/writes the physical memory.
  3. If there is no valid mapping (e.g. the page isn’t currently in RAM, or has been swapped out, or simply never allocated), the CPU triggers a page fault exception.
  4. The OS’s page fault handler runs. It:
    1. Checks whether the address is valid (i.e., part of a memory region assigned to that process). If invalid, the process gets an access violation.
    2. If valid but not resident, load the data from its backing store (file mapping or page file) into a free physical page.
    3. Update the page tables (so the virtual-to-physical mapping is established).
    4. Resume the faulting instruction (so the access “succeeds” this time, now that mapping exists).

This mechanism lets programs act as if they have a large, continuous memory space, without requiring all of it to be physically in RAM at once.

in Windows, memory is not managed byte by byte, but in fixed-size blocks known as pages. The page is the fundamental unit of memory management. All attributes, such as whether a memory region can be read from, written to, or executed, are applied at this page level. This means an entire page shares the same protection status; you cannot, for instance, make half a page readable and the other half read-only. The standard page size is determined by the CPU architecture, which for most modern systems is 4 KB. Consequently, all memory operations performed by the OS, from allocating memory to changing its protection, are done in multiples of this page size.
To optimize performance for applications that handle large datasets, Windows also supports large pages (typically 2 MB) and huge pages (1 GB). By mapping a vast memory region with a single translation entry instead of hundreds, these larger pages significantly speed up memory access and reduce the overhead on the CPU's address translation cache (the TLB).
However, this performance boost comes with notable trade-offs. Large and huge pages require a single, unbroken block of contiguous physical memory, which can be difficult to allocate if RAM is fragmented. Furthermore, they are non-pageable, meaning they are locked into physical RAM and can never be swapped out to disk, which can put pressure on system memory. Finally, they support a more limited set of protection flags compared to standard 4 KB pages.

Page States

each page in virtual memory can be in one of the three state

  1. Free
    1. The memory is completely unused.
    2. There is no data, no physical memory, and no reservation associated with that address range.
    3. If a program tries to read or write from a free page, the CPU will raise an access violation (the same as a segmentation fault).
    4. Example: when a process first starts, most of its address space is free.
    5. but why? it mean there is no process have this space yet. so any process request new memory the OS can give it from the free.
  2. Committed
    1. The memory is fully allocated and usable.
    2. A committed page has physical memory (RAM) or a backing store (like a page file) assigned to it.
    3. The process can access it normally, as long as permissions allow (e.g., read, write, execute).
    4. Example: when you use `malloc()` or `VirtualAlloc(..., MEM_COMMIT)`, Windows commits pages — they can now store data or code.
  3. Reserved
    1. The memory range is marked as “taken” (reserved), but no physical memory is assigned yet.
    2. It simply prevents other allocations from using that virtual address range.
    3. From the CPU’s perspective, reserved pages behave like free ones — any access to them still causes an access violation because they are not committed.
    4. Example: a program can reserve a large region (say 1 GB) using VirtualAlloc(..., MEM_RESERVE) so that it has a continuous range of virtual addresses, but commit smaller portions (4 KB or more) later when actually needed. This is common in thread stack allocation and memory pools.

System Memory (kernel memory)

The lower part of the address space is for user-mode processes use.and the upper part is for the operating system and kernel-mode code.

in 32-bit Windows, the total address space is 4 GB (2³² = 4 GB). By default, the lower 2 GB (0x00000000–0x7FFFFFFF) are for user-mode processes, and the upper 2 GB (0x80000000–0xFFFFFFFF) are for the operating system.

On 64-bit Windows, the address space is vastly larger.In Windows 8 and Server 2012 (and earlier), the OS uses the upper 8 TB for kernel-mode space. From Windows 8.1 and Server 2012 R2 onward, the OS reserves the upper 128 TB for kernel-mode space.

  • System space (or kernel space) is not relative to a process. It is a **common, absolute region** of virtual memory that is mapped into *every* process’s address space. That means from any process you look at, the system space addresses refer to the *same* kernel code/data.
  • Because system space is shared and absolute, an address in system space has the same meaning across all processes. That is, address `0xFFFF…` in process A’s kernel region refers to the same kernel object as address `0xFFFF…` in process B’s kernel region.
  • User-mode code cannot access system space addresses—attempting to do so causes an access violation.
  • System space holds the kernel itself, the HAL (hardware abstraction layer), kernel drivers, etc. Because those are system-wide, they must be globally accessible (in kernel mode) and protected from user mode.
  • If a kernel driver “leaks” memory in system space (i.e. allocates kernel memory and never frees it), that leak persists beyond the driver’s lifetime because that memory is in system space, not tied to a user process.
  • In contrast, memory allocated in user space belongs to a specific process; when that process exits, the kernel reclaims and frees all its user-space allocations and closes all handles.

Published Oct 27, 2025