osdev-notes/Physical & Virtual Memory.md
2025-09-25 23:49:32 -04:00

6.1 KiB

Most processes are not permitted to directly access physical memory. Access is mediated transparently through the MMU (Memory Management Unit). Even the kernel's memory access is mediated through the MMU, so that it can account for all the allocated physical memory, and so that it doesn't overwrite its own address space.

Physical Memory

Physical memory on x86 is divided into "Page Frames":

  • Strictly physical.
  • Serves as the backing for a "Page", which is strictly virtual.
  • All of physical memory is separated into a number of page frames.
  • Can be either 4KiB, 2MiB, or 1GiB depending on the "level" of paging. This is a useful abstraction, because pages must be aligned at a multiple of their size - for example, a 2MiB page must be aligned on a 2MiB boundary. In the simplest setup involving 2MiB pages, all of physical memory would be divided into 2MiB page frames for accounting.

Virtual Memory & Pages

In the paging scheme, virtual memory is divided into cascading tables. The depth of the tables will differ depending on which paging mode is enabled. There exists several modes: two-level, three-level, four-level, and five-level paging. The last two require Physical Address Extensions.

Pages

A page is a virtual, contiguous segment of memory, which is backed by exactly one page frame. A page is defined by its entry in a page table, or page directory, each of which may contain up to 4096 pages.

32-Bit Paging

32-Bit Paging involves two tables, or two "levels", that are traversed to determine the physical location of a logical address. The first table is the "Page Directory", and each entry in the page directory points to a "Page Table", which contains entries pointing to physical frames. In a sense, each entry in the page table is a page, in that it represents one physical page frame. Both the page directory, and each page table, contains 1024 4-byte (32-bit) entries. Overall, the page directory is 4096KiB, and so is each page table. In 32-bit paging mode, bits 12..=31 of the register CR3 indicate the address of the root page directory.

Each page directory entry looks like this:

31..=12 11..=8 7 6 5 4 3 2 1 0
Bits 31..=12 of address AVL PS=0 AVL A PCD PWT U/S R/W P
If the Page Size bit is set, the entry refers directly to a 4MiB page, instead of a page table.
31..=22 21 20..=13 12 11..=9 8 7 6 5 4 3 2 1 0
Bits 31..=22 of address RSVD Bits 39..=32 of address PAT AVL G PS=1 D A PCD PWT U/S R/W P
If the Page Size bit is set, the entry refers directly to a 4MiB page, instead of a page table.
Here are the keys and their meanings:
  • RSVD: Reserved - Must be set to 0, otherwise, will cause a page fault.
  • P: Present - If this bit is 1, the page is in memory. If this bit is 0, accessing the page will throw a Page Fault.
  • R/W: Read-Write - If this bit is 1, the page is writable. If this bit is 0, the page is read-only, and writing to it will Page Fault.
  • U/S: User/Supervisor - If this bit is 1, the page is accessible by unprivileged code. If this bit is 0, it is only accessible to the kernel or supervisor.
  • PWT: Page Write-Through - If this bit is 1, write-through caching is enabled, and changes to the cache are immediately written to physical memory. If this bit is 0, changes to the cache are only written when the cache is invalidated.
  • PCD: Page Cache Disable - If this bit is set, the page will not be cached.
  • A: Accessed - Set to 1 by the CPU if the Page Directory Entry was read by the CPU, as opposed to the address being obtained from the TLB.
  • D: Dirty - Set to 1 if a page is written to.
  • PS: Page Size - Set to 1 if the entry refers to a 4MiB page, instead of 1,024 4KiB pages stored in the referenced page table.
  • G: Global - If set to 1, the processor will not invalidate the relevant TLB entry if CR3 is modified.
  • AVL: Available - Unused by the CPU, and can be used by the OS. As far as I know, nothing popular uses these bits.

32-Bit PAE

If Physical Address Extensions is enabled, then 3-level paging is used. In 3-level paging, instead of the root table being a page directory, it will be a Page Directory Pointer Table, which is a list of 4 64-bit entries. Each entry refers to a page directory, full of 512 64-bit entries. Each page directory entry points to a page table, which is full of 512 64-bit page entries.

64-Bit Paging

Requires Physical Address Extensions (PAE). 64-bit paging enables the use of 3, 4, and 5-level paging.

CR3
└─ PML5 Table: 512 * 64-bit entries
   └─ PML4 Table: 512 * 64-bit entries
      └─ PDP Table: 4 * 64-bit entries
         └─ Page Directory: 512 * 64-bit entries
	        └─ Page Table: 512 * 64-bit entries
			   └─ Page
type Page: u64; // Page reference is 64 bits, stores address and flags of an individual page
type PageTable: [u64;512]; // Page Table entry is 64 bits, refers to a Page
type PageDirectory: [u64;512]; // Page Directory entry is 64 bits, refers to a PageTable
type PDPTable: [u64;512]; // Page Directory Pointer Table entry is 64 bits, refers to a Page Directory
type PML4Table: [u64;512]; // Page Map Level 4 Table entry is 64 bits, refers to a Page Directory Pointer Table
type PML5Table: [u64;512];
// CR3 holds a reference to the root PML5Table

Address Translation Example

If a process wanted to access the virtual address 0xdeadbeef (a 32-bit virtual address), the MMU would separate the address into three parts. Address: 0xdeadbeef / 0b11011110101011011011111011101111 First 10 bits: 0b1101111010 / 890 - Page Directory Offset (Page Table) Next 10 bits: 0b1011011011 / 731 - Page Table Offset (Page Table Entry) Last 12 bits: 0b111011101111 / 3823 - Offset from start of page So the MMU would search for the 3,823rd byte, of the 731st page, of the 890th page table.