How Linux Distinguishes Parent and Child Processes
In the Linux operating system, processes exist in a strict
hierarchical tree where every process (except the initial
systemd or init process) is spawned by a
parent. Linux differentiates between a parent and a child process
primarily through the return value of the fork() system
call, distinct Process Identifiers (PIDs and PPIDs), and internal
tracking within the kernel’s process control block. This article breaks
down the exact mechanisms the kernel and user space use to tell parent
and child processes apart.
1. The fork()
System Call Return Value
The primary programmatic distinction occurs at the exact moment of
creation. When an existing process calls the fork() system
call, the kernel creates an almost identical duplicate of the calling
process.
Both processes continue execution at the instruction immediately
following the fork() call, but the operating system
delivers different return values to each:
- To the parent process:
fork()returns the Process ID (PID) of the newly created child process. This enables the parent to keep track of, monitor, or manage the child. - To the child process:
fork()returns0. This signifies to the child process that it is the newly created entity. - On failure: If the system cannot allocate resources
for the new process,
fork()returns-1to the parent, and no child is created.
Programmers use a simple conditional block (if/else)
evaluating this return value to define different behaviors for the
parent and the child.
2. PID and PPID (Process Identifiers)
At the user and administrative levels, Linux distinguishes between processes using numerical identifiers:
- PID (Process ID): Every process running on Linux has a unique numerical identifier assigned by the kernel. The child receives its own new, distinct PID upon creation.
- PPID (Parent Process ID): The child process explicitly stores the PID of the process that spawned it in its attributes.
You can observe this directly in user space using commands such as
ps -ef or pstree, or by checking the
/proc filesystem (e.g., inspecting the PPid
field in /proc/[PID]/status). Inside C code, a process can
find its own identity using getpid() and its parent's
identity using getppid().
3. Kernel Tracking via
task_struct
Internally, the Linux kernel represents every process as an instance
of the struct task_struct structure (the process control
block). The kernel maintains the parent-child relationship using
dedicated pointer fields within this structure:
real_parent/parent: Pointers within the child'stask_structthat point directly to the memory address of the parent process’stask_struct.children: A doubly-linked list head inside the parent'stask_structthat tracks all active children it has spawned.sibling: A linked-list node used to link a child process to other children spawned by the same parent.
These pointers enable the kernel to manage lifecycle events, such as
passing exit status codes when a child terminates (via
wait() or waitpid()) or re-parenting orphaned
children to init (PID 1) if the parent process dies before
the child.
4. Resource Allocation and Copy-on-Write (COW)
While a child process inherits file descriptors, environment variables, and memory mappings from its parent, the kernel isolates their execution using virtual memory management.
Linux employs a technique called Copy-on-Write (COW):
- Initially, parent and child share the same physical memory pages marked as read-only.
- The moment either the parent or the child attempts to modify a memory page, the kernel generates an interrupt, duplicates that specific page, and assigns the unique copy to the modifying process.
This ensures that while the child starts as a duplicate, the operating system maintains complete memory separation, allowing both processes to run independently without corrupting each other's state.