keos_project3/lazy_pager.rs
1//! # Lazy Paging
2//!
3//! The lazy paging or demand paging is an another policy for the
4//! paging, which used by modern operating systems. Unlike the [`EagerPager`]
5//! that you implemented in project 2, the [`LazyPager`] defers physical page
6//! allocation until a page fault occurs. This method optimizes memory usage by
7//! mapping memory pages **on demand**, rather than preallocating them.
8//!
9//! Instead of allocating physical memory during the `mmap` call, the OS records
10//! **metadata** about the mapping and waits to allocate physical memory until
11//! the first **page fault** on that region. When a page fault occurs, the
12//! kernel allocates and maps the required physical page.
13//! In other words, **page table entries are created only when accessed**.
14//!
15//! ## Page Fault in KeOS
16//!
17//! The main function responsible for handling page faults lies in
18//! [`Task::page_fault`]. This resolves the page fault reason into
19//! [`PageFaultReason`] by reading the `cr2`, which contains faulting address,
20//! and decoding the error code on the interrupt stack.
21//!
22//! It then delegates the page fault handling into the
23//! [`LazyPager::handle_page_fault`]. This method is responsible to look up the
24//! lazy mapping metadata recorded during the `mmap` and determine whether the
25//! fault is bogus fault or not. If the address is valid, it should allocate a
26//! new physical page and maps the page into page table. Otherwise, killing the
27//! current process by returning the [`KernelError`].
28//!
29//! ## [`VmAreaStruct`]
30//!
31//! The [`VmAreaStruct`] represents a range of virtual addresses that share the
32//! same memory permissions, similar to the Linux kernel's `struct
33//! vm_area_struct`. It serves as the core metadata structure for memory-mapped
34//! regions created via `mmap`, capturing the virtual range and the method
35//! for populating that region's contents on access.
36//!
37//! Each [`VmAreaStruct`] is associated with an implementation of the
38//! [`MmLoader`] trait, which defines how the contents of a page should be
39//! supplied when the region is accessed. This trait-based abstraction
40//! enables the kernel to support multiple types of memory mappings in a uniform
41//! way. For instance, file-backed mappings use a [`FileBackedLoader`], which
42//! reads contents from a file, while anonymous mappings use an [`AnonLoader`],
43//! which typically supplies zero-filled pages. Each loader implementation can
44//! maintain its own internal state, supporting extensibility and encapsulates
45//! the complexity of mapping behavior within each loader.
46//!
47//! The [`MmLoader`] trait provides a single method, `load`, which is called
48//! during demand paging when a page fault occurs at an address within the
49//! associated [`VmAreaStruct`]. The method must return a fully initialized
50//! [`Page`] object corresponding to that virtual address. The returned page is
51//! then mapped into the page table by the pager.
52//!
53//! This loader-based architecture provides a clean separation of concerns:
54//! [`VmAreaStruct`] tracks regions and permissions, while [`MmLoader`]
55//! encapsulates how pages are provisioned. This allows KeOS to support flexible
56//! and efficient memory models while maintaining clean abstractions.
57//!
58//! ## Implementation Requirements
59//! You need to implement the followings:
60//! - [`LazyPager`]
61//! - [`LazyPager::new`]
62//! - [`LazyPager::mmap`]
63//! - [`LazyPager::munmap`]
64//! - [`LazyPager::get_user_page`]
65//! - [`LazyPager::access_ok`]
66//! - [`PageFaultReason::is_demand_paging_fault`]
67//! - [`LazyPager::do_lazy_load`]
68//! - [`VmAreaStruct`]
69//! - [`FileBackedLoader`]
70//! - [`FileBackedLoader::load`]
71//!
72//! After implement the functionalities, move on to the next [`section`].
73//!
74//! [`section`]: mod@crate::fork
75//! [`EagerPager`]: ../../keos_project2/mmap/struct.EagerPager.html
76
77use alloc::sync::Arc;
78#[cfg(doc)]
79use keos::task::Task;
80use keos::{
81 KernelError,
82 addressing::Va,
83 fs::RegularFile,
84 mm::{Page, PageRef, page_table::Permission},
85 task::PFErrorCode,
86};
87use keos_project2::{page_table::PageTable, pager::Pager};
88
89/// A trait for loading the contents of a virtual memory page on demand.
90///
91/// This trait abstracts the mechanism for supplying the contents of a page
92/// during **demand paging**. It is used by a lazy pager when handling a
93/// page fault for a region that has not yet been populated.
94///
95/// Implementors of this trait can define custom behaviors, such as reading
96/// from a file, or zero-filling anonymous pages.
97pub trait MmLoader
98where
99 Self: Send + Sync,
100{
101 /// Loads and returns the content for the page at the given virtual address.
102 ///
103 /// The pager will call this function when a page fault occurs at `addr`
104 /// within the corresponding [`VmAreaStruct`]. This method must return a
105 /// fully initialized [`Page`] containing the data for that virtual page.
106 ///
107 /// # Parameters
108 /// - `addr`: The virtual address of the page to be loaded. This address is
109 /// guaranteed to lie within the virtual memory area associated with this
110 /// loader.
111 ///
112 /// # Returns
113 /// - A newly allocated [`Page`] containing the initialized data for the
114 /// page.
115 fn load(&self, addr: Va) -> Page;
116}
117
118/// A loader for anonymous memory regions.
119///
120/// [`AnonLoader`] is used for memory mappings that are not backed by any file.
121/// When a page fault occurs, this loader simply returns a newly allocated
122/// zero-filled [`Page`].
123pub struct AnonLoader {}
124impl MmLoader for AnonLoader {
125 /// Returns a zero-filled page for the given virtual address.
126 ///
127 /// Since anonymous memory is not backed by any persistent source, this
128 /// implementation always returns a freshly zero-initialized [`Page`].
129 fn load(&self, _addr: Va) -> Page {
130 Page::new()
131 }
132}
133
134/// A loader for file-backed memory regions.
135///
136/// [`FileBackedLoader`] is used for memory mappings backed by files, such as
137/// when `mmap` is called with a regular file. This loader reads data from
138/// the underlying file starting at a specific offset and returns it in a
139/// newly allocated [`Page`].
140///
141/// The offset within the file is determined based on the virtual address
142/// passed to `load`, relative to the mapping’s start address and offset.
143///
144/// This loader handles partial page reads and fills any unread bytes with
145/// zeroes.
146pub struct FileBackedLoader {
147 // TODO: Define any member you need.
148}
149
150impl MmLoader for FileBackedLoader {
151 /// Loads a page from the file based on the given virtual address.
152 ///
153 /// This implementation calculates the offset within the file and reads
154 /// up to one page of data into memory. If the read returns fewer than
155 /// `PAGE_SIZE` bytes, the remainder of the page is zero-filled.
156 fn load(&self, addr: Va) -> Page {
157 todo!()
158 }
159}
160
161/// Represents a memory-mapped region within a process's virtual address space,
162/// corresponding to the Linux kernel's `struct vm_area_struct`.
163///
164/// Each [`VmAreaStruct`] corresponds to a contiguous region with a specific
165/// mapping behavior—e.g., anonymous memory or file-backed memory.
166/// This abstraction allows the pager to defer the actual page population
167/// until the memory is accessed, using demand paging.
168///
169/// The key component is the [`MmLoader`], which defines how to load
170/// the contents of a page when it is accessed (e.g., reading from a file
171/// or zero-filling the page).
172#[derive(Clone)]
173pub struct VmAreaStruct {
174 /// A handle to the memory loader for this region.
175 ///
176 /// The [`MmLoader`] defines how to populate pages in this VMA during
177 /// lazy loading. The loader must be thread-safe and cloneable.
178 pub loader: Arc<dyn MmLoader>,
179 // TODO: Define any member you need.
180}
181
182/// The [`LazyPager`] structure implements lazy paging, where memory pages are
183/// mapped only when accessed (on page fault), instead of during `mmap` calls.
184#[derive(Clone)]
185pub struct LazyPager {
186 // TODO: Define any member you need.
187}
188
189impl Pager for LazyPager {
190 /// Creates a new instance of [`LazyPager`].
191 ///
192 /// This constructor initializes an empty [`LazyPager`] struct.
193 fn new() -> Self {
194 LazyPager {
195 // TODO: Initialize any member you need.
196 }
197 }
198
199 /// Memory map function (`mmap`) for lazy paging.
200 ///
201 /// This function creates the metadata for memory mappings, and delegate the
202 /// real mappings on page fault.
203 ///
204 /// Returns an address for the mapped area.
205 fn mmap(
206 &mut self,
207 _page_table: &mut PageTable,
208 addr: Va,
209 size: usize,
210 prot: Permission,
211 file: Option<&RegularFile>,
212 offset: usize,
213 ) -> Result<usize, KernelError> {
214 todo!()
215 }
216
217 /// Memory unmap function (`munmap`) for lazy paging.
218 ///
219 /// This function would unmap a previously mapped memory region, releasing
220 /// any associated resources.
221 ///
222 /// # Returns
223 /// - Zero (if succeed) or an error ([`KernelError`]).
224 fn munmap(&mut self, page_table: &mut PageTable, addr: Va) -> Result<usize, KernelError> {
225 todo!()
226 }
227
228 /// Find a mapped page at the given virtual address. If the page for addr is
229 /// not loaded, load it and then returns.
230 ///
231 /// This function searches for a memory page mapped at `addr` and, if found,
232 /// returns a tuple of [`PageRef`] to the page and its corresponding
233 /// [`Permission`] flags.
234 ///
235 /// # Parameters
236 /// - `addr`: The virtual address ([`Va`]) of the page to find.
237 ///
238 /// # Returns
239 /// - `Some(([`PageRef`], [`Permission`]))`: If the page is found.
240 /// - `None`: If no mapped page is found at `addr`.
241 fn get_user_page(
242 &mut self,
243 page_table: &mut PageTable,
244 addr: Va,
245 ) -> Option<(PageRef<'_>, Permission)> {
246 todo!()
247 }
248
249 /// Checks whether access to the given virtual address is permitted.
250 ///
251 /// This function verifies that a virtual address `va` is part of a valid
252 /// memory mapping and that the requested access type (read or write) is
253 /// allowed by the page's protection flags. Note that this does not trigger
254 /// the demand paging.
255 fn access_ok(&self, va: Va, is_write: bool) -> bool {
256 todo!()
257 }
258}
259
260/// Represents the reason for a page fault in a virtual memory system.
261///
262/// This struct is used to capture various details about a page fault, including
263/// the faulting address, the type of access that caused the fault (read or
264/// write).
265#[derive(Debug)]
266pub struct PageFaultReason {
267 /// The address that caused the page fault.
268 ///
269 /// This is the virtual address that the program attempted to access when
270 /// the page fault occurred. It can be useful for debugging and
271 /// identifying the location in memory where the fault happened.
272 pub fault_addr: Va,
273
274 /// Indicates whether the fault was due to a write access violation.
275 ///
276 /// A value of `true` means that the program attempted to write to a page
277 /// that was marked as read-only or otherwise restricted from write
278 /// access. A value of `false` indicates that the access was a read
279 /// or the page allowed write access.
280 pub is_write_access: bool,
281
282 /// Indicates whether the page that caused the fault is present in memory.
283 ///
284 /// A value of `true` means that the page is currently loaded into memory,
285 /// and the fault may have occurred due to other conditions, such as
286 /// protection violations. A value of `false` means the page is not present
287 /// in memory (e.g., the page might have been swapped out or mapped as a
288 /// non-resident page).
289 pub is_present: bool,
290}
291
292impl PageFaultReason {
293 /// Probe the cause of page fault into a [`PageFaultReason`].
294 ///
295 /// This function decodes a hardware-provided [`PFErrorCode`],
296 /// generated by the CPU when a page fault occurs, into a structured
297 /// [`PageFaultReason`] that the kernel can interpret.
298 ///
299 /// The decoded information includes:
300 /// - The type of access (`is_write_access`),
301 /// - Whether the faulting page is currently mapped (`is_present`),
302 /// - The faulting virtual address (`fault_addr`).
303 pub fn new(ec: PFErrorCode, cr2: Va) -> Self {
304 PageFaultReason {
305 fault_addr: cr2,
306 is_write_access: ec.contains(PFErrorCode::WRITE_ACCESS),
307 is_present: ec.contains(PFErrorCode::PRESENT),
308 }
309 }
310
311 /// Returns `true` if the fault is due to **demand paging**.
312 ///
313 /// # Returns
314 /// - `true` if this fault was caused by an demand paging.
315 /// - `false` otherwise.
316 #[inline]
317 pub fn is_demand_paging_fault(&self) -> bool {
318 todo!()
319 }
320}
321
322impl LazyPager {
323 /// Handles a page fault by performing **lazy loading** of the faulting
324 /// page.
325 ///
326 /// This method is invoked when a page fault occurs due to **demand
327 /// paging**— that is, when a program accesses a virtual address that is
328 /// validly mapped but not yet backed by a physical page. This function
329 /// allocates and installs the corresponding page into the page table on
330 /// demand.
331 ///
332 /// The kernel may initialize the page from a file (if the mapping was
333 /// file-backed) or zero-fill it (if anonymous). The newly loaded page
334 /// must also be mapped with the correct permissions, as defined at the
335 /// time of the original `mmap`.
336 ///
337 /// # Parameters
338 /// - `page_table`: The page table of the faulting process.
339 /// - `reason`: The [`PageFaultReason`] that describes the faulting reason.
340 ///
341 /// This must indicate a **demand paging fault**.
342 ///
343 /// # Returns
344 /// - `Ok(())` if the page was successfully loaded and mapped.
345 /// - `Err(KernelError)`: If the faulting address is invalid, out of bounds,
346 /// or if page allocation fails.
347 pub fn do_lazy_load(
348 &mut self,
349 page_table: &mut PageTable,
350 reason: &PageFaultReason,
351 ) -> Result<(), KernelError> {
352 todo!()
353 }
354
355 /// Handles a **page fault** by allocating a physical page and updating the
356 /// page table.
357 ///
358 /// This function is called when a process accesses a lazily mapped page
359 /// that has not been allocated yet. The function must:
360 /// 1. Identify the faulting virtual address from [`PageFaultReason`].
361 /// 2. Check if the address was previously recorded in `mmap` metadata.
362 /// 3. Allocate a new physical page.
363 /// 4. Update the page table with the new mapping.
364 /// 5. Invalidate the TLB entry to ensure memory consistency.
365 ///
366 /// # Arguments
367 /// - `page_table`: Mutable reference to the page table.
368 /// - `reason`: The cause of the page fault, including the faulting address.
369 ///
370 /// If the faulting address was not mapped via `mmap`, the system should
371 /// trigger a **segmentation fault**, resulting process exit.
372 pub fn handle_page_fault(
373 &mut self,
374 page_table: &mut PageTable,
375 reason: &PageFaultReason,
376 ) -> Result<(), KernelError> {
377 if reason.is_demand_paging_fault() {
378 self.do_lazy_load(page_table, reason)
379 } else if reason.is_cow_fault() {
380 self.do_copy_on_write(page_table, reason)
381 } else {
382 Err(KernelError::InvalidAccess)
383 }
384 }
385}