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}