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}