keos_project5/
lib.rs

1//! Project 5: File System
2//!
3//! Until project4, you have implemented the core features of modern OSes
4//! including memory, multi-threading, and scheduling. In Project 5, you will
5//! focus on implementing the file system, the abstraction of disk.
6//!
7//! This project will involve introducing **page cache** for caching a file
8//! contents, and developing a **fast file system** with **journaling** to
9//! ensure data integrity and recoverability after a system crash or failure.
10//!
11//! ## Getting Started
12//!
13//! To get started, navigate to the `keos-project5/grader` directory and run the
14//! following command:
15//!
16//! ```bash
17//! $ cargo run
18//! ```
19//!
20//! ## Modifiable Files
21//! In this project, you can modify the following files:
22//! - `page_cache/mod.rs`
23//! - `ffs/inode.rs`
24//! - `ffs/journal.rs`
25//! - `ffs/fs_objects.rs`
26//! - `advanced_file_structs.rs`
27//!
28//! ## Project Outline
29//! - [`Page Cache`]: Implement a caching mechanism to optimize access to
30//!   frequently used files.
31//! - [`Fast File System`]: Implement the simplfied version of fast file system
32//!   with journaling.
33//! - [`Advanced File System Call`]: Implement the advanced feature for the file
34//!   system.
35//!
36//! [`Page Cache`]: page_cache
37//! [`Fast File System`]: ffs
38//! [`Advanced File System Call`]: advanced_file_structs
39
40#![no_std]
41#![no_main]
42#![feature(slice_as_array, step_trait)]
43#![deny(rustdoc::broken_intra_doc_links)]
44
45extern crate alloc;
46#[allow(unused_imports)]
47#[macro_use]
48extern crate keos;
49
50macro_rules! const_assert {
51    ($($tt:tt)*) => {
52        const _: () = assert!($($tt)*);
53    }
54}
55
56pub mod advanced_file_structs;
57pub mod ffs;
58pub mod lru;
59pub mod page_cache;
60pub mod process;
61
62use core::ops::Range;
63
64use advanced_file_structs::AdvancedFileStructs;
65use alloc::{boxed::Box, collections::btree_set::BTreeSet};
66use keos::{
67    KernelError,
68    addressing::{Pa, Va},
69    sync::SpinLock,
70    syscall::Registers,
71    task::{PFErrorCode, Task},
72    thread::with_current,
73};
74use keos_project1::syscall::SyscallAbi;
75use keos_project3::{fork::fork, get_phys::get_phys};
76pub use process::Thread;
77
78#[doc(hidden)]
79pub static ACCESS_CHECK_BYPASS_LIST: SpinLock<BTreeSet<Va>> = SpinLock::new(BTreeSet::new());
80
81/// Represents system call numbers used in project5.
82///
83/// Each variant corresponds to a specific system call that can be invoked
84/// using the system call interface. The numeric values align with the
85/// syscall table in the operating system.
86#[repr(usize)]
87pub enum SyscallNumber {
88    // == Pj 1 ==
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    // == Pj 2 ==
106    /// Map the memory.
107    Mmap = 8,
108    /// Unmap the memory.
109    Munmap = 9,
110    // == Pj 3 ==
111    /// Fork the process.
112    Fork = 10,
113    // == Pj 4 ==
114    /// Create a thread.
115    ThreadCreate = 11,
116    /// Join a Thread.
117    ThreadJoin = 12,
118    /// Terminates the process, by terminating all threads.
119    ExitGroup = 13,
120    // == Pj 5 ==
121    /// Create a regular file.
122    Create = 14,
123    /// Make a directory.
124    Mkdir = 15,
125    /// Unlink a file.
126    Unlink = 16,
127    /// Change the current working directory.
128    Chdir = 17,
129    /// Read the contents of a directory.
130    Readdir = 18,
131    /// Stat a file.
132    Stat = 19,
133    /// Synchronize a file's in-memory state with disk.
134    Fsync = 20,
135    // == Grading Only ==
136    /// Get Physical Address of Page (for grading purposes only)
137    GetPhys = 0x81,
138}
139
140impl TryFrom<usize> for SyscallNumber {
141    type Error = KernelError;
142    fn try_from(no: usize) -> Result<SyscallNumber, Self::Error> {
143        match no {
144            0 => Ok(SyscallNumber::Exit),
145            1 => Ok(SyscallNumber::Open),
146            2 => Ok(SyscallNumber::Read),
147            3 => Ok(SyscallNumber::Write),
148            4 => Ok(SyscallNumber::Seek),
149            5 => Ok(SyscallNumber::Tell),
150            6 => Ok(SyscallNumber::Close),
151            7 => Ok(SyscallNumber::Pipe),
152            8 => Ok(SyscallNumber::Mmap),
153            9 => Ok(SyscallNumber::Munmap),
154            10 => Ok(SyscallNumber::Fork),
155            11 => Ok(SyscallNumber::ThreadCreate),
156            12 => Ok(SyscallNumber::ThreadJoin),
157            13 => Ok(SyscallNumber::ExitGroup),
158            14 => Ok(SyscallNumber::Create),
159            15 => Ok(SyscallNumber::Mkdir),
160            16 => Ok(SyscallNumber::Unlink),
161            17 => Ok(SyscallNumber::Chdir),
162            18 => Ok(SyscallNumber::Readdir),
163            19 => Ok(SyscallNumber::Stat),
164            20 => Ok(SyscallNumber::Fsync),
165            0x81 => Ok(SyscallNumber::GetPhys),
166            _ => Err(KernelError::NoSuchSyscall),
167        }
168    }
169}
170
171impl Task for Thread {
172    /// Handles a system call request from a user program.
173    ///
174    /// This function is the entry point for system call handling. It retrieves
175    /// the system call ABI from the provided [`Registers`] structure, which
176    /// includes the system call number and its arguments. Based on the
177    /// system call number (`sysno`), it looks up the appropriate handler
178    /// function in a predefined list. If a handler is found, it is invoked
179    /// with the ABI, otherwise, an error ([`KernelError::NoSuchSyscall`]) is
180    /// returned.
181    ///
182    /// After the handler function processes the system call, the return value
183    /// (either a success or error) is set back into the CPU registers via
184    /// the `set_return_value` method. The return value is stored in the `%rax`
185    /// register as per the x86-64 system call convention.
186    ///
187    /// # Parameters
188    ///
189    /// - `regs`: A mutable reference to the [`Registers`] struct, which
190    ///   contains the current state of the CPU registers. This structure will
191    ///   be used to retrieve the system call number and its arguments, and also
192    ///   to set the return value.
193    ///
194    /// # Functionality
195    ///
196    /// The function processes the system call as follows:
197    /// 1. Extracts the system call number and arguments using the
198    ///    [`SyscallAbi::from_registers`].
199    /// 2. Looks up the corresponding handler function from a predefined list of
200    ///    system calls. The handler function is selected based on the system
201    ///    call number (`sysno`).
202    /// 3. If a handler is found, it is executed with the ABI as an argument. If
203    ///    no handler is found, the function returns a
204    ///    [`KernelError::NoSuchSyscall`] error.
205    ///
206    /// The result of the system call handler (either success or error) is then
207    /// returned via the [`SyscallAbi::set_return_value`] method, which
208    /// modifies the CPU registers accordingly.
209    fn syscall(&mut self, regs: &mut Registers) {
210        // ** YOU DON'T NEED TO CHANGE THIS FUNCTION **
211        let abi = SyscallAbi::from_registers(regs); // Extract ABI from the registers.
212        // Lookup the system call handler function based on the system call number.
213        let return_val = SyscallNumber::try_from(abi.sysno).and_then(|no| match no {
214            SyscallNumber::Exit => self.exit_group(&abi),
215            SyscallNumber::Open => self.with_file_struct_mut(|fs, abi| fs.open(abi), &abi),
216            SyscallNumber::Read => self.with_file_struct_mut(|fs, abi| fs.read(abi), &abi),
217            SyscallNumber::Write => self.with_file_struct_mut(|fs, abi| fs.write(abi), &abi),
218            SyscallNumber::Seek => self.with_file_struct_mut(|fs, abi| fs.seek(abi), &abi),
219            SyscallNumber::Tell => self.with_file_struct_mut(|fs, abi| fs.tell(abi), &abi),
220            SyscallNumber::Close => self.with_file_struct_mut(|fs, abi| fs.close(abi), &abi),
221            SyscallNumber::Pipe => self.with_file_struct_mut(|fs, abi| fs.pipe(abi), &abi),
222            SyscallNumber::Mmap => {
223                self.with_file_mm_struct_mut(|fs, mm, abi| mm.mmap(fs, abi), &abi)
224            }
225            SyscallNumber::Munmap => self.with_mm_struct_mut(|mm, abi| mm.munmap(abi), &abi),
226            SyscallNumber::Fork => self.with_file_mm_struct_mut(
227                |fs, mm, abi| {
228                    fork(fs, mm, abi, |file_struct, mm_struct| {
229                        with_current(|th| {
230                            let builder = keos::thread::ThreadBuilder::new(&th.name);
231                            let tid = builder.get_tid();
232                            builder.attach_task(Box::new(Thread::from_fs_mm_struct(
233                                file_struct,
234                                mm_struct,
235                                tid,
236                            )))
237                        })
238                    })
239                },
240                &abi,
241            ),
242            SyscallNumber::ThreadCreate => self.thread_create(&abi),
243            SyscallNumber::ThreadJoin => self.thread_join(&abi),
244            SyscallNumber::ExitGroup => self.exit_group(&abi),
245            SyscallNumber::Create => self.with_file_struct_mut(|fs, abi| fs.create(abi), &abi),
246            SyscallNumber::Mkdir => self.with_file_struct_mut(|fs, abi| fs.mkdir(abi), &abi),
247            SyscallNumber::Unlink => self.with_file_struct_mut(|fs, abi| fs.unlink(abi), &abi),
248            SyscallNumber::Chdir => self.with_file_struct_mut(|fs, abi| fs.chdir(abi), &abi),
249            SyscallNumber::Readdir => self.with_file_struct_mut(|fs, abi| fs.readdir(abi), &abi),
250            SyscallNumber::Stat => self.with_file_struct_mut(|fs, abi| fs.stat(abi), &abi),
251            SyscallNumber::Fsync => self.with_file_struct_mut(|fs, abi| fs.fsync(abi), &abi),
252            SyscallNumber::GetPhys => {
253                self.with_file_mm_struct_mut(|fs, mm, abi| get_phys(mm, fs, abi), &abi)
254            }
255        });
256        // Set the return value of the system call (success or error) back into the
257        // registers.
258        abi.set_return_value(return_val);
259    }
260
261    #[inline]
262    fn access_ok(&self, addr: Range<Va>, is_write: bool) -> bool {
263        let guard = ACCESS_CHECK_BYPASS_LIST.lock();
264
265        let result = if guard.contains(&addr.start) {
266            true
267        } else {
268            self.0.access_ok(addr, is_write)
269        };
270        guard.unlock();
271        result
272    }
273
274    #[inline]
275    fn page_fault(&mut self, ec: PFErrorCode, cr2: Va) {
276        self.0.page_fault(ec, cr2)
277    }
278
279    #[inline]
280    fn with_page_table_pa(&self, f: &fn(Pa)) {
281        self.0.with_page_table_pa(f)
282    }
283}