Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Page Entry Format & Flags

Both page directory entries and page table entries use the same 32-bit format.

Entry Structure

Bit:  31          12 | 11  9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
      ──────────────   ──────   ─   ─   ─   ─   ─   ─   ─   ─   ─
      Physical Address  Avail   G   PS  D   A  PCD PWT U/S R/W  P
      (20 bits, page-              (PT (PT
       aligned)                    only) only)

The upper 20 bits hold the physical address of the next-level structure (or the page itself). Since all structures are 4KB-aligned, the lower 12 bits of the address are always zero — the CPU uses those bits for flags instead.

Flag Definitions

BitFlagNameDescription
0PPresent1 = page/table exists in physical memory. If 0, accessing this address causes a page fault.
1R/WRead/Write0 = read-only, 1 = read/write
2U/SUser/Supervisor0 = kernel-only (ring 0), 1 = accessible from user mode (ring 3)
3PWTWrite-ThroughCache policy. 0 = write-back, 1 = write-through
4PCDCache Disable1 = disable CPU cache for this page
5AAccessedCPU sets this bit when the page is read or written
6DDirtyCPU sets this when the page is written (page table entries only)
7PSPage SizeIn PD entries: 0 = 4KB pages, 1 = 4MB pages
8GGlobalPage is not flushed from TLB on CR3 write
9-11AvailAvailableReserved for OS use — CPU ignores these bits

The Flags We Use

#![allow(unused)]
fn main() {
pub const PAGE_PRESENT:  u32 = 1 << 0;  // Page exists
pub const PAGE_WRITABLE: u32 = 1 << 1;  // Allow writes
pub const PAGE_USER:     u32 = 1 << 2;  // User mode accessible
}

For kernel pages: PAGE_PRESENT | PAGE_WRITABLE (no user access) For user pages: PAGE_PRESENT | PAGE_WRITABLE | PAGE_USER

Constructing an Entry

#![allow(unused)]
fn main() {
// Physical address: 0x00123000
// Flags: present + writable
let entry: u32 = 0x00123000 | PAGE_PRESENT | PAGE_WRITABLE;
// = 0x00123003
}

The physical address must be 4KB-aligned (lower 12 bits zero), so |-ing in the flags never corrupts the address.

Extracting the Address

#![allow(unused)]
fn main() {
let physical_address = entry & !0xFFF;   // Mask off the flags
let flags = entry & 0xFFF;              // Mask off the address
}

The PDEBit Enum

In our implementation, bits are represented as an enum for type-safe manipulation:

#![allow(unused)]
fn main() {
#[repr(u32)]
#[derive(Copy, Clone)]
pub enum PDEBit {
    P   = 0,   // Present
    RW  = 1,   // Read/Write
    US  = 2,   // User/Supervisor
    PWT = 3,   // Write-Through
    PCD = 4,   // Cache Disable
    A   = 5,   // Accessed
    D   = 6,   // Dirty
    PS  = 7,   // Page Size
    G   = 8,   // Global
}

impl PDEBit {
    const fn mask(self) -> u32 { 1 << (self as u32) }
}

fn is_set(entry: u32, bit: PDEBit) -> bool {
    (entry & bit.mask()) != 0
}
}