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. If 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 Kva::new(self.0.as_ref().as_ptr() as usize)
236 .unwrap()
237 .into_pa()
238 }
239
240 /// Map a virtual address (`va`) to a physical page (`pg`) with the
241 /// specified permissions (`perm`).
242 ///
243 /// This method updates the page table by mapping the provided virtual
244 /// address to the given physical page, along with the requested
245 /// permissions. It ensures that the virtual address is correctly mapped to
246 /// the physical page, enabling proper memory access.
247 ///
248 /// # Arguments
249 /// - `va`: The virtual address to map.
250 /// - `pg`: The physical page to map to the virtual address.
251 /// - `perm`: The permissions to apply to the mapping (e.g., read, write).
252 ///
253 /// # Returns
254 /// A `Result` indicating success or failure of the mapping operation. If
255 /// successful, it returns `Ok(())`. If there's an error (e.g., invalid
256 /// virtual address or permissions), it returns an appropriate
257 /// [`PageTableMappingError`].
258 pub fn map(&mut self, va: Va, pg: Page, perm: Permission) -> Result<(), PageTableMappingError> {
259 let pa = pg.into_raw();
260 unsafe {
261 self.do_map(va, pa, perm).inspect_err(|_| {
262 Page::from_pa(pa);
263 })
264 }
265 }
266
267 /// Map a physical address (`pa`) to a virtual address (`va`) with the
268 /// specified permissions (`perm`).
269 ///
270 /// # Safety
271 /// This method is marked `unsafe` because it relies on the assumption
272 /// that the physical address (`pa`) is valid.
273 ///
274 /// # Arguments
275 /// - `va`: The virtual address to map.
276 /// - `pa`: The physical address to map to the virtual address.
277 /// - `perm`: The permissions to apply to the mapping (e.g., read, write).
278 ///
279 /// # Returns
280 /// A `Result` indicating success or failure of the mapping operation. If
281 /// successful, it returns `Ok(())`. If there's an error (e.g., invalid
282 /// physical address or permissions), it returns an appropriate
283 /// [`PageTableMappingError`].
284 pub unsafe fn do_map(
285 &mut self,
286 va: Va,
287 pa: Pa,
288 perm: Permission,
289 ) -> Result<(), PageTableMappingError> {
290 let indices = PtIndices::from_va(va)?;
291 // Hint: Use `Page::new()` to allocate tables.
292 todo!()
293 }
294
295 /// Unmap the given virtual address (`va`) and return the physical page that
296 /// was mapped to it.
297 ///
298 /// This method removes the mapping for the specified virtual address (`va`)
299 /// and returns the physical page (`Page`) that was mapped to it, if
300 /// such a mapping existed.
301 ///
302 /// # Arguments
303 /// - `va`: The virtual address to unmap.
304 ///
305 /// # Returns
306 /// A `Result` containing the physical page ([`Page`]) that was mapped to
307 /// the given virtual address, or an error if the unmapping operation
308 /// fails (e.g., the virtual address was not previously mapped).
309 pub fn unmap(&mut self, va: Va) -> Result<Page, PageTableMappingError> {
310 let indices = PtIndices::from_va(va)?;
311 // Hint: Use `Page::from_pa()`.
312 let pa = todo!();
313 Ok(StaleTLBEntry::new(va, unsafe { Page::from_pa(pa) }).invalidate())
314 }
315
316 /// Walk through the page table to find reference to the corresponding page
317 /// table entry (PTE) for the given virtual address (`va`).
318 ///
319 /// This method traverses the 4-level page table structure and returns a
320 /// reference to the page table entry (Pte) for the specified virtual
321 /// address, if such an entry exists.
322 ///
323 /// # Arguments
324 /// - `va`: The virtual address to find the corresponding page table entry
325 /// for.
326 ///
327 /// # Returns
328 /// A `Result` containing a reference to the page table entry (Pte)
329 /// corresponding to the given virtual address, or an error if the entry
330 /// does not exist (e.g., if the address is not mapped).
331 pub fn walk(&self, va: Va) -> Result<&Pte, PageTableMappingError> {
332 let indices = PtIndices::from_va(va)?;
333 todo!()
334 }
335
336 /// Walk through the page table to find mutable reference for the
337 /// corresponding page table entry (PTE) for the given virtual address
338 /// (`va`).
339 ///
340 /// This method traverses the 4-level page table structure and returns a
341 /// object to modify the page table entry (Walked) for the specified virtual
342 /// address, if such an entry exists.
343 ///
344 /// # Arguments
345 /// - `va`: The virtual address to find the corresponding page table entry
346 /// for.
347 ///
348 /// # Returns
349 /// A `Result` containing a `Walked` referring to the page table entry (Pte)
350 /// corresponding to the given virtual address, or an error if the entry
351 /// does not exist (e.g., if the address is not mapped).
352 pub fn walk_mut(&mut self, va: Va) -> Result<Walked<'_>, PageTableMappingError> {
353 let indices = PtIndices::from_va(va)?;
354 todo!()
355 }
356
357 /// Clears all entries from the page table and deallocates associated pages.
358 ///
359 /// This function traverses all levels of the page table, unmapping each
360 /// mapped virtual address and deallocating the corresponding physical
361 /// pages. After calling this method, the page table will be empty,
362 /// including intermediate levels such as PDP, PD, and PT, except
363 /// for the PML4 page itself, which remains allocated.
364 ///
365 /// This method is automatically called when a [`PageTable`] is dropped.
366 /// At this point, it is guaranteed that no cores are using this page table.
367 ///
368 /// # Behavior
369 /// - Unmaps all virtual addresses currently mapped in the page table.
370 /// - Frees all allocated pages, including intermediate-level page tables.
371 /// - Leaves only the root page (PML4) intact.
372 ///
373 /// # Safety
374 /// - Must only be called when no active process depends on the current
375 /// mappings.
376 /// - Calling this on a live page table (e.g., the currently active one) may
377 /// result in undefined behavior.
378 fn clear(&mut self) {
379 // TODO: Clear the page table.
380 // You must clear the mid-level table.
381 // You don't need to care about pml4i indices larger than
382 // [`PageTableRoot::KBASE`].
383 todo!()
384 }
385}
386
387impl Default for PageTable {
388 fn default() -> Self {
389 Self::new()
390 }
391}
392
393impl Drop for PageTable {
394 fn drop(&mut self) {
395 assert_ne!(
396 keos::intrinsics::read_cr3(),
397 self.pa().into_usize(),
398 "Trying to drop activated page table."
399 );
400 self.clear()
401 }
402}
403
404/// A mutable reference to a page table entry (PTE) associated with a virtual
405/// address.
406///
407/// `Walked` provides safe and convenient access for modifying an existing
408/// mapping in the page table. It is typically the result of a successful page
409/// table walk and includes both the virtual address and a mutable reference to
410/// the corresponding page table entry.
411///
412/// This abstraction is useful for implementing operations such as clearing
413/// mappings, updating physical page mappings, or changing permissions.
414pub struct Walked<'a> {
415 addr: Va,
416 pte: &'a mut Pte,
417}
418
419impl Walked<'_> {
420 /// Clears the current mapping by returning the physical page and a
421 /// [`StaleTLBEntry`] for flushing the TLB.
422 ///
423 /// # Returns
424 /// - `Some(StaleTLBEntry)` if the entry is mapped.
425 /// - `None` if the entry is not valid.
426 pub fn clear(&mut self) -> Option<StaleTLBEntry> {
427 unsafe {
428 self.pte
429 .clear()
430 .map(|pa| StaleTLBEntry::new(self.addr, Page::from_pa(pa)))
431 }
432 }
433
434 /// Sets this page table entry to map to the given page with the specified
435 /// flags.
436 ///
437 /// # Parameters
438 /// - `page`: The physical page to be mapped.
439 /// - `flags`: The desired access permissions and attributes for the
440 /// mapping.
441 ///
442 /// # Errors
443 /// Returns `Err(PageTableMappingError)` if the physical address cannot be
444 /// set (e.g., due to address alignment or capacity limits).
445 pub fn set_page(&mut self, page: Page, flags: PteFlags) -> Result<(), PageTableMappingError> {
446 if self.pte.flags().contains(PteFlags::P) {
447 Err(PageTableMappingError::Duplicated)
448 } else {
449 unsafe {
450 self.pte.set_pa(page.into_raw())?.set_flags(flags);
451 }
452 Ok(())
453 }
454 }
455}
456
457impl Deref for Walked<'_> {
458 type Target = Pte;
459
460 fn deref(&self) -> &Self::Target {
461 self.pte
462 }
463}