keos_project4/
process.rs

1//! # Multithreaded Process
2//!
3//! Modern applications increasingly rely on **multithreading** to achieve
4//! responsiveness, scalability, and efficient utilization of multicore
5//! processors. By enabling a single process to run multiple threads
6//! concurrently, the system can perform I/O and computation in parallel, reduce
7//! latency, and better respond to user interactions or real-time events.
8//!
9//! To support such workloads, the operating system must provide robust
10//! mechanisms for creating, scheduling, and synchronizing threads within a
11//! single process. This includes managing shared memory access, ensuring
12//! thread-safe operations, and allowing coordination through synchronization
13//! primitives such as mutexes, condition variables, and semaphores.
14//!
15//! In KeOS, extending the process abstraction to support multithreading is a
16//! critical step toward building a realistic and capable system. This
17//! enhances your knowledge about how the multithreading, the foundation for
18//! concurrent programming models, works in real-world operating system.
19//!
20//! ## Multithreading in KeOS
21//!
22//! Previously, a process in KeOS consisted of a single thread. Your goal is
23//! extending the process to run **multiple concurrent threads**, each with its
24//! own execution context but sharing the same address space and resources.
25//! Each thread maintains its own register states while sharing the same states.
26//!
27//! In earlier projects, each `Process` owned its own [`FileStruct`] and
28//! [`MmStruct`]. However, on the multi-threaded model, these components are
29//! **shared across all threads** of a process. That is, they become **shared
30//! resources**, requiring proper synchronization. To support shared and mutable
31//! access, these resources are wrapped inside an `Arc<Mutex<_>>`.
32//! - [`Arc`] provides shared ownership with reference counting.
33//! - [`Mutex`] ensures exclusive access to mutable state.
34//!
35//! This allows multiple threads to safely access and modify shared structures
36//! like file tables and virtual memory mappings.
37//!
38//! #### Thread Life Cycle
39//!
40//! KeOS supports a lightweight threading model within a single process,
41//! enabling multiple threads to execute concurrently while sharing the same
42//! address space. The life cycle of a thread is managed through four key system
43//! calls:
44//!
45//! - [`thread_create`]: Creates a new thread within the same process, executing
46//!   a given function on a user-supplied stack.
47//! - [`thread_join`]: Waits for a specified thread to terminate and retrieves
48//!   its return value.
49//! - [`exit`]: Terminates the calling thread without affecting other threads in
50//!   the same process.
51//! - [`exit_group`]: Terminates all threads within the process simultaneously.
52//!
53//! When creating a new thread via [`thread_create`], the user must provide a
54//! pointer to a valid, writable memory region that will serve as the new
55//! thread’s stack. This approach mirrors Linux's `clone()` system call and
56//! gives userspace full control over stack allocation and reuse. The kernel
57//! validates that the provided stack lies within a properly mapped and writable
58//! memory region to ensure memory safety.
59//!
60//! Threads can be terminated individually using the [`exit`] system call,
61//! which affects only the calling thread. Other threads in the same process
62//! continue executing. To coordinate with thread termination, a thread may
63//! invoke [`thread_join`], which blocks until the target thread exits and
64//! returns its result. This can be implemented using a [`Semaphore`]
65//! initialized with zero permits, where the exiting thread signals completion
66//! by releasing a permit.
67//!
68//! In contrast, [`exit_group`] is used when the entire process must be
69//! terminated, bringing down all associated threads by calling
70//! [`thread::kill_by_tid`]. This is necessary in scenarios such as a fatal
71//! error in the main thread, unhandled signals, or explicit process termination
72//! by the application. Unlike [`exit`], which only marks the calling thread for
73//! termination, [`exit_group`] ensures that all threads in the process are
74//! promptly and safely terminated, and that the process is cleaned up
75//! consistently. This behavior aligns with the semantics of multi-threaded
76//! processes in modern operating systems and prevents resource leaks or partial
77//! process shutdowns.
78//!
79//! Together, these mechanisms provide a simple yet robust model for managing
80//! thread life cycles in KeOS, balancing fine-grained control with process-wide
81//! coordination.
82//!
83//! ## Implementation Requirements
84//! You need to implement the followings:
85//! - [`Thread`]
86//! - [`Thread::from_file_mm_struct`]
87//! - [`Thread::with_file_struct_mut`]
88//! - [`Thread::with_mm_struct_mut`]
89//! - [`Thread::thread_create`]
90//! - [`Thread::exit`]
91//! - [`Thread::thread_join`]
92//! - [`Thread::exit_group`]
93//!
94//! By implementing this section, you can move on to the next [`section`] with
95//! the final form of execution model that widely used in modern OSes:
96//! ```text
97//! +========= Process =========+
98//! | Shared States:            |
99//! |  - MmStruct               |
100//! |  - FileStruct             |
101//! |                           |
102//! | Threads:                  |
103//! |  +----- Thread 1 -----+   |
104//! |  |  - Register State  |   |
105//! |  |  - User Stack      |   |
106//! |  +--------------------+   |
107//! |           ...             |
108//! |  +----- Thread N -----+   |
109//! |  |  - Register State  |   |
110//! |  |  - User Stack      |   |
111//! |  +--------------------+   |
112//! +===========================+
113//! ```
114//!
115//! [`exit`]: Thread::exit
116//! [`thread_create`]: Thread::thread_create
117//! [`thread_join`]: Thread::thread_join
118//! [`exit_group`]: Thread::exit_group
119//! [`Arc`]: <https://doc.rust-lang.org/beta/alloc/sync/struct.Arc.html>
120//! [`section`]: crate::round_robin
121//! [`thread::kill_by_tid`]: keos::thread::kill_by_tid
122//! [`Mutex`]: crate::sync::Mutex
123//! [`Semaphore`]: crate::sync::semaphore
124
125use alloc::{boxed::Box, string::String};
126use keos::{KernelError, addressing::Pa, syscall::Registers, thread::ThreadBuilder};
127use keos_project1::{file_struct::FileStruct, syscall::SyscallAbi};
128use keos_project2::mm_struct::MmStruct;
129use keos_project3::lazy_pager::LazyPager;
130
131/// A thread state of project 4, which contains file and memory state.
132pub struct Thread {
133    pub tid: u64,
134    pub page_table_pa: Pa,
135    // TODO: Add and fix any member you need.
136    pub file_struct: FileStruct,
137    pub mm_struct: MmStruct<LazyPager>,
138}
139
140impl Default for Thread {
141    fn default() -> Self {
142        Self::from_file_mm_struct(FileStruct::new(), MmStruct::new(), 0)
143    }
144}
145
146impl Thread {
147    /// Create a thread with given [`MmStruct`].
148    pub fn from_mm_struct(mm_struct: MmStruct<LazyPager>, tid: u64) -> Self {
149        Self::from_file_mm_struct(FileStruct::new(), mm_struct, tid)
150    }
151
152    /// Create a thread with given [`MmStruct`] and [`FileStruct`].
153    pub fn from_file_mm_struct(
154        file_struct: FileStruct,
155        mm_struct: MmStruct<LazyPager>,
156        tid: u64,
157    ) -> Self {
158        let page_table_pa = mm_struct.page_table.pa();
159
160        // TODO: Initialize any member you need.
161
162        Self {
163            // TODO: Add and fix any member you need.
164            tid,
165            page_table_pa,
166            mm_struct,
167            file_struct,
168        }
169    }
170
171    /// Executes a closure with mutable access to the underlying file struct
172    /// ([`FileStruct`]).
173    ///
174    /// This method provides a way to access and mutate the file struct
175    /// associated with the current thread. It accepts a closure `f` that
176    /// receives a mutable reference to the `FileStruct` and an
177    /// additional argument of type `Args`.
178    pub fn with_file_struct_mut<Args, R>(
179        &self,
180        f: impl FnOnce(&mut FileStruct, Args) -> R,
181        args: Args,
182    ) -> R {
183        f(todo!(), args)
184    }
185
186    /// Executes a closure with mutable access to the underlying memory struct
187    /// ([`MmStruct`]).
188    ///
189    /// This method provides a way to access and mutate the memory struct
190    /// associated with the current thread. It accepts a closure `f` that
191    /// receives a mutable reference to the `MmStruct<LazyPager>` and an
192    /// additional argument of type `Args`.
193    pub fn with_mm_struct_mut<Args, R>(
194        &self,
195        f: impl FnOnce(&mut MmStruct<LazyPager>, Args) -> R,
196        args: Args,
197    ) -> R {
198        f(todo!(), args)
199    }
200
201    /// Executes a closure with mutable access to the underlying file struct
202    /// ([`FileStruct`]) and memory struct ([`MmStruct`]).
203    ///
204    /// This method provides a way to access and mutate the file struct
205    /// associated with the current thread. It accepts a closure `f` that
206    /// receives a mutable reference to the `FileStruct` and an
207    /// additional argument of type `Args`.
208    pub fn with_file_mm_struct_mut<Args, R>(
209        &self,
210        f: impl FnOnce(&mut FileStruct, &mut MmStruct<LazyPager>, Args) -> R,
211        args: Args,
212    ) -> R {
213        self.with_mm_struct_mut(
214            |mm, args| self.with_file_struct_mut(|fs, args| f(fs, mm, args), args),
215            args,
216        )
217    }
218
219    /// Exit the current thread.
220    ///
221    /// This function terminates the calling thread, returning the provided
222    /// exit code to any thread that `join`s on it.
223    ///
224    /// # Syscall API
225    /// ```c
226    /// void exit(int status);
227    /// ```
228    /// - `status`: The exit code returned to a joining thread.
229    ///
230    /// # Behavior
231    /// - Wakes up any thread waiting via `thread_join`.
232    /// - Cleans up thread-local resources.
233    pub fn exit(&self, abi: &SyscallAbi) -> Result<usize, KernelError> {
234        todo!()
235    }
236
237    /// Create a new thread in the current process.
238    ///
239    /// This function creates a new thread that begins execution at the given
240    /// entry point with the specified argument.
241    ///
242    /// # Syscall API
243    /// ```c
244    /// int thread_create(char *name, void *stack, void *(*start_routine)(void *), void *arg);
245    /// ```
246    /// - `name`: Name of the thread.
247    /// - `stack`: Stack of the thread.
248    /// - `start_routine`: Pointer to the function to be executed by the thread.
249    /// - `arg`: Argument to be passed to the thread function.
250    ///
251    /// # Behavior
252    /// - The new thread shares the same address space as the calling thread.
253    /// - The stack for the new thread is allocated automatically.
254    pub fn thread_create(&self, abi: &SyscallAbi) -> Result<usize, KernelError> {
255        let name: String = todo!();
256        let regs: Registers = todo!();
257
258        let builder = ThreadBuilder::new(name);
259        let tid = builder.get_tid();
260
261        let task: Box<Thread> = todo!();
262
263        builder.attach_task(task).spawn(move || regs.launch());
264        Ok(tid as usize)
265    }
266
267    /// Wait for a thread to finish.
268    ///
269    /// This function blocks the calling thread until the specified thread
270    /// terminates, and retrieves its exit code.
271    ///
272    /// Note that only a single call can receives the exit code of the dying
273    /// thread. If multiple `thread_join` is called on the same thread,
274    /// return values of others than the first one are InvalidArgument
275    /// error.
276    ///
277    /// # Syscall API
278    /// ```c
279    /// int thread_join(int thread_id, int *retval);
280    /// ```
281    /// - `thread_id`: ID of the thread to join.
282    /// - `retval`: Pointer to store the thread's exit code (optional).
283    ///
284    /// # Behavior
285    /// - If the target thread has already exited, returns immediately with the
286    ///   proper exit code.
287    /// - If `retval` is non-null, the exit code of the target thread is stored.
288    pub fn thread_join(&self, abi: &SyscallAbi) -> Result<usize, KernelError> {
289        todo!()
290    }
291
292    /// Exit a process.
293    ///
294    /// This function terminates all the threads in the current process,
295    /// including the current caller thread. The exit code is provided as
296    /// the first argument (`arg1`) of the system call.
297    ///
298    /// # Syscall API
299    /// ```c
300    /// int exit_group(int status);
301    /// ```
302    /// - `status`: The thread's exit code.
303    ///
304    /// # Notes
305    /// - This function does not return in normal execution, as it terminates
306    ///   the process.
307    /// - If an error occurs, it returns a `KernelError`
308    pub fn exit_group(&self, abi: &SyscallAbi) -> Result<usize, KernelError> {
309        todo!()
310    }
311}