keos_project2/
mm_struct.rs

1//! # Memory State of a process
2//!
3//! In the project 1, you implemented the file state management for a
4//! process. An equally important state of a process is memory state.
5//! This memory state involves managing virtual memory regions, tracking memory
6//! allocations, and implementing memory deallocation when memory is no longer
7//! needed. The operating system must track all active mappings per process,
8//! enforce correct access permissions, and ensure proper cleanup when memory is
9//! unmapped.
10//!
11//! ## Memory in KeOS
12//!
13//! The state of a process's memory is represented by the [`MmStruct`]
14//! structure, similiar to the Linux kernel's `struct mm_struct`. Each process
15//! maintains an [`MmStruct`] instance, which tracks the memory mapping state
16//! for a process. This state plays a central role for serving a memory mapping
17//! system calls: **mmap** and **munmap**.
18//!
19//! Memory mapping (`mmap`) allows processes to allocate, share, or access
20//! memory in a flexible way. It is commonly used for:
21//! - Allocating memory dynamically without relying on heap or stack growth.
22//! - Mapping files into memory for fast access.
23//! - Sharing memory between processes.
24//!
25//! The `mmap` system call establishes a mapping between a process's virtual
26//! address space and either physical memory or a file. These memory mappings
27//! are recorded within the **page table**, similar to how regular memory pages
28//! are managed. However, unlike normal heap allocations, `mmap`-based
29//! allocations allow more control over page protection, mapping policies, and
30//! address alignment. When a process accesses an address within the mapped
31//! region, a **page fault** occurs, triggering the operating system to allocate
32//! and map the corresponding physical pages.
33//!
34//! The [`MmStruct`] contains the two key components:
35//! - **Page Table**: Tracks mappings between virtual and physical addresses.
36//! - **Pager**: Defines the policy for memory mapping. and unmapping.`
37//!
38//! ### Validating User Input
39//!
40//! One of the important aspects of managing memory safely is ensuring that
41//! user input, such as memory addresses provided by system calls, is validated
42//! correctly. **The kernel must never crash due to user input.**
43//!
44//! Many system calls, like `read` and `write`, rely on user-supplied memory
45//! addresses—typically as buffer pointers. These addresses need to be carefully
46//! validated before they can be used to ensure the integrity and stability of
47//! the system.
48//!
49//! Without proper validation, several serious problems may arise:
50//! - **Unauthorized access to kernel memory**: If the user is allowed to access
51//!   kernel memory, this can lead to privilege escalation vulnerabilities,
52//!   potentially giving the user more control than intended.
53//! - **Dereferencing invalid pointers**: Accessing uninitialized or incorrectly
54//!   mapped memory can result in segmentation faults or undefined behavior,
55//!   leading to crashes or unexpected outcomes.
56//! - **Memory corruption**: Improper handling of memory can result in
57//!   corrupting the system's memory state, which can affect other processes,
58//!   crash the kernel, or destabilize the entire system.
59//!
60//! [`MmStruct`] mitigate these risks with the [`MmStruct::access_ok`] method,
61//! which ensures that the memory addresses provided by system calls are **valid
62//! and safe** before being used. This validation mechanism will
63//! prevent dangerous operations by checking if memory access is allowed, based
64//! on the current process's memory layout and protection policies.
65//!
66//! By incorporating proper validation, [`MmStruct::access_ok`] ensures that
67//! invalid memory accesses are handled **gracefully**, returning appropriate
68//! errors rather than causing kernel panics or undefined behavior. This
69//! validation mechanism plays a crucial role in maintaining system integrity
70//! and protecting the kernel from potential vulnerabilities.
71
72//! ### `Pager`
73//!
74//! The actual behavior of [`MmStruct`] lies in the [`Pager`] trait.
75//! When a user program invokes those system calls, the [`MmStruct`] parses the
76//! arguements from the [`SyscallAbi`] and forwarded them to an implementation
77//! of the [`Pager`] traits, which provides the core interface for handling
78//! memory mappings. Similarly, the core implementation to validate memory also
79//! lies on the [`Pager::access_ok`].
80//!
81//! ## Implementation Requirements
82//! You need to implement the followings:
83//! - [`MmStruct::mmap`]
84//! - [`MmStruct::munmap`]
85//! - [`MmStruct::access_ok`]
86//!
87//! After implementing them, move on to the next [`section`] to implement
88//! paging policy, called `EagerPager`.
89//!
90//! [`section`]: crate::eager_pager
91
92use crate::{page_table::PageTable, pager::Pager};
93use core::ops::Range;
94use keos::{
95    KernelError,
96    addressing::Va,
97    fs::RegularFile,
98    mm::{PageRef, page_table::Permission},
99};
100use keos_project1::{file_struct::FileStruct, syscall::SyscallAbi};
101
102/// The [`MmStruct`] represents the memory state for a specific process,
103/// corresponding to the Linux kernel's `struct mm_struct`.
104///
105/// This struct encapsulates the essential information required to manage
106/// a process's virtual memory, including its page table and the pager
107/// responsible for handling memory mapping operations (such as `mmap` and
108/// `munmap`).
109///
110/// The [`MmStruct`] ensures that memory-related system calls and operations are
111/// correctly applied within the process’s address space. It provides mechanisms
112/// to allocate, map, and unmap memory pages, and serves as the interface
113/// through which the OS kernel interacts with the user process’s memory layout.
114///
115/// # Memory State
116///
117/// The memory state includes the page table (referenced by `page_table_addr`)
118/// that manages the virtual-to-physical address translations for the process,
119/// and a pager (`pager`) that defines how memory-mapped files and dynamic
120/// memory allocations are handled. Together, these components allow each
121/// process to maintain its own isolated memory environment, supporting both
122/// memory protection and efficient address space management.
123///
124/// Like its Linux counterpart, [`MmStruct`] plays a central role in memory
125/// management, providing the kernel with per-process control over virtual
126/// memory.
127pub struct MmStruct<P: Pager> {
128    /// The page table that maintains mappings between virtual and physical
129    /// addresses.
130    pub page_table: PageTable,
131
132    /// The pager that handles memory allocation (`mmap`) and deallocation
133    /// (`munmap`).
134    pub pager: P,
135}
136
137impl<P: Pager> Default for MmStruct<P> {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl<P: Pager> MmStruct<P> {
144    /// Creates a new [`MmStruct`] with an empty page table and a new pager
145    /// instance.
146    ///
147    /// # Returns
148    /// - A new [`MmStruct`] instance initialized with a new [`PageTable`] and
149    ///   `P::new()`.
150    pub fn new() -> Self {
151        Self {
152            page_table: PageTable::new(),
153            pager: P::new(), // Initialize the pager.
154        }
155    }
156
157    // Check whether a given memory range is accessible by the process.
158    ///
159    /// This function ensures that system calls using memory addresses (such as
160    /// `read`, `write`, etc.) operate only on **valid and accessible**
161    /// memory regions.
162    ///
163    /// # Parameters
164    /// - `addr`: A range of virtual addresses to be accessed.
165    /// - `is_write`: `true` if the memory is being written to, `false` if it's
166    ///   only being read.
167    ///
168    /// # Returns
169    /// - `true` if the memory range is valid.
170    /// - `false` if the memory range is invalid or inaccessible.
171    pub fn access_ok(&self, addr: Range<Va>, is_write: bool) -> bool {
172        todo!()
173    }
174
175    /// Wrapper function for the pager's `mmap` method. It delegates the actual
176    /// memory mapping operation to the pager's `mmap` method.
177    ///
178    /// # Parameters
179    /// - `fstate`: A mutable reference to the file state.
180    /// - `abi`: The system call ABI, which contains the arguments for the
181    ///   system call.
182    ///
183    /// # Returns
184    /// - The result of the memory mapping operation, returned by the pager's
185    ///   `mmap`.
186    pub fn do_mmap(
187        &mut self,
188        addr: Va,
189        size: usize,
190        prot: Permission,
191        file: Option<&RegularFile>,
192        offset: usize,
193    ) -> Result<usize, KernelError> {
194        // Calls the real implementation in pager.
195        let Self { page_table, pager } = self;
196        pager.mmap(page_table, addr, size, prot, file, offset)
197    }
198
199    /// Maps a file into the process's virtual address space.
200    ///
201    /// This function implements the `mmap` system call, which maps either an
202    /// anonymous mapping (fd = -1) or portion of a file into memory (fd >= 0).
203    /// If the mapped region represent a file content, user programs can
204    /// access the file contents as the part of the process’s memory.
205    ///
206    /// # Syscall API
207    /// ```c
208    /// void *mmap(void *addr, size_t length, int prot, int fd, off_t offset);
209    /// ```
210    /// - `addr`: Desired starting address of the mapping (must be page-aligned
211    ///   and non-zero).
212    /// - `length`: Number of bytes to map (must be non-zero).
213    /// - `prot`: Desired memory protection flags.
214    /// - `fd`: File descriptor of the file to be mapped.
215    /// - `offset`: Offset in the file where mapping should begin.
216    ///
217    /// # Arguments
218    ///
219    /// * `fstate` - Mutable reference to the current file state.
220    /// * `abi` - A reference to the system call arguments, including the file
221    ///   descriptor, mapping length, protection flags, and file offset.
222    ///
223    /// # Behavior
224    ///
225    /// This function performs validation on the provided arguments before
226    /// forwarding the request to the pager’s `mmap` method. The following
227    /// conditions must be met:
228    ///
229    /// - `addr` must be non-zero and page-aligned.
230    /// - `length` must be non-zero.
231    /// - The file descriptor must refer to a regular file or -1 for anonymous
232    ///   mapping.
233    /// - The mapping must not overlap with any already mapped region, including
234    ///   the user stack or any memory occupied by the program binary.
235    ///
236    /// Unlike Linux, KeOS does not support automatic address selection for
237    /// `addr == NULL`, so `mmap` fails if `addr` is zero.
238    ///
239    /// If the length of the file is not a multiple of the page size, any excess
240    /// bytes in the final page are zero-filled.
241    ///
242    /// # Returns
243    ///
244    /// Returns the starting virtual address of the mapped region on success, or
245    /// a [`KernelError`] on failure due to invalid arguments or conflicts with
246    /// existing memory mappings.
247    pub fn mmap(
248        &mut self,
249        fstate: &mut FileStruct,
250        abi: &SyscallAbi,
251    ) -> Result<usize, KernelError> {
252        self.do_mmap(todo!(), todo!(), todo!(), todo!(), todo!())
253    }
254
255    /// Unmaps a memory-mapped file region.
256    ///
257    /// This function implements the `munmap` system call, which removes a
258    /// previously established memory mapping created by `mmap`. It releases
259    /// the virtual memory associated with the mapping.
260    ///
261    /// # Syscall API
262    /// ```c
263    /// int munmap(void *addr);
264    /// ```
265    /// - `addr`: The starting virtual address of the mapping to unmap. This
266    ///   must match the address returned by a previous call to `mmap` by the
267    ///   same process and must not have been unmapped already.
268    ///
269    /// # Arguments
270    ///
271    /// * `fstate` - Mutable reference to the current file state.
272    /// * `abi` - A reference to the system call arguments, including the
273    ///   address to unmap.
274    ///
275    /// # Behavior
276    ///
277    /// - Unmaps the virtual memory region starting at `addr` that was
278    ///   previously mapped via `mmap`.
279    /// - Unmodified pages are simply discarded.
280    /// - The virtual pages corresponding to the mapping are removed from the
281    ///   process's address space.
282    ///
283    /// # Additional Notes
284    ///
285    /// - Calling `close` on a file descriptor or removing the file from the
286    ///   filesystem does **not** unmap any of its active mappings.
287    /// - To follow the Unix convention, mappings remain valid until they are
288    ///   explicitly unmapped via `munmap`.
289    ///
290    /// # Returns
291    ///
292    /// Returns `Ok(0)` on success or a [`KernelError`] if the address is
293    /// invalid or does not correspond to an active memory mapping.
294    pub fn munmap(&mut self, abi: &SyscallAbi) -> Result<usize, KernelError> {
295        // Calls the pager's munmap method with placeholders for arguments.
296        self.pager.munmap(&mut self.page_table, todo!())
297    }
298
299    /// Find a mapped page at the given virtual address and apply a function to
300    /// it.
301    ///
302    /// This function searches for a memory page mapped at `addr` and, if found,
303    /// applies the provided function `f` to it. The function `f` receives a
304    /// [`PageRef`] to the page and its corresponding [`Permission`] flags.
305    ///
306    /// # Parameters
307    /// - `addr`: The virtual address ([`Va`]) of the page to find.
308    /// - `f`: A closure that takes a [`PageRef`] and its [`Permission`] flags,
309    ///   returning a value of type `R`.
310    ///
311    /// # Returns
312    /// - `Some(R)`: If the page is found, the function `f` is applied, and its
313    ///   result is returned.
314    /// - `None`: If no mapped page is found at `addr`.
315    ///
316    /// # Usage
317    /// This method allows safe access to a mapped page. It is useful for
318    /// performing read operations or permission checks on a page.
319    ///
320    /// # Safety
321    /// - The rust's lifetime guarantees that the closure `f` never stores the
322    ///   [`PageRef`] beyond its invocation.
323    pub fn get_user_page_and<R>(
324        &mut self,
325        addr: Va,
326        f: impl FnOnce(PageRef, Permission) -> R,
327    ) -> Result<R, KernelError> {
328        let Self { page_table, pager } = self;
329        if let Some((pgref, perm)) = pager.get_user_page(page_table, addr) {
330            Ok(f(pgref, perm))
331        } else {
332            Err(KernelError::BadAddress)
333        }
334    }
335}