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}