keos_project2/page_table.rs
1//! # Four-Level Page Table of x86_64
2//!
3//! One of the main roles of an operating system is resource abstraction. An
4//! important resource in a computer system is memory. Each process operates in
5//! its own memory space, which must be isolated from other processes. For
6//! instance, your web browser should not have access to the memory used by your
7//! music player. To ensure such isolation, hardware introduces a memory
8//! protection mechanism that isolates the memory between processes.
9//!
10//! ## Virtual Memory
11//!
12//! The concept of virtual memory abstracts memory addresses from the underlying
13//! physical storage device. Instead of directly accessing the physical memory,
14//! addresses are translated through the Memory Management Unit (MMU). To
15//! distinguish between these two types of addresses:
16//! - A **virtual address** is used by programs before translation.
17//! - A **physical address** refers to the actual location in memory after
18//! translation.
19//!
20//! A key distinction between virtual and physical addresses is that physical
21//! addresses are unique and always refer to the same memory location. In
22//! contrast, virtual addresses can be mapped to the same physical address or
23//! different physical addresses at different times.
24//!
25//! ## Paging
26//!
27//! Paging is a memory management technique that divides both physical and
28//! virtual memory into small, fixed-size chunks called **pages**. Typically, a
29//! page is 4096 bytes in size. The mapping of physical and virtual memory is
30//! managed via a **page table**, with each entry representing a page. The
31//! active page table is typically managed through a special CPU register (e.g.,
32//! `cr3` in x86_64).
33//!
34//! For every memory access, the CPU translates the virtual address to a
35//! physical address using the page table. Since checking the page table for
36//! every conversion would be inefficient, the CPU stores the results in a cache
37//! called the **Translation Lookaside Buffer (TLB)**.
38//!
39//! The TLB is a CPU cache that stores recent translations of virtual memory
40//! addresses to physical memory. The TLB is not updated automatically when the
41//! page table is modified, so the kernel must explicitly invalidate the TLB
42//! entries after a page table update. Unless you invalidate the entry, the kernel
43//! may be work with a stale memory.
44//!
45//! ## Paging in x86_64
46//!
47//! x86_64 uses 4096-byte pages and employs a 4-level page table. Each table is
48//! 4096 bytes in size, which is the same size as the page, and each entry in
49//! the table is 8 bytes. This structure allows for a 48-bit physical address
50//! space to be covered by the page table.
51//!
52//! The virtual address in x86_64 can be broken down into the following levels:
53//! ```
54//! 63 48 47 39 38 30 29 21 20 12 11 0
55//! +-------------+----------------+----------------+----------------+-------------+------------+
56//! | Sign Extend | Page-Map | Page-Directory | Page-directory | Page-Table | Page |
57//! | | Level-4 Offset | Pointer | Offset | Offset | Offset |
58//! +-------------+----------------+----------------+----------------+-------------+------------+
59//! | | | | | |
60//! +------- 9 ------+------- 9 ------+------- 9 ------+----- 9 -----+---- 12 ----+
61//! Virtual Address
62//! ```
63//!
64//! - The **sign extend** portion represents the higher bits, ensuring proper
65//! sign extension for the full address.
66//! - The Page Map Level 4 (PM4) identifies the highest-level page directory.
67//! - The subsequent levels (Page Directory, Page Table) map smaller chunks of
68//! virtual memory to physical memory.
69//! - The **page offset** specifies the position within the 4096-byte page.
70//!
71//! A page must be **page-aligned**, meaning the virtual address must be
72//! divisible by the page size (4096 bytes). The last 12 bits of the 64-bit
73//! virtual address represent the page **offset**, while the upper bits are used
74//! as indices for the page table.
75//!
76//! The page table also defines various attributes for each page, such as access
77//! permissions (e.g., read/write/user). Note that the attributes from all
78//! levels are **AND**ed together. This means attributes of the intermediate
79//! level must contain all possible attributes.
80//!
81//! In x86_64, the `invlpg` instruction invalidates a specific TLB entry based
82//! on the given virtual address. Note that the entire TLB is also flushed when
83//! the `cr3` register is reloaded.
84//!
85//! ## Managing [`PageTable`] in KeOS
86//! You need to implement x86_64's 4-level page table scheme. The core
87//! abstraction about page table is [`PageTable`]. With this abstraction, you
88//! will implement page table walking, mapping, and unmapping. In addition to
89//! mapping and unmapping pages, the page table must be clear the entries when
90//! it deallocates an associated memory. This traverses the entire 4-level page
91//! table, unmapping each mapped virtual address and deallocating
92//! the corresponding physical pages. After calling this method, all page table
93//! levels—including the Page Directory Pointer Table (PDP), Page Directory
94//! (PD), and Page Table (PT)—will be deallocated, leaving only the root Page
95//! Map Level 4 (PML4).
96//!
97//! This [`PageTable`] represents a page table of a user process. Each process
98//! has its own set of user pages, which reside below the kernel base address,
99//! where pml4 index is lower than [`PageTableRoot::KBASE`]. The set of
100//! kernel pages, however, is global and remains fixed in the virtual
101//! address space, regardless of the running process or thread. These pages are
102//! shared between multiple page tables, meaning that they **MUST NOT**
103//! deallocated in any cases.
104//!
105//! KeOS already provides several abstractions to work with page table.
106//! - The virtual and physical addresses: [`Pa`] and [`Va`].
107//! - The Memory Permission: [`Permission`].
108//! - Each table entry: [`Pml4e`], [`Pdpe`], [`Pde`], and [`Pte`].
109//! - Flag of each table entry: [`Pml4eFlags`], [`PdpeFlags`], [`PdeFlags`], and
110//! [`PteFlags`].
111//! - Invalidate a TLB entry: [`StaleTLBEntry::invalidate`].
112//!
113//! ## Implementation Requirements
114//! You need to implement the followings:
115//! - [`PtIndices::from_va`]
116//! - [`PageTable::do_map`]
117//! - [`PageTable::unmap`]
118//! - [`PageTable::walk`]
119//! - [`PageTable::walk_mut`]
120//! - [`PageTable::clear`]
121//!
122//! Make sure to implement the necessary functions for TLB
123//! invalidation, and ensure the correct handling of memory protection and
124//! access permissions for pages.
125//!
126//! By the end of this part, you will have built an essential component for
127//! memory management, ensuring that processes can access their memory securely
128//! and efficiently through the page table.
129//! When you finish implementing all tasks, move on to the next [`section`].
130//!
131//! [`StaleTLBEntry`]: StaleTLBEntry
132//! [`section`]: crate::mm_struct
133
134use alloc::boxed::Box;
135use core::ops::Deref;
136use keos::{
137 addressing::{Kva, Pa, Va},
138 mm::{Page, page_table::*},
139};
140
141/// Represents page table indices for a given virtual address (VA).
142///
143/// In the x86_64 architecture, virtual addresses are translated to physical
144/// addresses using a 4-level paging hierarchy:
145/// - PML4 (Page Map Level 4)
146/// - PDPT (Page Directory Pointer Table)
147/// - PD (Page Directory)
148/// - PT (Page Table)
149///
150/// This structure extracts the index values for each of these levels from a
151/// virtual address. This struct provides a way to **decompose** a virtual
152/// address into its respective page table indices.
153pub struct PtIndices {
154 /// The virtual address (VA) associated with this page table index
155 /// breakdown.
156 pub va: Va,
157
158 /// Page Map Level 4 Index (PML4EI).
159 pub pml4ei: usize,
160
161 /// Page Directory Pointer table Index (PDPTEI).
162 pub pdptei: usize,
163
164 /// Page Directory Index (PDEI).
165 pub pdei: usize,
166
167 /// Page Table Index (PTEI).
168 pub ptei: usize,
169}
170
171impl PtIndices {
172 /// Extracts page table indices from a given virtual address ([`Va`]).
173 ///
174 /// This function takes a virtual address and calculates the corresponding
175 /// PML4, PDPT, PD, and PT indices based on x86_64 paging structure.
176 ///
177 /// # Arguments
178 /// - `va`: A virtual address ([`Va`]) to be broken down into page table
179 /// indices.
180 ///
181 /// # Returns
182 /// - `Ok(Self)`: If `va` is page-aligned (i.e., lower 12 bits are zero).
183 /// - `Err(PageTableMappingError::Unaligned)`: If `va` is not page-aligned.
184 pub fn from_va(va: Va) -> Result<Self, PageTableMappingError> {
185 if va.into_usize() & 0xFFF == 0 {
186 Ok(Self {
187 va,
188 pml4ei: todo!(),
189 pdptei: todo!(),
190 pdei: todo!(),
191 ptei: todo!(),
192 })
193 } else {
194 Err(PageTableMappingError::Unaligned)
195 }
196 }
197}
198
199/// Page Table Structure for x86_64 Architecture.
200///
201/// This implements a 4-level page table structure for the x86_64 architecture.
202/// It includes an inner structure ([`PageTableRoot`]) that stores the actual
203/// entries for each of the 512 slots in the page table at each level. The
204/// [`PageTable`] provides methods for mapping virtual addresses (VAs) to
205/// physical addresses (PAs) with different permission levels, unmapping pages,
206/// and walking the page table to find page table entries (PTEs)
207/// for given virtual addresses. This is a fundamental part of managing virtual
208/// memory in an operating system.
209pub struct PageTable(pub Box<PageTableRoot>);
210
211impl PageTable {
212 /// Create an empty page table.
213 ///
214 /// This method initializes a new page table that allows to access every
215 /// kernel address. The page table is represented as a
216 /// `Box<PageTableRoot>`, which contains an array of 512 [`Pml4e`] entries.
217 /// This structure is used in the 4-level paging system of x86_64
218 /// architecture.
219 ///
220 /// # Returns
221 /// A new [`PageTable`] instance with all entries initialized to zero.
222 pub fn new() -> Self {
223 Self(PageTableRoot::new_boxed_with_kernel_addr())
224 }
225
226 /// Get physical address of this page table.
227 ///
228 /// This method calculates the physical address (PA) corresponding to the
229 /// current page table. It does this by converting the virtual address
230 /// (VA) of the [`PageTable`] structure into a physical address.
231 ///
232 /// # Returns
233 /// The physical address of the page table ([`Pa`]).
234 pub fn pa(&self) -> Pa {
235 self.0.as_ref().pa()
236 }
237
238 /// Map a virtual address (`va`) to a physical page (`pg`) with the
239 /// specified permissions (`perm`).
240 ///
241 /// This method updates the page table by mapping the provided virtual
242 /// address to the given physical page, along with the requested
243 /// permissions. It ensures that the virtual address is correctly mapped to
244 /// the physical page, enabling proper memory access.
245 ///
246 /// # Arguments
247 /// - `va`: The virtual address to map.
248 /// - `pg`: The physical page to map to the virtual address.
249 /// - `perm`: The permissions to apply to the mapping (e.g., read, write).
250 ///
251 /// # Returns
252 /// A `Result` indicating success or failure of the mapping operation. If
253 /// successful, it returns `Ok(())`. If there's an error (e.g., invalid
254 /// virtual address or permissions), it returns an appropriate
255 /// [`PageTableMappingError`].
256 pub fn map(&mut self, va: Va, pg: Page, perm: Permission) -> Result<(), PageTableMappingError> {
257 let pa = pg.into_raw();
258 unsafe {
259 self.do_map(va, pa, perm).inspect_err(|_| {
260 Page::from_pa(pa);
261 })
262 }
263 }
264
265 /// Map a physical address (`pa`) to a virtual address (`va`) with the
266 /// specified permissions (`perm`).
267 ///
268 /// # Safety
269 /// This method is marked `unsafe` because it relies on the assumption
270 /// that the physical address (`pa`) is valid.
271 ///
272 /// # Arguments
273 /// - `va`: The virtual address to map.
274 /// - `pa`: The physical address to map to the virtual address.
275 /// - `perm`: The permissions to apply to the mapping (e.g., read, write).
276 ///
277 /// # Returns
278 /// A `Result` indicating success or failure of the mapping operation. If
279 /// successful, it returns `Ok(())`. If there's an error (e.g., invalid
280 /// physical address or permissions), it returns an appropriate
281 /// [`PageTableMappingError`].
282 pub unsafe fn do_map(
283 &mut self,
284 va: Va,
285 pa: Pa,
286 perm: Permission,
287 ) -> Result<(), PageTableMappingError> {
288 let indices = PtIndices::from_va(va)?;
289 // Hint: Use `Page::new()` to allocate tables.
290 todo!()
291 }
292
293 /// Unmap the given virtual address (`va`) and return the physical page that
294 /// was mapped to it.
295 ///
296 /// This method removes the mapping for the specified virtual address (`va`)
297 /// and returns the physical page (`Page`) that was mapped to it, if
298 /// such a mapping existed.
299 ///
300 /// # Arguments
301 /// - `va`: The virtual address to unmap.
302 ///
303 /// # Returns
304 /// A `Result` containing the physical page ([`Page`]) that was mapped to
305 /// the given virtual address, or an error if the unmapping operation
306 /// fails (e.g., the virtual address was not previously mapped).
307 pub fn unmap(&mut self, va: Va) -> Result<Page, PageTableMappingError> {
308 let indices = PtIndices::from_va(va)?;
309 // Hint: Use `Page::from_pa()`.
310 let pa = todo!();
311 Ok(StaleTLBEntry::new(va, unsafe { Page::from_pa(pa) }).invalidate())
312 }
313
314 /// Walk through the page table to find reference to the corresponding page
315 /// table entry (PTE) for the given virtual address (`va`).
316 ///
317 /// This method traverses the 4-level page table structure and returns a
318 /// reference to the page table entry (Pte) for the specified virtual
319 /// address, if such an entry exists.
320 ///
321 /// # Arguments
322 /// - `va`: The virtual address to find the corresponding page table entry
323 /// for.
324 ///
325 /// # Returns
326 /// A `Result` containing a reference to the page table entry (Pte)
327 /// corresponding to the given virtual address, or an error if the entry
328 /// does not exist (e.g., if the address is not mapped).
329 pub fn walk(&self, va: Va) -> Result<&Pte, PageTableMappingError> {
330 let indices = PtIndices::from_va(va)?;
331 todo!()
332 }
333
334 /// Walk through the page table to find mutable reference for the
335 /// corresponding page table entry (PTE) for the given virtual address
336 /// (`va`).
337 ///
338 /// This method traverses the 4-level page table structure and returns a
339 /// object to modify the page table entry (Walked) for the specified virtual
340 /// address, if such an entry exists.
341 ///
342 /// # Arguments
343 /// - `va`: The virtual address to find the corresponding page table entry
344 /// for.
345 ///
346 /// # Returns
347 /// A `Result` containing a `Walked` referring to the page table entry (Pte)
348 /// corresponding to the given virtual address, or an error if the entry
349 /// does not exist (e.g., if the address is not mapped).
350 pub fn walk_mut(&mut self, va: Va) -> Result<Walked<'_>, PageTableMappingError> {
351 let indices = PtIndices::from_va(va)?;
352 todo!()
353 }
354
355 /// Clears all entries from the page table and deallocates associated pages.
356 ///
357 /// This function traverses all levels of the page table, unmapping each
358 /// mapped virtual address and deallocating the corresponding physical
359 /// pages. After calling this method, the page table will be empty,
360 /// including intermediate levels such as PDP, PD, and PT, except
361 /// for the PML4 page itself, which remains allocated.
362 ///
363 /// This method is automatically called when a [`PageTable`] is dropped.
364 /// At this point, it is guaranteed that no cores are using this page table.
365 ///
366 /// # Behavior
367 /// - Unmaps all virtual addresses currently mapped in the page table.
368 /// - Frees all allocated pages, including intermediate-level page tables.
369 /// - Leaves only the root page (PML4) intact.
370 ///
371 /// # Safety
372 /// - Must only be called when no active process depends on the current
373 /// mappings.
374 /// - Calling this on a live page table (e.g., the currently active one) may
375 /// result in undefined behavior.
376 fn clear(&mut self) {
377 // TODO: Clear the page table.
378 // You must clear the mid-level table.
379 // You don't need to care about pml4i indices larger than
380 // [`PageTableRoot::KBASE`].
381 todo!()
382 }
383}
384
385impl Default for PageTable {
386 fn default() -> Self {
387 Self::new()
388 }
389}
390
391impl Drop for PageTable {
392 fn drop(&mut self) {
393 assert_ne!(
394 keos::intrinsics::read_cr3(),
395 self.pa().into_usize(),
396 "Trying to drop activated page table."
397 );
398 self.clear()
399 }
400}
401
402/// A mutable reference to a page table entry (PTE) associated with a virtual
403/// address.
404///
405/// `Walked` provides safe and convenient access for modifying an existing
406/// mapping in the page table. It is typically the result of a successful page
407/// table walk and includes both the virtual address and a mutable reference to
408/// the corresponding page table entry.
409///
410/// This abstraction is useful for implementing operations such as clearing
411/// mappings, updating physical page mappings, or changing permissions.
412pub struct Walked<'a> {
413 addr: Va,
414 pte: &'a mut Pte,
415}
416
417impl Walked<'_> {
418 /// Clears the current mapping by returning the physical page and a
419 /// [`StaleTLBEntry`] for flushing the TLB.
420 ///
421 /// # Returns
422 /// - `Some(StaleTLBEntry)` if the entry is mapped.
423 /// - `None` if the entry is not valid.
424 pub fn clear(&mut self) -> Option<StaleTLBEntry> {
425 unsafe {
426 self.pte
427 .clear()
428 .map(|pa| StaleTLBEntry::new(self.addr, Page::from_pa(pa)))
429 }
430 }
431
432 /// Sets this page table entry to map to the given page with the specified
433 /// flags.
434 ///
435 /// # Parameters
436 /// - `page`: The physical page to be mapped.
437 /// - `flags`: The desired access permissions and attributes for the
438 /// mapping.
439 ///
440 /// # Errors
441 /// Returns `Err(PageTableMappingError)` if the physical address cannot be
442 /// set (e.g., due to address alignment or capacity limits).
443 pub fn set_page(&mut self, page: Page, flags: PteFlags) -> Result<(), PageTableMappingError> {
444 if self.pte.flags().contains(PteFlags::P) {
445 Err(PageTableMappingError::Duplicated)
446 } else {
447 unsafe {
448 self.pte.set_pa(page.into_raw())?.set_flags(flags);
449 }
450 Ok(())
451 }
452 }
453}
454
455impl Deref for Walked<'_> {
456 type Target = Pte;
457
458 fn deref(&self) -> &Self::Target {
459 self.pte
460 }
461}