keos_project3/
lib.rs

1//! # Project 3: Advanced Memory Management
2//!
3//! In Project 3, you will expand KeOS's memory subsystem by implementing more
4//! sophisticated techniques to manage memory efficiently. The focus will be on
5//! two key features:
6//!
7//! - **Lazy Paging**: A strategy that delays allocation until a page is
8//!   actually accessed, reducing memory usage and improving performance.
9//!
10//! - **Fork with Copy-On-Write**: An optimized process duplication mechanism
11//!   where memory pages are initially shared between the parent and child
12//!   processes, and only duplicated when a write occurs.
13//!
14//! Together, these mechanisms are essential for building a modern, efficient
15//! operating system that handles memory-intensive workloads effectively.
16//!
17//! ## Getting Started
18//!
19//! To get started, navigate to the `keos-project3/grader` directory and run:
20//!
21//! ```bash
22//! $ cargo run
23//! ```
24//!
25//! ## Modifiable Files
26//! In this project, you can modify the following two files:
27//! - `lazy_pager.rs`
28//! - `fork.rs`
29//!
30//! ## Project Outline
31//!
32//! - [`Lazy Paging`]: Implement demand paging, deferring memory allocation and
33//!   actual page loading until the first access triggers a page fault.
34//!
35//! - [`Fork`]: Implement the `fork` system call using Copy-on-Write (COW),
36//!   allowing processes to share memory pages efficiently until one attempts to
37//!   modify them.
38//!
39//! [`Lazy Paging`]: lazy_pager
40//! [`Fork`]: mod@crate::fork
41
42#![no_std]
43#![no_main]
44#![deny(rustdoc::broken_intra_doc_links)]
45
46extern crate alloc;
47#[allow(unused_imports)]
48#[macro_use]
49extern crate keos;
50
51pub mod fork;
52pub mod get_phys;
53pub mod lazy_pager;
54pub mod process;
55
56use alloc::boxed::Box;
57use core::ops::Range;
58use fork::fork;
59use keos::{
60    KernelError,
61    addressing::{Pa, Va},
62    syscall::Registers,
63    task::PFErrorCode,
64    task::Task,
65    thread::{Current, ThreadBuilder, with_current},
66};
67use keos_project1::syscall::SyscallAbi;
68use keos_project2::mm_struct::MmStruct;
69#[cfg(doc)]
70use lazy_pager::LazyPager;
71use lazy_pager::PageFaultReason;
72pub use process::Process;
73
74/// Represents system call numbers used in project3.
75///
76/// Each variant corresponds to a specific system call that can be invoked
77/// using the system call interface. The numeric values align with the
78/// syscall table in the operating system.
79#[repr(usize)]
80#[derive(Debug)]
81pub enum SyscallNumber {
82    /// Terminates the calling process.
83    Exit = 0,
84    /// Opens a file and returns a file descriptor.
85    Open = 1,
86    /// Reads data from a file descriptor.
87    Read = 2,
88    /// Writes data to a file descriptor.
89    Write = 3,
90    /// Moves the file offset of an open file.
91    Seek = 4,
92    /// Retrieves the current file offset.
93    Tell = 5,
94    /// Closes an open file descriptor.
95    Close = 6,
96    /// Create an interprocess communication channel.
97    Pipe = 7,
98    /// Map the memory.
99    Mmap = 8,
100    /// Unmap the memory.
101    Munmap = 9,
102    /// Fork the process.
103    Fork = 10,
104    /// Get Physical Address of Page (for grading purposes only)
105    GetPhys = 0x81,
106}
107
108impl TryFrom<usize> for SyscallNumber {
109    type Error = KernelError;
110    fn try_from(no: usize) -> Result<SyscallNumber, Self::Error> {
111        match no {
112            0 => Ok(SyscallNumber::Exit),
113            1 => Ok(SyscallNumber::Open),
114            2 => Ok(SyscallNumber::Read),
115            3 => Ok(SyscallNumber::Write),
116            4 => Ok(SyscallNumber::Seek),
117            5 => Ok(SyscallNumber::Tell),
118            6 => Ok(SyscallNumber::Close),
119            7 => Ok(SyscallNumber::Pipe),
120            8 => Ok(SyscallNumber::Mmap),
121            9 => Ok(SyscallNumber::Munmap),
122            10 => Ok(SyscallNumber::Fork),
123            0x81 => Ok(SyscallNumber::GetPhys),
124            _ => Err(KernelError::NoSuchSyscall),
125        }
126    }
127}
128
129impl Task for Process {
130    /// Handles a system call request from a user program.
131    ///
132    /// This function is the entry point for system call handling. It retrieves
133    /// the system call ABI from the provided [`Registers`] structure, which
134    /// includes the system call number and its arguments. Based on the
135    /// system call number (`sysno`), it looks up the appropriate handler
136    /// function in a predefined list. If a handler is found, it is invoked
137    /// with the ABI, otherwise, an error ([`KernelError::NoSuchSyscall`]) is
138    /// returned.
139    ///
140    /// After the handler function processes the system call, the return value
141    /// (either a success or error) is set back into the CPU registers via
142    /// the `set_return_value` method. The return value is stored in the `%rax`
143    /// register as per the x86-64 system call convention.
144    ///
145    /// # Parameters
146    ///
147    /// - `regs`: A mutable reference to the [`Registers`] struct, which
148    ///   contains the current state of the CPU registers. This structure will
149    ///   be used to retrieve the system call number and its arguments, and also
150    ///   to set the return value.
151    ///
152    /// # Functionality
153    ///
154    /// The function processes the system call as follows:
155    /// 1. Extracts the system call number and arguments using the
156    ///    [`SyscallAbi::from_registers`].
157    /// 2. Looks up the corresponding handler function from a predefined list of
158    ///    system calls. The handler function is selected based on the system
159    ///    call number (`sysno`).
160    /// 3. If a handler is found, it is executed with the ABI as an argument. If
161    ///    no handler is found, the function returns a
162    ///    [`KernelError::NoSuchSyscall`] error.
163    ///
164    /// The result of the system call handler (either success or error) is then
165    /// returned via the [`SyscallAbi::set_return_value`] method, which
166    /// modifies the CPU registers accordingly.
167    fn syscall(&mut self, regs: &mut Registers) {
168        // ** YOU DON'T NEED TO CHANGE THIS FUNCTION **
169        let abi = SyscallAbi::from_registers(regs); // Extract ABI from the registers.
170        // Lookup the system call handler function based on the system call number.
171        let return_val = SyscallNumber::try_from(abi.sysno).and_then(|no| match no {
172            SyscallNumber::Exit => self.exit(&abi),
173            SyscallNumber::Open => self.file_struct.open(&abi),
174            SyscallNumber::Read => self.file_struct.read(&abi),
175            SyscallNumber::Write => self.file_struct.write(&abi),
176            SyscallNumber::Seek => self.file_struct.seek(&abi),
177            SyscallNumber::Tell => self.file_struct.tell(&abi),
178            SyscallNumber::Close => self.file_struct.close(&abi),
179            SyscallNumber::Pipe => self.file_struct.pipe(&abi),
180            SyscallNumber::Mmap => self.mm_struct.mmap(&mut self.file_struct, &abi),
181            SyscallNumber::Munmap => self.mm_struct.munmap(&abi),
182            SyscallNumber::Fork => fork(
183                &mut self.file_struct,
184                &mut self.mm_struct,
185                &abi,
186                |file_struct, mm_struct| {
187                    with_current(|th| {
188                        ThreadBuilder::new(&th.name).attach_task(Box::new(Process {
189                            file_struct,
190                            mm_struct,
191                        }))
192                    })
193                },
194            ),
195            SyscallNumber::GetPhys => get_phys::get_phys(&self.mm_struct, &self.file_struct, &abi),
196        });
197        // Set the return value of the system call (success or error) back into the
198        // registers.
199        abi.set_return_value(return_val);
200    }
201
202    /// Validates whether the given memory range is accessible for the process.
203    ///
204    /// This function checks if a memory region is safe to read or write before
205    /// performing a memory-related operation. It ensures that user-provided
206    /// addresses do not access restricted memory regions, preventing
207    /// potential security vulnerabilities or crashes.
208    ///
209    /// # Parameters
210    /// - `addr`: A range of virtual addresses to be accessed.
211    /// - `is_write`: `true` if the access involves writing to memory, `false`
212    ///   for read-only access.
213    ///
214    /// # Returns
215    /// - `true` if the memory range is valid and accessible.
216    /// - `false` if access is denied due to invalid address range or
217    ///   insufficient permissions.
218    fn access_ok(&self, addr: Range<Va>, is_write: bool) -> bool {
219        // Delegate the validation to the memory management system.
220        self.mm_struct.access_ok(addr, is_write)
221    }
222
223    /// Handles a page fault by acquiring the memory state lock and calling
224    /// [`LazyPager::handle_page_fault`].
225    ///
226    /// This function is typically invoked when a process accesses a memory
227    /// region that has been lazily mapped but not yet backed by a physical
228    /// page. It performs the following steps:
229    /// 1. Locks the `mm_state` to ensure thread-safe access to the process's
230    ///    memory state.
231    /// 2. Calls [`LazyPager::handle_page_fault`], passing the locked `mm_state`
232    ///    and fault reason.
233    /// 3. The [`LazyPager::handle_page_fault`] function will allocate a
234    ///    physical page and update the page table.
235    fn page_fault(&mut self, ec: PFErrorCode, cr2: Va) {
236        let reason = PageFaultReason::new(ec, cr2);
237
238        // Delegate the fault handling to [`LazyPager::handle_page_fault`],
239        // which will update the page table and allocate a physical page if necessary.
240        let MmStruct { page_table, pager } = &mut self.mm_struct;
241        if pager.handle_page_fault(page_table, &reason).is_err() {
242            Current::exit(-1)
243        }
244    }
245
246    fn with_page_table_pa(&self, f: &fn(Pa)) {
247        f(self.mm_struct.page_table.pa())
248    }
249}