- 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.
84 lines
2.8 KiB
Markdown
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
|