Writing an OS Kernel from Scratch – Part 8: fork and exec — Let Processes “Reproduce”!
“A single process running alone? That’s not an OS, that’s a single-player game. Today, we give processes the ability to ‘reproduce’ — duplicating themselves via fork, and transforming into new programs via exec!”
In the previous post, we successfully ran the first user-mode program and implemented secure interaction via system calls. But that’s not enough: a real OS must dynamically create, manage, and recycle processes, supporting shells that launch arbitrary commands and build process trees.
The core of all this is the two pillars of Unix philosophy:
✅ fork() — duplicate the current process
✅ exec() — replace the current process with a new program
Why fork + exec?
You might ask: why not just “load a new program directly”?
Because Unix’s design philosophy is: “Process creation” and “program loading” are two independent operations.
Classic flow: fork() → (modify environment) → exec()
Advantages:
- Flexibility: modify environment after fork (like redirecting stdin/stdout), then exec
- Consistency: all process creation goes through the same mechanism
- Simplicity: shell is trivial to implement (just fork + exec)
Without fork/exec, there would be no modern shell, pipes, or background tasks!
Process Control Block (PCB) Upgrade
Each process needs lifecycle management fields: pid, parent_pid, state, exit_code — the foundation for process relationship and state management.
Implementing sys_fork
fork() duplicates the parent’s entire execution environment:
- Child returns 0, parent returns child’s PID
- Memory is independent (Copies page tables)
Steps:
- Allocate new PCB and PID
- Copy parent’s page directory (copy physical pages page by page)
- Copy kernel stack and register state
- Set up return value magic
Implementing sys_exec
exec() doesn’t create a new process — it overwrites the current process’s user space with a new program.
Steps:
- Release current user page table (keep kernel portion)
- Load new ELF into user space
- Set up new user stack (push argc, argv, environment)
- Set new EIP to ELF entry point
Zombie Processes and sys_wait
When a process calls exit(), it shouldn’t immediately release its PCB — the parent may need to get its exit status.
Flow:
- Child exits → enters ZOMBIE state (keep PCB, release memory)
- Parent calls wait() → kernel returns child PID and exit code
- Kernel fully releases child’s PCB
Comments