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}