CConcurrency

C Processes, fork() & exec(): System-Level Multitasking and IPC

TT
TopicTrick Team
C Processes, fork() & exec(): System-Level Multitasking and IPC

C Processes, fork() & exec(): System-Level Multitasking and IPC


Table of Contents


Processes vs Threads: The Isolation Trade-off

FeatureThreadProcess
MemoryShared heap + own stackCompletely separate address space
CommunicationDirect (shared variables)IPC needed (pipe, socket, shm)
Creation cost~µs (stack allocation)~ms (address space copy)
Fault isolationOne crash kills all threadsOne crash doesn't affect others
SecurityNo isolationFull OS-level isolation
Use caseCPU parallelism, I/O overlapReliability, multi-user systems

fork(): Creating a Child Process

fork() duplicates the current process. After the call, both parent and child are running:

mermaid
c

Critical: After fork(), both parent and child continue executing from the line after the fork() call. The return value of fork() is the only way to determine which process you're in.


Copy-on-Write: Why fork() Is Fast

A naive implementation of fork() would copy the entire parent's address space (potentially gigabytes). Modern kernels use Copy-on-Write (CoW):

  1. After fork(), both parent and child share the same physical pages with read-only mappings.
  2. When either process writes to a page, the kernel copies that specific page and gives the writing process its own private copy.
  3. Pages that are never modified are never copied.
c

For a 100MB process, fork() costs only microseconds (just page table copies), not hundreds of milliseconds of actual memory copying.


exec(): Replacing the Process Image

exec() family replaces the current process's code, data, and stack with a new program. The PID remains the same, but everything else is completely new:

c

Key fact: If exec succeeds, it never returns — the current process is completely replaced. If it returns, that means it failed (and errno is set).


The fork+exec Pattern: How Shells Work

Every shell (bash, zsh, fish) uses the fork+exec pattern to run commands:

c

waitpid(): Preventing Zombie Processes

When a child process exits, it doesn't disappear immediately. It becomes a zombie — its entry remains in the process table until the parent calls wait() or waitpid() to collect its exit status:

c

Preventing zombie accumulation in long-running servers:

c

Pipes: Inter-Process Communication

Pipes are unidirectional byte streams connecting two processes. They are the oldest and simplest form of IPC:

c

This is exactly how shell pipelining works: ls -l | grep ".c"ls writes to a pipe, grep reads from it.


Shared Memory: High-Speed IPC

For high-throughput IPC (database shared buffers, multimedia pipelines), POSIX shared memory lets two processes share a physical memory page — zero copy:

c

PostgreSQL's shared_buffers uses shared memory — the database buffer pool is one large shmget segment shared among all backend worker processes.


Real-World Case Studies

SystemStrategyWhy
Google ChromeOne process per tabCrash isolation: one bad page doesn't kill browser
NginxMaster + N worker processesWorkers can be killed/restarted without downtime
Apache (prefork)One process per requestIsolation between HTTP clients
PostgreSQLOne process per connectionClient crashes don't affect the database engine
Bash shellfork+exec for every commandClean separation of shell state from command state
AndroidOne Zygote + fork per appFast app startup via CoW from pre-loaded runtime

Frequently Asked Questions

What happens if the parent exits before the child? The child becomes an orphan. The Linux kernel automatically re-parents it to init (PID 1) or the current subreaper, which will call wait() to clean it up. Orphans don't cause problems — unreaped zombies do.

Can processes share memory safely without explicit shared memory? No. After fork, parent and child have separate address spaces (with CoW). Writing to shared_var in the child doesn't affect the parent's copy. Use pipes, shared memory (shm_open), sockets, or memory-mapped files for actual sharing.

What is a daemon process? A daemon is a background process that: detaches from its controlling terminal, creates a new session (setsid()), changes to the root directory (chdir("/")), and redirects stdin/stdout/stderr to /dev/null. System services (sshd, nginx, postgrad) run as daemons.

How does vfork differ from fork? vfork creates a child that temporarily shares the parent's address space (no CoW) and suspends the parent until the child calls exec or exit. It's ultra-fast but extremely dangerous — the child must not access or modify any variables, as it uses the parent's stack. Use only immediately followed by exec.


Key Takeaway

fork() and exec() are the Foundation of Unix Multitasking. They are the primitive operations from which shells, web servers, and databases are built. Process isolation — the guarantee that one process's crash, bug, or malicious behavior cannot corrupt another — is one of operating systems' most important security properties.

Understanding fork, exec, waitpid, and IPC mechanisms positions you to build multi-process server architectures that are both high-performance and fault-tolerant.

Read next: Signals & Interrupt Handling: Trapping OS Events →


Part of the C Mastery Course — 30 modules from C basics to expert systems engineering.