keos_project3/
fork.rs

1//! # `Fork` with Copy-On-Write optimization.
2//
3//! `fork` is a system call that creates a new process by duplicating the
4//! calling process. The new child process is almost identical to the parent,
5//! inheriting the same memory layout, open file descriptors, and register
6//! state. The child receives a copy of the parent’s process state, including
7//! [`FileStruct`] and [`MmStruct`]. Two processes can communicate via opened
8//! `pipe`s after the forking. The only difference is the return value of
9//! the syscall: the parent receives the child’s PID, while the child receives
10//! 0.
11//!
12//! ### Copy-On-Write
13//
14//! In modern operating system, **fork** utilizes **copy-on-write (COW)**
15//! optimization to efficiently share memory between parent and child. Instead
16//! of copying all memory pages immediately, the parent and child initially
17//! share all pages marked as read-only. If either process writes to one of
18//! these shared pages, a page fault triggers the kernel to create a private
19//! copy for that process.
20//
21//! Note that modern CPUs include a **Translation Lookaside Buffer (TLB)**, a
22//! hardware cache that stores recent virtual-to-physical address translations.
23//! This leads to case where even after you modify the permission of the
24//! address, the change is **not immediately visible** to the CPU if the TLB
25//! still holds a cached, now-stale mapping. Therefore, you must maintain the
26//! consistency with the TLB. To maintain memory protection correctness:
27//! - The kernel must **shut down** TLB for all pages made read-only by
28//!   write-protection since they were previously writable.
29//! - The kernel must **invalidate** a TLB entry after a new private page is
30//!   installed , replacing a previously shared page.
31//!
32//! Without these TLB flushes, processes may continue using stale or incorrect
33//! mappings, bypassing copy-on-write or causing data corruption.
34//
35//! In KeOS, copy-on-write works as follow:
36//! 1. When a process invokes a **fork** system call, the kernel makes copy of
37//!    [`FileStruct`].
38//! 2. The kernel write-protected ptes by calling
39//!    [`LazyPager::write_protect_ptes`] to make copy of [`MmStruct`]. This
40//!    marks all writable pages as read-only when the child is created. This
41//!    ensures any future writes will trigger a page fault.
42//! 3. After write-protecting pages, the kernel **shuts down the TLB** entries
43//!    for those pages to remove stale writable translations from the CPU's
44//!    cache. This is done via [`tlb_shutdown`].
45//! 4. Execute a new process for child with the copy of states.
46//! 5. Resume the execution of both parent and child.
47//!
48//! After resuming the execution, process might confront a **page fault** from
49//! the write-protect. The page fault handler determines whether the fault is
50//! copy-on-write fault with [`PageFaultReason::is_cow_fault`] and handle it
51//! with [`LazyPager::do_copy_on_write`]. This function finds the pte with
52//! [`PageTable::walk_mut`], allocates and installs a new private copy of a
53//! page. After mapping the new page, the kernel **invalidates the old TLB
54//! entry** with the [`StaleTLBEntry::invalidate`].
55//!
56//! ## Implementation Requirements
57//! You need to implement the followings:
58//! - [`LazyPager::write_protect_ptes`]
59//! - [`PageFaultReason::is_cow_fault`]
60//! - [`LazyPager::do_copy_on_write`]
61//! - [`fork`]
62//!
63//! This ends the project 3.
64//!
65//! [`tlb_shutdown`]: keos::mm::page_table::tlb_shutdown
66
67use crate::lazy_pager::{LazyPager, PageFaultReason};
68#[cfg(doc)]
69use keos::mm::page_table::StaleTLBEntry;
70use keos::{KernelError, thread::ThreadBuilder};
71use keos_project1::{file_struct::FileStruct, syscall::SyscallAbi};
72use keos_project2::{mm_struct::MmStruct, page_table::PageTable};
73
74impl LazyPager {
75    /// Handles a copy-on-write (COW) page fault by creating a private copy of
76    /// the faulted page.
77    ///
78    /// This method is invoked when a process attempts to write to a page that
79    /// is currently shared and marked read-only as part of a copy-on-write
80    /// mapping. It ensures that the faulting process receives its own
81    /// writable copy of the page while preserving the original contents for
82    /// other processes that may still share the original page.
83    ///
84    /// ### Steps:
85    /// 1. Find write-protected page table entry with [`PageTable::walk_mut`].
86    /// 2. Allocates a new page and copies the contents of the original page
87    ///    into it.
88    /// 3. Updates the page table to point to the new page with write
89    ///    permissions.
90    /// 4. Invalidates the TLB entry for the faulting address to ensure the CPU
91    ///    reloads the mapping.
92    ///
93    /// ### Parameters
94    /// - `page_table`: The faulting process’s page table.
95    /// - `reason`: Information about the page fault, including the faulting
96    ///   address and access type.
97    pub fn do_copy_on_write(
98        &mut self,
99        page_table: &mut PageTable,
100        reason: &PageFaultReason,
101    ) -> Result<(), KernelError> {
102        todo!()
103    }
104
105    /// Applies write-protection to all user-accessible pages in the memory
106    /// layout.
107    ///
108    /// This method is called during `fork` to prepare the address space for
109    /// copy-on-write semantics. It traverses the entire virtual memory
110    /// layout, identifies writable mappings, and rewrites their page table
111    /// entries (PTEs) as read-only. This allows parent and child
112    /// processes to safely share physical memory until one performs a write, at
113    /// which point a private copy is created.
114    ///
115    /// After modifying the page tables, stale entries in the **Translation
116    /// Lookaside Buffer (TLB)** are invalidated to ensure that the CPU
117    /// observes the new permissions by calling [`tlb_shutdown`].
118    ///
119    /// ### Parameters
120    /// - `mm_struct`: The current process’s memory layout, including its
121    ///   [`LazyPager`] state.
122    ///
123    /// ### Returns
124    /// - A new [`MmStruct`] representing the forked child process, with updated
125    ///   page table mappings.
126    ///
127    /// [`tlb_shutdown`]: keos::mm::page_table::tlb_shutdown
128    pub fn write_protect_ptes(
129        mm_struct: &mut MmStruct<LazyPager>,
130    ) -> Result<MmStruct<LazyPager>, KernelError> {
131        let MmStruct { page_table, pager } = mm_struct;
132        let mut new_page_table = PageTable::new();
133        todo!()
134    }
135}
136
137impl PageFaultReason {
138    /// Returns `true` if the fault is a **copy-on-write** violation.
139    ///
140    /// # Returns
141    /// - `true` if this fault requires COW handling.
142    /// - `false` otherwise.
143    #[inline]
144    pub fn is_cow_fault(&self) -> bool {
145        todo!()
146    }
147}
148
149/// Creates a new process by duplicating the current process using
150/// copy-on-write.
151///
152/// `fork` is a system call that creates a child process that is
153/// identical to the calling (parent) process. The child inherits the parent's
154/// memory layout, file descriptors, and register state. After the fork, both
155/// processes continue execution independently from the point of the call.
156///
157/// This implementation uses **copy-on-write (COW)** to avoid eagerly copying
158/// the entire address space. Memory pages are initially shared between the
159/// parent and child and marked as read-only. When either process attempts to
160/// write to a shared page, a page fault occurs and
161/// [`LazyPager::do_copy_on_write`] handles creating a private writable copy of
162/// the page.
163///
164/// # Syscall API
165/// ```c
166/// int fork(void);
167/// ```
168///
169/// ### Behavior
170/// - The parent receives the child’s PID as the return value.
171/// - The child receives `0` as the return value.
172/// - On failure, the parent receives `Err(KernelError)` and no new process is
173///   created.
174///
175/// ### Memory Management
176/// - Invokes [`LazyPager::write_protect_ptes`] to mark shared pages as
177///   read-only.
178/// - Creates a new address space and page table for the child.
179/// - Invalidates stale TLB entries to enforce new memory protection rules.
180///
181/// ### File Descriptors
182/// - Duplicates the parent's file descriptor table.
183/// - File objects are shared and reference-counted across parent and child,
184///   consistent with the UNIX file model.
185///
186/// ### ABI and Register State
187/// - Copies the parent’s ABI state into the child.
188/// - Adjusts the child’s register state to reflect a return value of `0`.
189///
190/// ### Parameters
191/// - `file_struct`: The parent’s file descriptor table to be duplicated.
192/// - `mm_struct`: The parent’s memory layout (address space).
193/// - `abi`: The parent’s syscall ABI and register snapshot.
194/// - `create_task`: A closure for creating and spawning the new process.
195///
196/// ### Returns
197/// - `Ok(pid)`: The parent receives the child process ID.
198/// - `Err(KernelError)`: If the fork operation fails due to memory or resource
199///   constraints.
200pub fn fork(
201    file_struct: &mut FileStruct,
202    mm_struct: &mut MmStruct<LazyPager>,
203    abi: &SyscallAbi,
204    create_task: impl FnOnce(FileStruct, MmStruct<LazyPager>) -> ThreadBuilder,
205) -> Result<usize, KernelError> {
206    let file_struct = file_struct.clone();
207    let mm_struct = LazyPager::write_protect_ptes(mm_struct)?;
208    // TODO: Clone the register state and set the rax to be zero.
209    let regs: keos::syscall::Registers = todo!();
210
211    let handle = create_task(file_struct, mm_struct).spawn(move || regs.launch());
212    Ok(handle.tid as usize)
213}