keos_project2/lib.rs
1//! # Project 2: Memory Management
2//!
3//! In Project 2, you will expand your operating system with memory management
4//! system, forming the isolation boundary between user applications and the
5//! kenrel. This project builds upon the concepts from Project 1 and introduces
6//! key topics such as **page tables**, **memory state management**, and **user
7//! program loading**. By the end of this project, your kernel will be capable
8//! of running multiple user programs in an isolated, virtual memory
9//! environment.
10//!
11//! ## Getting Started
12//!
13//! To begin, navigate to the `keos-project2/grader` directory and run:
14//!
15//! ```bash
16//! $ cargo run
17//! ```
18//!
19//! ## Modifiable Files
20//! In this project, you can modify the following six files:
21//! - `page_table.rs`
22//! - `mm_struct.rs`
23//! - `eager_pager.rs`
24//! - `loader/mod.rs`
25//! - `loader/elf.rs`
26//! - `loader/stack_builder.rs`
27//!
28//! ## Project Outline
29//!
30//! This project consists of three major components:
31//!
32//! - [`Page Table`]: Implement an x86_64 page table mechanism to manage virtual
33//! memory and perform address translation for user processes.
34//!
35//! - [`Memory State`]: Develop memory state management and implement system
36//! calls for dynamic memory allocation and deallocation.
37//!
38//! - [`User Program Loader`]: Build a loader that loads ELF executables into
39//! memory, sets up the stack, and transitions into user mode execution.
40//!
41//! Successfully completing this project will enable KeOS to run simple C
42//! programs, setting the stage for more complex user-space features in future
43//! projects.
44//!
45//! ## Debugging a User Process
46//!
47//! Because KeOS's internal backtrace only support for kernel's virtual
48//! addresses, user program's instruction address may be shown as unknown.
49//! For such cases, you may utilize `addr2line` utility to see which user mode
50//! codes are responsible for the error.
51//! For example, if you get the following error message:
52//!
53//! ```plaintext
54//! User process ABORT at sys_mmap_err.c:14 in main(): assertion `write(1, NULL, 0x1000) < 0' failed.
55//! Call stack: 0x402a83 0x401100 0x402a02
56//! The `addr2line' program can make call stacks useful.
57//! Read "Debugging a User Process" chapter in the
58//! KeOS documentation for more information.
59//! ```
60//!
61//! You translate the address to respective source code's line by passing the
62//! program's name and address to `addr2line` utility:
63//!
64//! ```bash
65//! $ addr2line -e sys_mmap_err 0x402a83
66//! ../../kelibc/debug.c:29
67//! $ addr2line -e sys_mmap_err 0x401100
68//! userprog/sys_mmap_err.c:16
69//! ```
70//!
71//!
72//! [`Page Table`]: page_table
73//! [`Memory State`]: mm_struct
74//! [`User Program Loader`]: loader
75
76#![no_std]
77#![no_main]
78#![deny(rustdoc::broken_intra_doc_links)]
79
80extern crate alloc;
81#[allow(unused_imports)]
82#[macro_use]
83extern crate keos;
84
85pub mod eager_pager;
86pub mod loader;
87pub mod mm_struct;
88pub mod page_table;
89pub mod pager;
90pub mod process;
91
92use core::ops::Range;
93use keos::{
94 KernelError,
95 addressing::{Pa, Va},
96 syscall::Registers,
97 task::Task,
98};
99use keos_project1::syscall::SyscallAbi;
100
101pub use process::Process;
102
103/// Represents system call numbers used in project2
104///
105/// Each variant corresponds to a specific system call that can be invoked
106/// using the system call interface. The numeric values align with the
107/// syscall table in the operating system.
108#[repr(usize)]
109pub enum SyscallNumber {
110 /// Terminates the calling process.
111 Exit = 0,
112 /// Opens a file and returns a file descriptor.
113 Open = 1,
114 /// Reads data from a file descriptor.
115 Read = 2,
116 /// Writes data to a file descriptor.
117 Write = 3,
118 /// Moves the file offset of an open file.
119 Seek = 4,
120 /// Retrieves the current file offset.
121 Tell = 5,
122 /// Closes an open file descriptor.
123 Close = 6,
124 /// Create an interprocess communication channel.
125 Pipe = 7,
126 /// Map the memory.
127 Mmap = 8,
128 /// Unmap the memory.
129 Munmap = 9,
130}
131
132impl TryFrom<usize> for SyscallNumber {
133 type Error = KernelError;
134 fn try_from(no: usize) -> Result<SyscallNumber, Self::Error> {
135 match no {
136 0 => Ok(SyscallNumber::Exit),
137 1 => Ok(SyscallNumber::Open),
138 2 => Ok(SyscallNumber::Read),
139 3 => Ok(SyscallNumber::Write),
140 4 => Ok(SyscallNumber::Seek),
141 5 => Ok(SyscallNumber::Tell),
142 6 => Ok(SyscallNumber::Close),
143 7 => Ok(SyscallNumber::Pipe),
144 8 => Ok(SyscallNumber::Mmap),
145 9 => Ok(SyscallNumber::Munmap),
146 _ => Err(KernelError::NoSuchSyscall),
147 }
148 }
149}
150
151impl Task for Process {
152 /// Handles a system call request from a user program.
153 ///
154 /// This function is the entry point for system call handling. It retrieves
155 /// the system call ABI from the provided [`Registers`] structure, which
156 /// includes the system call number and its arguments. Based on the
157 /// system call number (`sysno`), it looks up the appropriate handler
158 /// function in a predefined list. If a handler is found, it is invoked
159 /// with the ABI, otherwise, an error ([`KernelError::NoSuchSyscall`]) is
160 /// returned.
161 ///
162 /// After the handler function processes the system call, the return value
163 /// (either a success or error) is set back into the CPU registers via
164 /// the `set_return_value` method. The return value is stored in the `%rax`
165 /// register as per the x86-64 system call convention.
166 ///
167 /// # Parameters
168 ///
169 /// - `regs`: A mutable reference to the [`Registers`] struct, which
170 /// contains the current state of the CPU registers. This structure will
171 /// be used to retrieve the system call number and its arguments, and also
172 /// to set the return value.
173 ///
174 /// # Functionality
175 ///
176 /// The function processes the system call as follows:
177 /// 1. Extracts the system call number and arguments using the
178 /// [`SyscallAbi::from_registers`].
179 /// 2. Looks up the corresponding handler function from a predefined list of
180 /// system calls. The handler function is selected based on the system
181 /// call number (`sysno`).
182 /// 3. If a handler is found, it is executed with the ABI as an argument. If
183 /// no handler is found, the function returns a
184 /// [`KernelError::NoSuchSyscall`] error.
185 ///
186 /// The result of the system call handler (either success or error) is then
187 /// returned via the [`SyscallAbi::set_return_value`] method, which
188 /// modifies the CPU registers accordingly.
189 fn syscall(&mut self, regs: &mut Registers) {
190 // ** YOU DON'T NEED TO CHANGE THIS FUNCTION **
191 let abi = SyscallAbi::from_registers(regs); // Extract ABI from the registers.
192 // Lookup the system call handler function based on the system call number.
193 let return_val = SyscallNumber::try_from(abi.sysno).and_then(|no| match no {
194 SyscallNumber::Exit => self.exit(&abi),
195 SyscallNumber::Open => self.file_struct.open(&abi),
196 SyscallNumber::Read => self.file_struct.read(&abi),
197 SyscallNumber::Write => self.file_struct.write(&abi),
198 SyscallNumber::Seek => self.file_struct.seek(&abi),
199 SyscallNumber::Tell => self.file_struct.tell(&abi),
200 SyscallNumber::Close => self.file_struct.close(&abi),
201 SyscallNumber::Pipe => self.file_struct.pipe(&abi),
202 SyscallNumber::Mmap => self.mm_struct.mmap(&mut self.file_struct, &abi),
203 SyscallNumber::Munmap => self.mm_struct.munmap(&abi),
204 });
205 // Set the return value of the system call (success or error) back into the
206 // registers.
207 abi.set_return_value(return_val);
208 }
209
210 /// Validates whether the given memory range is accessible for the process.
211 ///
212 /// This function checks if a memory region is safe to read or write before
213 /// performing a memory-related operation. It ensures that user-provided
214 /// addresses do not access restricted memory regions, preventing
215 /// potential security vulnerabilities or crashes.
216 ///
217 /// # Parameters
218 /// - `addr`: A range of virtual addresses to be accessed.
219 /// - `is_write`: `true` if the access involves writing to memory, `false`
220 /// for read-only access.
221 ///
222 /// # Returns
223 /// - `true` if the memory range is valid and accessible.
224 /// - `false` if access is denied due to invalid address range or
225 /// insufficient permissions.
226 fn access_ok(&self, addr: Range<Va>, is_write: bool) -> bool {
227 // Delegate the validation to the memory management system.
228 self.mm_struct.access_ok(addr, is_write)
229 }
230
231 fn with_page_table_pa(&self, f: &fn(Pa)) {
232 f(self.mm_struct.page_table.pa())
233 }
234}