- Added paging_clone_directory_from(): deep-copies user-space pages so parent and child have independent memory. Kernel pages are shared. - Fixed process_fork() to accept registers_t* for accurate child state, and to clone from the parent's page directory (not the kernel's). - Refactored process_exit() to properly context-switch to next process using new process_switch_to_user assembly stub (loads full registers_t and performs iret), instead of halting unconditionally. - Fixed sys_waitpid() to use proper blocking: marks process BLOCKED, invokes scheduler, and resumes with exit code when child dies. - Added SYSCALL_SWITCHED mechanism to prevent syscall_handler from clobbering the next process's EAX after a context switch. - Created fork-test user app that validates fork + waitpid. - Added docs/fork.md with architecture documentation. Tested: fork-test creates child, both print messages, parent waits for child exit (code 7), parent reaps and exits (code 0). hello-world also verified to still work correctly after the process_exit refactor.
2.8 KiB
Fork System Call
Overview
The fork() system call duplicates the calling process, creating a new child
process with an independent copy of the parent's address space.
System Call Interface
- Number:
SYS_FORK(3) - Arguments: None
- Returns: Child PID in the parent, 0 in the child, -1 on error
Implementation
Address Space Cloning
paging_clone_directory_from(src_pd_phys) performs a deep copy of a process's
page directory:
-
Kernel-space entries (no
PAGE_USERflag): shared directly between parent and child. Both processes see the same kernel mappings. -
User-space entries (
PAGE_USERflag set): fully deep-copied. For each user-space page directory entry:- A new page table is allocated
- Each present user page has a new physical page allocated and the content copied byte-for-byte
- This ensures parent and child have completely independent memory
Register State
The child receives a copy of the parent's register state at the time of the
INT 0x80 syscall, with EAX set to 0. This means the child resumes execution
at the instruction immediately following the INT 0x80 that triggered fork.
Process Exit and Waitpid
process_exit() was refactored to support multi-process scenarios:
- When a process exits, it scans for any process blocked on
waitpid()for its PID and unblocks it, setting the waiter's savedEAXto the exit code. - If another process is ready,
process_switch_to_user()is called to directly context-switch via an assembly stub that loads the full register set and performsiret. - If no processes remain, the system halts.
sys_waitpid() supports blocking:
- If the child is already a zombie, it reaps immediately
- Otherwise, the caller is marked
PROCESS_BLOCKEDand the scheduler is invoked to switch to another process - When the child exits, the parent is unblocked with the exit code
Assembly Support
process_switch_to_user in interrupts.S loads a full registers_t struct
and performs iret to enter user mode. This is used when process_exit()
needs to context-switch outside the normal ISR return path.
Syscall Flow
User: INT 0x80 (EAX=SYS_FORK)
→ ISR stub pushes registers
→ isr_handler → syscall_handler → sys_fork(regs)
→ process_fork(regs)
→ Clone page directory with deep user-page copy
→ Copy current interrupt frame to child (EAX=0)
→ Return child PID to parent (via EAX)
→ ISR stub pops registers, iret
→ Parent continues with EAX=child_pid
→ [Timer interrupt] → scheduler picks child
→ Child starts with EAX=0
Testing
The fork-test application validates fork by:
- Calling
SYS_FORK - Parent prints "Parent" and calls
SYS_WAITPID - Child prints "Child" and exits with code 7
- Parent reaps child, prints "Reaped", exits with code 0