Kahibaro
Discord Login Register

7.5.1 Process lifecycle

Overview of the Process Lifecycle

In Linux, a process moves through a series of well-defined phases: creation, execution, possible blocking, and termination, plus various intermediate states (zombie, stopped, traced). This chapter focuses on how these states are represented and managed in the kernel, and how user space can observe and influence them.

High-level pipeline:

$$
\text{fork/clone/vfork} \rightarrow \text{execve} \rightarrow \text{running/blocked} \rightarrow \text{exit} \rightarrow \text{reaped}
$$

Each step has specific kernel data structures, syscalls, and observable side effects.


Process Creation

`fork()` and copy-on-write

The classic way to create a new process is fork():

Copy-on-write (CoW) means both processes initially share physical pages marked as read-only; when either writes, the kernel allocates a new page and updates that process’s page table.

`vfork()`

vfork() is an optimization for the common pattern:

  1. fork()
  2. execve() in the child soon after

Differences:

Modern Linux + fork() with CoW often make vfork() unnecessary, but it still exists and is used in some performance-critical code (like shells or posix_spawn implementations).

`clone()` and process vs thread semantics

clone() is the low-level primitive backing both processes and threads. It allows fine-grained control over what is shared with the parent, via flags like:

Threads in Linux are just tasks (kernel task_structs) that share certain resources. A “thread” is typically created by:

This yields:

User-space libraries (e.g. pthread_create()) wrap clone() to provide POSIX threads.


Executing a New Program: `execve()`

After fork()/clone(), the child often calls execve() (or a wrapper like execl(), execvp()):

What execve() does:

From the lifecycle point of view:

execve() is atomic with respect to signals: either a new image is fully in place, or the old one continues; there is no halfway state visible to user space.


Process States

Linux tracks a process via a task_struct that includes a state and related flags. You can observe these from user space:

Common states (as reported by ps in the STAT column):

Running vs runnable

Internally, there is a distinction:

From user space, both are shown as R.

Interruptible sleep (`S`)

Uninterruptible sleep (`D`)

Stopped and traced (`T`)

Zombie and Orphan Processes

Zombie processes

A zombie is a process that has:

Lifecycle around exit:

  1. Child calls _exit(status) (or returns from main).
  2. Kernel:
    • Marks process as TASK_DEAD internally.
    • Stores exit code and stats.
    • Sends SIGCHLD to the parent.
  3. Process becomes a zombie (Z in ps).
  4. Parent:
    • Calls wait*() syscall.
    • Kernel returns collected info and fully removes the child entry.
  5. Zombie disappears; process is “reaped”.

A few short-lived zombies are normal. Persistent zombies usually indicate:

Orphan processes and `init` (or `systemd`) re-parenting

A process becomes an orphan when its parent exits before it does:

This prevents permanent zombies: eventually, PID 1 will wait() on adopted children.


Scheduling, Preemption, and Context Switch

High-level scheduling flow

At any point, a process is either:

The scheduler:

Common scheduling policies (user visible)

You can inspect and adjust them with:

Context switching

A context switch is the core mechanism that moves the CPU from one process (or thread) to another:

Context switches happen when:

You can see context switch metrics in:

Blocking, Waking, and Wait Queues

Blocking

When a process cannot continue (e.g., I/O not ready), it typically:

  1. Moves to a sleeping state (TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE).
  2. Is added to a wait queue associated with the resource (file, socket, event).
  3. Scheduler picks another runnable task.

Examples of blocking operations:

Wait queues and wake-ups

Internally, many kernel objects have wait queues. Generic flow:

From user space:

Signals in the Lifecycle

Signals are a key control mechanism for process state transitions.

Common signals affecting the lifecycle:

Lifecycle edges driven by signals:

You can see pending and blocked signals in /proc/<pid>/status.


Exit, Termination, and Reaping

Process termination paths

A process can terminate via:

Exit status is an 8-bit value (0–255) visible to the parent via wait*().

Parent waiting for children

The parent typically calls:

This:

If the parent never waits:

Namespaces and the Lifecycle

Although full namespaces behavior belongs elsewhere, they affect identity during the lifecycle:

This means:

Observing Lifecycle from User Space

A few practical ways to see lifecycle states and transitions.

`/proc` views

For a given PID:

`ps` and `top`

Examples:

Using `strace` to observe lifecycle events

This is useful for understanding when and how processes:

Summary of the Lifecycle Transitions

At a high level:

  1. Creation:
    • fork(), clone(), vfork():
      • New task_struct.
      • Inherits or shares various resources.
  2. Program replacement (optional):
    • execve():
      • New program image, same PID.
  3. Running and blocking:
    • Scheduler moves process between:
      • running/runnable (R).
      • sleeping (S/D).
      • stopped/traced (T).
  4. Termination:
    • exit() / _exit() or fatal signal.
    • Process releases resources; becomes zombie.
  5. Reaping:
    • Parent (or PID 1) calls wait*().
    • Kernel removes zombie entry; lifecycle ends.

Understanding these steps at the kernel level is the foundation for deeper topics like memory management, signals & IPC, namespaces, and cgroups, which all interact tightly with the process lifecycle.

Views: 143

Comments

Please login to add a comment.

Don't have an account? Register now!