keos_project4/
lib.rs

1//! # Project 4: Synchronization and Multithreading
2//!
3//! In Project 4, you will expand KeOS's process abstraction to support
4//! **multithreading**. You will implement basic **synchronization primitives**
5//! such as **mutexes**, **condition variables**, and **semaphores** to
6//! synchronize resources between threads.
7//!
8//! Synchronization primitives will be used to support correct multithreaded
9//! behavior. For example, **thread join** functionality will be built using
10//! semaphores, which themselves are implemented using a combination of mutexes
11//! and condition variables.
12//!
13//! Finally, you will improve the scheduler by implementing a **round-robin**
14//! scheduling algorithm. Unlike previous projects where the unit of scheduling
15//! was the **entire process**, starting from this project, the unit of
16//! scheduling becomes an individual **thread**.
17//!
18//! ## Getting Started
19//!
20//! To get started, navigate to the `keos-project4/grader` directory and run:
21//!
22//! ```bash
23//! $ cargo run
24//! ```
25//!
26//! ## Modifiable Files
27//! In this project, you can modify the following five files:
28//! - `sync/mutex.rs`
29//! - `sync/condition_variable.rs`
30//! - `sync/semaphore.rs`
31//! - `process.rs`
32//! - `round_robin.rs`
33//!
34//! ## Project Outline
35//!
36//! - [`Synchronization Primitives`]:
37//!   - **Mutex**: Provide mutual exclusion.
38//!   - **Condition Variable**: Enable waiting for conditions.
39//!   - **Semaphore**: Implement higher-level synchronization using mutex and
40//!     condition variables.
41//!
42//! - [`MultiThreading`]: Implement thread creation, termination, and and join
43//!   mechanisms.
44//!
45//! - [`Round Robin Scheduler`]: Implement a round-robin scheduler that switches
46//!   between threads fairly, using time slices.
47//!
48//!
49//! [`Synchronization Primitives`]: sync
50//! [`MultiThreading`]: process
51//! [`Round Robin Scheduler`]: round_robin
52
53#![no_std]
54#![no_main]
55#![feature(negative_impls)]
56#![deny(rustdoc::broken_intra_doc_links)]
57
58extern crate alloc;
59#[allow(unused_imports)]
60#[macro_use]
61extern crate keos;
62
63pub mod process;
64pub mod round_robin;
65pub mod sync;
66
67use alloc::boxed::Box;
68use core::ops::Range;
69use keos::{
70    KernelError,
71    addressing::{Pa, Va},
72    syscall::Registers,
73    task::PFErrorCode,
74    task::Task,
75    thread::{ThreadBuilder, with_current},
76};
77use keos_project1::syscall::SyscallAbi;
78use keos_project2::mm_struct::MmStruct;
79use keos_project3::{fork::fork, get_phys::get_phys, lazy_pager::PageFaultReason};
80pub use process::Thread;
81
82/// Represents system call numbers used in project4.
83///
84/// Each variant corresponds to a specific system call that can be invoked
85/// using the system call interface. The numeric values align with the
86/// syscall table in the operating system.
87#[repr(usize)]
88pub enum SyscallNumber {
89    /// Terminates the calling thread.
90    Exit = 0,
91    /// Opens a file and returns a file descriptor.
92    Open = 1,
93    /// Reads data from a file descriptor.
94    Read = 2,
95    /// Writes data to a file descriptor.
96    Write = 3,
97    /// Moves the file offset of an open file.
98    Seek = 4,
99    /// Retrieves the current file offset.
100    Tell = 5,
101    /// Closes an open file descriptor.
102    Close = 6,
103    /// Create an interprocess communication channel.
104    Pipe = 7,
105    /// Map the memory.
106    Mmap = 8,
107    /// Unmap the memory.
108    Munmap = 9,
109    /// Fork the process.
110    Fork = 10,
111    /// Create a thread.
112    ThreadCreate = 11,
113    /// Join a Thread.
114    ThreadJoin = 12,
115    /// Terminates the process, by terminating all threads.
116    ExitGroup = 13,
117    /// Get Physical Address of Page (for grading purposes only)
118    GetPhys = 0x81,
119}
120
121impl TryFrom<usize> for SyscallNumber {
122    type Error = KernelError;
123    fn try_from(no: usize) -> Result<SyscallNumber, Self::Error> {
124        match no {
125            0 => Ok(SyscallNumber::Exit),
126            1 => Ok(SyscallNumber::Open),
127            2 => Ok(SyscallNumber::Read),
128            3 => Ok(SyscallNumber::Write),
129            4 => Ok(SyscallNumber::Seek),
130            5 => Ok(SyscallNumber::Tell),
131            6 => Ok(SyscallNumber::Close),
132            7 => Ok(SyscallNumber::Pipe),
133            8 => Ok(SyscallNumber::Mmap),
134            9 => Ok(SyscallNumber::Munmap),
135            10 => Ok(SyscallNumber::Fork),
136            11 => Ok(SyscallNumber::ThreadCreate),
137            12 => Ok(SyscallNumber::ThreadJoin),
138            13 => Ok(SyscallNumber::ExitGroup),
139            0x81 => Ok(SyscallNumber::GetPhys),
140            _ => Err(KernelError::NoSuchSyscall),
141        }
142    }
143}
144
145impl Task for Thread {
146    /// Handles a system call request from a user program.
147    fn syscall(&mut self, regs: &mut Registers) {
148        let abi = SyscallAbi::from_registers(regs); // Extract ABI from the registers.
149        // Lookup the system call handler function based on the system call number.
150        let return_val = SyscallNumber::try_from(abi.sysno).and_then(|no| match no {
151            SyscallNumber::Exit => self.exit(&abi),
152            SyscallNumber::Open => self.with_file_struct_mut(|fs, abi| fs.open(abi), &abi),
153            SyscallNumber::Read => self.with_file_struct_mut(|fs, abi| fs.read(abi), &abi),
154            SyscallNumber::Write => self.with_file_struct_mut(|fs, abi| fs.write(abi), &abi),
155            SyscallNumber::Seek => self.with_file_struct_mut(|fs, abi| fs.seek(abi), &abi),
156            SyscallNumber::Tell => self.with_file_struct_mut(|fs, abi| fs.tell(abi), &abi),
157            SyscallNumber::Close => self.with_file_struct_mut(|fs, abi| fs.close(abi), &abi),
158            SyscallNumber::Pipe => self.with_file_struct_mut(|fs, abi| fs.pipe(abi), &abi),
159            SyscallNumber::Mmap => {
160                self.with_file_mm_struct_mut(|fs, mm, abi| mm.mmap(fs, abi), &abi)
161            }
162            SyscallNumber::Munmap => self.with_mm_struct_mut(|mm, abi| mm.munmap(abi), &abi),
163            SyscallNumber::Fork => self.with_file_mm_struct_mut(
164                |fs, mm, abi| {
165                    fork(fs, mm, abi, |file_struct, mm_struct| {
166                        with_current(|th| {
167                            let builder = ThreadBuilder::new(&th.name);
168                            let tid = builder.get_tid();
169                            builder.attach_task(Box::new(Thread::from_file_mm_struct(
170                                file_struct,
171                                mm_struct,
172                                tid,
173                            )))
174                        })
175                    })
176                },
177                &abi,
178            ),
179            SyscallNumber::ThreadCreate => self.thread_create(&abi),
180            SyscallNumber::ThreadJoin => self.thread_join(&abi),
181            SyscallNumber::ExitGroup => self.exit_group(&abi),
182            SyscallNumber::GetPhys => {
183                self.with_file_mm_struct_mut(|fs, mm, abi| get_phys(mm, fs, abi), &abi)
184            }
185        });
186        // Set the return value of the system call (success or error) back into the
187        // registers.
188        abi.set_return_value(return_val);
189    }
190
191    /// Validates whether the given memory range is accessible for the process.
192    fn access_ok(&self, addr: Range<Va>, is_write: bool) -> bool {
193        self.with_mm_struct_mut(
194            |mm_struct, (addr, is_write)| mm_struct.access_ok(addr, is_write),
195            (addr, is_write),
196        )
197    }
198
199    /// Handles a page fault.
200    fn page_fault(&mut self, ec: PFErrorCode, cr2: Va) {
201        if !self.with_mm_struct_mut(
202            |mm_struct, (ec, cr2)| {
203                let reason = PageFaultReason::new(ec, cr2);
204                // Acquire a lock on the thread's memory state (`mm_state`) to ensure safe
205                // access.
206
207                // Delegate the fault handling to [`LazyPager::handle_page_fault`],
208                // which will update the page table and allocate a physical page if necessary.
209                let MmStruct { page_table, pager } = mm_struct;
210                pager.handle_page_fault(page_table, &reason).is_ok()
211            },
212            (ec, cr2),
213        ) {
214            // If the fault is real fault, exit the process.
215            let _ = self.exit_group(&SyscallAbi {
216                sysno: SyscallNumber::ExitGroup as usize,
217                arg1: -1isize as usize,
218                arg2: 0,
219                arg3: 0,
220                arg4: 0,
221                arg5: 0,
222                arg6: 0,
223                regs: &mut Registers::default(),
224            });
225            unreachable!()
226        }
227    }
228
229    /// Runs a given closure with physical address of page table.
230    fn with_page_table_pa(&self, f: &fn(Pa)) {
231        f(self.page_table_pa)
232    }
233}