Files
claude-os/docs/fork.md
AI 42328ead0b feat: implement fork system call with deep address space cloning (AI)
- 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.
2026-02-23 12:42:02 +00:00

84 lines
2.8 KiB
Markdown

# 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:
1. **Kernel-space entries** (no `PAGE_USER` flag): shared directly between
parent and child. Both processes see the same kernel mappings.
2. **User-space entries** (`PAGE_USER` flag 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 saved `EAX` to 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 performs `iret`.
- 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_BLOCKED` and 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:
1. Calling `SYS_FORK`
2. Parent prints "Parent" and calls `SYS_WAITPID`
3. Child prints "Child" and exits with code 7
4. Parent reaps child, prints "Reaped", exits with code 0