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}