# Threads and processes

---

# Threads and processes

_Peios / Using Peios / Threads and processes_

> A process is a running program; a thread is a single line of execution inside it. Every action the system takes happens on some thread.

A **process** is a program that is running. When a program starts, the system
gives it a block of private memory that only it can see, a table of the files
and other resources it holds open, and a name the system tracks it by. That
running program, plus everything the system keeps for it, is a process. When
the program finishes, its process goes away.

A process can also do several things at once by running more than one
**thread**. A thread is a single line of execution — one sequence of steps the
system is working through. A process always has at least one thread (its
initial execution is its first thread), and it can start more. Every thread in
a process shares the same private memory and the same open resources. What
each thread keeps to itself is its own place in its sequence: each runs its
own steps, at its own pace, possibly in parallel with the others.

## The actors of the system

Processes and threads are what carry out work on the system. Every action —
opening a file, sending data over a network, starting another program — is
performed by some thread. When the system decides whether an action is
allowed, the question it answers is "is this thread allowed?" When it records
that something happened, it records which thread did it.

A thread always acts as someone — a person, a service, or the system itself.
Peios carries that identity along with the thread on an object called a
**token** (see [Tokens](/peios/security-fundamentals/tokens/overview.md)). Two facts matter here:

- Every thread is always acting as someone. There is no "nobody" state.
- When one process starts another, the new process begins acting as the same
  identity as the process that started it.

## What a process has

The things the system keeps for every process:

| A process has | What that means |
|---|---|
| Private memory | working space only this process can see; other processes cannot read it |
| Open resources | the files, connections, and other things it currently holds open |
| An identity | who it is acting as (carried on its token) |
| A place in a family tree | every process was started by another, so processes form a tree |
| A lifecycle | it is created, it runs, and it ends — and its end is always observed |
| One or more threads | the lines of execution doing its work |

This is not the full list — a process also carries other per-process state,
such as its
[Process Security Block](/peios/using-peios/threads-and-processes/the-process-security-block.md)
(PSB), which holds its security-related settings.

## Where to start

Continue with [The process and thread model](/peios/using-peios/threads-and-processes/the-process-and-thread-model.md)
for how a thread and a process relate, and why the thread — not the process —
is the more basic unit, with a "process" being one particular way of using
them.

This topic also covers:

- [Creating processes](/peios/using-peios/threads-and-processes/creating-processes.md) — how a
  process starts another, and what the new one begins with.
- [Process lifecycle](/peios/using-peios/threads-and-processes/process-lifecycle.md) — how a
  process ends, and how the system cleans up after it.
- [Process relationships and job control](/peios/using-peios/threads-and-processes/relationships-and-job-control.md)
  — the process tree, process groups, and sessions (distinct from the logon
  sessions in [Logon sessions](/peios/security-fundamentals/logon-sessions/overview.md)).

---

# The process and thread model

_Peios / Using Peios / Threads and processes_

> A thread is the basic unit of work; a process is the boundary around its threads, holding the memory, open resources, and identity they share.

A running program has two separable parts: the threads, which run, and the
process, which contains them.

The **thread** is the part that runs — one line of execution, a single
sequence of steps the system works through. A machine with several cores runs
that many threads genuinely in parallel, one per core; a single core runs many
threads by switching between them. Which thread runs, on which core, and for
how long is decided by the system's **scheduler** — a subject of its own. Some
tools and logs call a thread a **task**; it is the same thing.

The **process** is the boundary around a group of threads. It does not run —
it contains. A process holds the things its threads share: their private
memory, their open files and connections, and their identity. Every thread
inside one process sees the same memory and the same open resources; threads
in two different processes do not. That separation is what stops one program
from reaching into another's memory or files.

The thread does the work; the process is the shared space the work happens in.
A program that does one thing at a time is a process with a single thread. A
program that does several things at once is a process with several threads,
all sharing one space.

## Why the thread is the more basic idea

Threads, not processes, are the fundamental unit: what the system runs is
threads, and a "process" is the answer to the question "what does this thread
share, and with whom?" Bundle some threads together so they share one memory,
one set of open resources, and one identity, and that bundle is a process.
Give a thread its own fresh, separate memory instead of sharing an existing
one, and you have made a new process.

This matters because it explains creation. Making a new thread and making a
new process are the same mechanism with a different answer to "how much does
the new line of execution share with the one that made it?" Share everything,
and the result is another thread in the same process. Share nothing, and the
result is a new process. Everything in between is possible too. How the
operation is actually performed is covered in
[Creating processes](/peios/using-peios/threads-and-processes/creating-processes.md).

## What threads share, and what they don't

Within one process, every thread shares:

| Shared across threads | What that means in practice |
|---|---|
| The process's memory | One thread can read and change data another thread is using — cooperation is cheap, but concurrent access must be coordinated. |
| The open resources | A file one thread opens is usable by all of them. |
| The identity | By default every thread acts as the process's identity. |

Each thread keeps its own place in its sequence — which step it is on right
now. That is the point of having more than one: each thread makes progress
through its own work independently of the others.

There is one exception to the shared identity. A single thread can temporarily
act as a different principal — for example, a service handling a request can
act as the requester for that one piece of work, then revert. Only that one
thread is affected, and only until it reverts. This is covered in
[Impersonation](/peios/security-fundamentals/impersonation/overview.md); here it is enough to know that
"every thread in a process shares the process's identity" is the default, not
an unbreakable rule.

## Naming a process

The system gives each process a number called its **process ID**, or **PID**,
so that people and other programs can refer to it — to inspect it, signal it,
or wait for it to finish. Threads are individually identifiable too, by a
thread ID — see
[thread operations](/peios/using-peios/threads-and-processes/thread-operations-reference.md).

A PID is short and convenient, but it only names a process while that process
exists. Once a process ends, its PID can later be reused for a completely
different process. A PID tells you which process is which right now, but it is
useless as a lasting record — two unrelated processes can hold the same PID at
different times.

For that, every process also has a **Process GUID** (globally unique
identifier): a name that is never reused, assigned to each process when it
starts and fixed for the rest of its life. Where the PID is the convenient
short handle for the moment, the Process GUID is the permanent one — it
identifies that one specific process and no other, even long after it has
ended. This is what lets a record of something that happened on the system
name exactly the right process, with no chance of confusing two that happened
to share a PID.

Both the PID and the GUID refer to a process. Neither says who the process is
acting as — that is a separate question, answered by its identity rather than
by any number or name.

## Where to go next

To see how a process or a thread is created — and what a new one starts out
with — read [Creating processes](/peios/using-peios/threads-and-processes/creating-processes.md).

---

# Creating processes

_Peios / Using Peios / Threads and processes_

> How processes are created — fork, exec, spawn, and clone — what a new process starts with, and how to hold a reliable handle to one with a pidfd.

Every process on the system was started by another process — a process that is
already running has to create it. Creation is built from two operations, which
combine to cover every case.

## Splitting: one process becomes two

A running process can **split** into a copy of itself. The original keeps
going, and a second process appears beside it that begins as a near-identical
copy. The original is called the **parent** and the copy the **child**.

The child starts with:

- **Its own private memory**, copied from the parent. From the moment of the
  split the two memories are separate — a change one process makes is
  invisible to the other.
- **Copies of the parent's open resources** — the files and connections the
  parent had open, the child has open too.
- **The same identity** — the child begins acting as the same principal as its
  parent. (It inherits the parent's token; how the token carries across a
  split is in [Token lifecycle](/peios/security-fundamentals/tokens/lifecycle.md).)

It also gets things of its own that are not copied: a new PID, and a new
Process GUID — the child is its own distinct process, not a continuation of
the parent. A few pieces of per-process state start fresh rather than being
inherited, too.

This operation is called **fork**.

## Replacing: same process, different program

A process can also **replace** the program it is running with a different one.
It remains the same process — same PID, same Process GUID, same identity — but
from that point on it runs entirely different code. Whatever the previous
program was doing is discarded, replaced wholesale by the new one.

This operation is called **exec**.

## Putting them together

The two operations combine to launch a different program as a new process: a
process **splits**, and the child immediately **replaces** itself with the
program to run. This split-then-replace pair is how most programs are started
— when you run a command, something forks and the child execs your command.
There is also a single combined step, **spawn**, that does both at once for
the common case.

Underneath, splitting is one setting of a more general operation, **clone**,
that lets the creator choose how much the new line of execution shares with
it. Share everything, and the result is another thread in the same process;
share almost nothing, and the result is a new process — the two ends of the
range from
[the process and thread model](/peios/using-peios/threads-and-processes/the-process-and-thread-model.md).
Everyday code rarely uses the general form directly; it makes threads or
spawns programs, which are particular settings of it.

The exact contract — every `clone` sharing flag, precisely what `fork` passes to a
child, and the variants of `exec` — is collected in the
[process creation reference](/peios/using-peios/threads-and-processes/process-creation-reference.md).

## Getting hold of the new process

When a process creates a child, it usually needs to keep track of it — to wait
for it to finish, or to send it a signal later. For this it can hold a
**handle** to the child: a reference to that one specific process, called a
**pidfd**. It is the same kind of handle the system hands out for an open
file.

A process can be given a pidfd for its child at the moment it creates it, or
ask for one for a process that already exists. Either way the pidfd is tied to
that one specific process and can never be mistaken for another, so waiting on
it or signalling through it always reaches the intended target.

This is what makes a pidfd safer than a bare PID. A PID can be reused once the
process it named has ended; code that holds a bare PID risks later acting on a
completely different process that inherited the number. A pidfd never has that
problem — it refers to the one process it was created for and nothing else,
for as long as the program holds it.

## Where to go next

A process that has been created eventually ends. How it finishes, and how the
system cleans up after it, is covered in
[Process lifecycle](/peios/using-peios/threads-and-processes/process-lifecycle.md).

---

# Process lifecycle

_Peios / Using Peios / Threads and processes_

> How a process ends, how its result is collected, what happens when its parent ends first, and how the system cleans up after it.

Every process eventually ends. A process's life has three parts — it is
created, it runs, and it finishes — and the system ensures that when it
finishes, its result is collected and its resources are reclaimed.

## The states it passes through

While it exists, a process is in one of a handful of **states**, and the system
moves it between them. Most of the time it is either **running** — executing,
or ready to run the moment a core is free — or **sleeping**, paused while it
waits for something to happen, such as data to arrive or a timer to fire. A
sleeping process uses no processor time until what it is waiting for is ready;
most processes spend most of their lives asleep.

A few states are worth knowing by name:

- **Stopped** — suspended (for example by job control when a job is paused at
  the terminal); it does nothing until told to resume.
- **Traced** — stopped under the control of a debugger that has attached to it, so
  the debugger can inspect and step it.
- **Uninterruptible sleep** — waiting on something the system will not interrupt,
  almost always a brief piece of disk or device I/O. A process here cannot be woken
  or even killed until the operation finishes; it normally lasts an instant, and a
  process stuck in it is a sign that hardware or a filesystem is stuck too.
- **Zombie** — finished, but still held as a result waiting to be collected (see
  below).

Once its result has been collected the process is gone, removed from the system
entirely.

## Two ways a process ends

A process ends in one of two ways.

Most often it ends **on its own**: it finishes its work and stops. A process
that ends this way leaves behind an **exit status** — a small record of how it
went, usually just "succeeded" or "failed" (and, on failure, a number giving a
rough reason). The act of a process ending itself is called **exit**.

The other way is that a process is **stopped from outside** — something tells
it to stop, and it ends whether or not its work was done. (The messages that
do this are **signals**, a subject of their own.) A process ended this way
also leaves an exit status, marked to show it was stopped rather than
finishing on its own.

Either way, a record of the process remains until its result is collected.

## Collecting the result

Whenever a child changes state — ends, is stopped, or resumes — the system
sends its **parent** a signal, `SIGCHLD`, prompting it to check. The parent
then reads the child's exit status. This is called **waiting** for the
process.

Between the moment a process ends and the moment its parent collects the
result, the finished process is a **zombie**: no longer running, but with the
system still holding its exit status so the parent can read it. Despite the
name, a zombie is harmless — it does no work and uses almost nothing; it is a
result waiting to be collected. The instant the parent collects it, the zombie
is gone for good.

If a parent never collects, zombies accumulate — finished processes whose
results no one read. That is a fault in the parent, not normal behaviour.

A parent holding a [pidfd](/peios/using-peios/threads-and-processes/creating-processes.md) for its
child can wait on it directly — the dependable way to be told the exact moment the
child ends.

The exact calls — every `wait` variant, and how an exit status is read — are in the
[process lifecycle reference](/peios/using-peios/threads-and-processes/process-lifecycle-reference.md).

## When the parent ends first

A parent does not always outlive its children. If a parent ends while one of
its children is still running, that child becomes an **orphan**.

Orphans are not lost. The system's first process — PID 1, which on Peios is
**peinit** — adopts every orphan and stands in as its parent from then on:
when the orphan eventually ends, peinit collects its result, so no finished
process is ever left uncollected. Adoption is automatic; the orphan keeps
running unaffected, only with a new parent.

PID 1 is the catch-all, but a process can arrange to catch orphans within its
own subtree. By marking itself a **subreaper** (with
`prctl(PR_SET_CHILD_SUBREAPER)`), a process becomes the one that adopts any
orphan descended from it — its grandchildren and below — instead of letting
them reparent all the way up to PID 1. Service managers and container managers
use this to keep responsibility for everything they launched: when something
deep in their subtree loses its immediate parent, it reparents to the manager,
which still learns when it finally ends.

## What the system cleans up

When a process ends, the system reclaims what it was using. Its private memory is
freed and its open files and connections are closed.

It also **releases its identity**. A process acts as a principal, carried on
its token; when the process ends, its hold on that token is released. If other
processes were sharing the same identity — for example, siblings from the same
login — the identity lives on for them, and the system clears it away only once
nothing is using it any more. The details are in
[Token lifecycle](/peios/security-fundamentals/tokens/lifecycle.md).

The process's PID becomes available for the system to assign to a future
process. Its Process GUID is not reused — it remains a permanent marker of
that one process, so records of what it did still point unambiguously back to
it long after it is gone.

## Where to go next

Processes are arranged into a family tree and grouped together in ways that
matter for running them. That is
[Process relationships and job control](/peios/using-peios/threads-and-processes/relationships-and-job-control.md).

---

# Process relationships and job control

_Peios / Using Peios / Threads and processes_

> The two ways processes relate — descent, forming a family tree rooted at PID 1, and grouping into process groups and sessions for job control.

Every process is related to others in two different ways: by **descent** — who
started whom — and by **grouping** — which processes are handled together when
they are controlled at a terminal.

## The family tree

Every process was started by another, so every process has a **parent**: the
process that created it. A process can refer to its parent by the parent's PID,
sometimes called the **PPID** (parent process ID).

Follow the parents upward and they all lead back to the same place — PID 1, the
first process, which the system starts at boot. Every other process descends from
it, directly or through a chain of parents, so the processes on a running system
form a single **family tree** rooted at PID 1. This is the same tree that makes
orphan adoption work: when a process loses its parent, PID 1 takes over the role,
[as covered in the lifecycle](/peios/using-peios/threads-and-processes/process-lifecycle.md).

A few processes at the very root of the tree are special. **PID 1** is the first
process started at boot — on Peios, peinit — and the ancestor of all the rest; the
system protects it, refusing to kill it with any signal it has not chosen to handle,
because losing it would bring down the whole tree. Beneath the userspace processes
the kernel runs threads of its own: **PID 0** is the idle process (what a core runs
when it has nothing else to do), and **PID 2**, `kthreadd`, is the parent of every
kernel thread. The kernel's threads — among them the worker threads (`kworker`)
that run deferred work and the threads that service hardware interrupts — appear in
a process listing as the system's own work rather than as programs loaded from disk.

## Process groups

Several processes often do one job together. In a pipeline — the output of one
command feeding into the next — each command is its own process, but they form
one job: you stop, pause, or resume the whole job at once, not a piece at a
time.

To make that possible, related processes are collected into a **process group**.
A process group is a set of processes treated as a unit, so that an action — most
often a signal, like the one that stops a job — can be delivered to all of them
together instead of one by one.

## Sessions and the terminal

Process groups are themselves collected into a larger unit: a **session**, tied
to the **terminal** the work is running at.

A terminal can only take input for one job at a time, so within the session one
process group is the **foreground** group: the one connected to the keyboard, the
one typing reaches, the one a stop-or-interrupt keystroke acts on. Any other
groups are in the **background**, running without holding the keyboard. Moving a
job between foreground and background — and starting, stopping, and resuming jobs
— is **job control**.

## A different kind of "session"

The word "session" has two meanings here, and they are unrelated:

- A **job-control session**, described above, organises processes at a terminal
  — which groups share a terminal, which one is in the foreground. It says
  nothing about who anyone is.
- A **logon session** records one sign-in — a single authentication event — and
  ties together every identity token that came from it. It is part of the
  identity model, covered in [Logon sessions](/peios/security-fundamentals/logon-sessions/overview.md).

They often line up in practice — signing in at a terminal creates a logon session,
and the processes run there share a job-control session — but they are separate
things answering separate questions: "which processes share this terminal?" versus
"where did this identity come from?"

## Where to go next

Beyond its place in the tree and its groups, every process carries a bundle of its
security-related settings — its Process Security Block. That is
[The Process Security Block](/peios/using-peios/threads-and-processes/the-process-security-block.md).

---

# The Process Security Block

_Peios / Using Peios / Threads and processes_

> The Process Security Block (PSB) records what a process is — its program's trust, its hardening, and who may operate on it — as distinct from who it acts as.

A process's identity — who it is acting as — is carried on its token, and can
change moment to moment, since a thread can impersonate another principal. A
process has a second aspect that is independent of identity: **what it is**.
What program is it running? How trusted is that program? How hardened is it
against attack? Who is allowed to operate on it?

Those facts are gathered in one place — the **Process Security Block**, or **PSB**.
Every process has one. Where the token answers who this process is acting as, the
PSB answers what this process is. Unlike the token, the PSB never changes when a
thread impersonates: impersonation changes who, never what.

## What the PSB holds

- **Its permanent name.** The process's
  [Process GUID](/peios/using-peios/threads-and-processes/the-process-and-thread-model.md) — the
  never-reused identifier — lives on the PSB.
- **How trusted its program is.** When a process starts running its program, the
  system checks the program's cryptographic signature and records from it how
  trusted the program is. This is the process's **PIP** (Process Integrity
  Protection) label, and it decides which other processes are allowed to
  inspect, signal, or interfere with this one. The barrier is based on what
  program is running, not on who is running it: even a fully privileged process
  cannot disturb a more-trusted one. The full treatment is
  [Process integrity protection](/peios/security-fundamentals/process-integrity-protection/overview.md).
- **How it is hardened.** A set of **mitigations** — restrictions the process
  carries on what it may do with its own memory and code, so that a bug or
  injected code has far less room to do harm. They can only ever be tightened,
  never loosened. The catalog and rules are
  [Process mitigations](/peios/security-fundamentals/process-mitigations/overview.md).
- **Who may operate on it.** Every process has its own security descriptor — the
  rules for who is allowed to act on the process itself: inspect it, signal it, and
  so on. It lives on the PSB alongside the rest.

A few more specialised settings live here too — a process can be marked so that it
may no longer create children, for instance — but those four are the core.

## Inspecting and managing the PSB

Because the PSB is where a process's trust level and hardening live, there is a
dedicated tool for working with it: the **`psb`** command, which inspects a
process's PSB and manages its mitigations and PIP mode.

## Where to go next

The two largest parts of the PSB each have a topic of their own:
[Process integrity protection](/peios/security-fundamentals/process-integrity-protection/overview.md), for the
trust label that governs which processes may interfere with which, and
[Process mitigations](/peios/security-fundamentals/process-mitigations/overview.md), for the self-hardening
flags and how they are applied.

---

# Process creation reference

_Peios / Using Peios / Threads and processes_

> The precise contract for fork, vfork, clone and clone3, the clone sharing flags, the exec family, posix_spawn, and the pidfd calls.

This is the detailed counterpart to
[Creating processes](/peios/using-peios/threads-and-processes/creating-processes.md). That page
explains the ideas; this one gives the exact contract — what each call does, what a
new process inherits, and what each `clone` sharing flag controls.

## fork

`fork()` creates a new process by duplicating the caller. The child is a
near-identical copy that runs independently from the point of the call.

The child **inherits**:

- a private copy of the parent's memory, made lazily (copy-on-write), so the
  duplication is cheap and changes on either side stay invisible to the other
- copies of the parent's open file descriptors — each refers to the same open file
  description, so file offset, status flags, and signal-driven-I/O settings are
  shared between parent and child
- open directory streams and open message-queue descriptors
- open-file-description locks (`flock`, OFD locks) through those shared descriptors
- the parent's signal dispositions (the handler set for each signal)
- the file-creation mask (umask), the current directory, and the root directory
- resource limits and the timer-slack value
- the parent's identity — it begins acting on the same token (see
  [Token lifecycle](/peios/security-fundamentals/tokens/lifecycle.md))
- the parent's mitigations and protection, carried on its
  [PSB](/peios/using-peios/threads-and-processes/the-process-security-block.md)

The child does **not** inherit:

- the parent's PID — it receives a new PID and a new Process GUID
- memory locks (`mlock`, `mlockall`)
- memory regions marked `MADV_DONTFORK` (absent in the child) or `MADV_WIPEONFORK`
  (present but zeroed)
- process resource usage and CPU-time counters — reset to zero
- pending signals — the child's set starts empty
- semaphore adjustments (semadj)
- process-associated record locks (POSIX `fcntl` locks — distinct from the OFD and
  `flock` locks above, which are shared through the inherited descriptors)
- timers (`setitimer`, `alarm`, `timer_create`)
- outstanding asynchronous I/O
- directory-change notifications (`dnotify`)
- the `PR_SET_PDEATHSIG` parent-death-signal setting (reset)
- I/O-port access permissions (`ioperm`)

The child's termination signal — what its parent is notified with when it ends —
is `SIGCHLD`.

The child is created with a **single thread** — the one that called `fork()`. If
the parent had other threads, they do not exist in the child.

## vfork

`vfork()` is a lightweight variant of `fork()` for the one case where the child
will immediately replace itself with another program. It differs in two ways:

- the caller is **suspended** from the call until the child either `exec`s or exits;
- the child runs in the **parent's memory** — no copy is made — until that point.

Because the two share memory and the parent is frozen, the child must do almost
nothing first. The contract: the child must not return from the function that
called `vfork()`, must not modify any variable other than the one holding the
return value, and must not call any other function before a successful `exec` or
`_exit`. Doing otherwise is undefined behaviour.

`vfork` is an optimisation for the create-then-replace pattern. Ordinary code
should use `posix_spawn` (below) rather than reach for it directly.

## clone and clone3

`clone()` is the general creation call. `fork` and thread creation are both
`clone` with particular flag sets; `clone` exposes the choice directly. The caller
gives a set of **flags** controlling what the new task shares with the creator, a
stack for the new task, and locations for thread-ID and thread-local-storage
bookkeeping.

`clone3()` is the modern form, taking a structure instead of positional arguments
so it can grow new fields over time without changing the call:

| Field | Purpose |
|---|---|
| `flags` | The sharing flags (below). |
| `pidfd` | Where to store a pidfd for the new child. |
| `child_tid` / `parent_tid` | Where to record the new thread's ID, in the child's and the parent's memory. |
| `exit_signal` | The signal delivered to the parent when the child ends. |
| `stack` / `stack_size` | The new task's stack and its size. |
| `tls` | The new task's thread-local-storage area. |
| `set_tid` / `set_tid_size` | Request specific thread IDs for the new task — a privileged operation (see below). |
| `cgroup` | Place the child directly into a resource group at creation (see resource management). |

The structure is **size-versioned**: the caller passes the size it knows, the
kernel reads up to the size it knows, and any unknown trailing fields must be
zero. This is how new fields are added without breaking existing programs.

Requesting a specific thread ID (`set_tid`) is a **privileged** operation — it
exists for checkpoint-and-restore tools that must recreate a process with its
original ID. The privilege required follows the
[capability model](/peios/advanced-peios/linux-compatibility/dac-neutralization-and-capabilities.md).

## clone sharing flags

Each flag makes the new task **share** something with its creator instead of
getting its own copy. The flags that build threads and control creation:

| Flag | Effect |
|---|---|
| `CLONE_VM` | Share memory rather than taking a private copy. |
| `CLONE_FS` | Share filesystem context — current directory, root directory, file-creation mask. |
| `CLONE_FILES` | Share the open-descriptor table, so opening or closing a descriptor in one is seen by the other. |
| `CLONE_SIGHAND` | Share the table of signal handlers. Requires `CLONE_VM`. |
| `CLONE_THREAD` | Put the new task in the same process (thread group) as the creator. Requires `CLONE_SIGHAND`. |
| `CLONE_SETTLS` | Set the new task's thread-local-storage area. |
| `CLONE_SYSVSEM` | Share the System V semaphore-adjustment list. |
| `CLONE_IO` | Share the I/O context, so the two are accounted as one for disk I/O. |
| `CLONE_PIDFD` | Return a pidfd for the new child. |
| `CLONE_PARENT` | Make the new task a sibling of the creator — its parent becomes the creator's parent, which is also what is signalled when the task ends. An init process (PID 1) cannot use this flag, as it would create unreapable zombies. |
| `CLONE_PARENT_SETTID` / `CLONE_CHILD_SETTID` | Record the new task's ID in the parent's / child's memory. |
| `CLONE_CHILD_CLEARTID` | Clear the recorded thread ID and wake a waiter when the task exits — the mechanism behind joining a thread. |
| `CLONE_UNTRACED` | Prevent a tracer from forcing tracing onto the new child. |
| `CLONE_VFORK` | Suspend the creator until the child `exec`s or exits (the `vfork` behaviour). |

A **thread** is `CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD`
(plus TLS and thread-ID bookkeeping) — everything shared, same process. A `fork`
is the opposite end, with almost nothing shared.

Being in one thread group has consequences beyond shared memory. Every thread
returns the same PID from `getpid` (the thread-group ID), and signal **actions** are
process-wide — an unhandled fatal signal delivered to any thread ends them all —
though each thread keeps its own signal mask. A thread that ends does not notify the
thread that created it and cannot be collected with a wait call; only once every
thread in the group has ended is the process's parent sent `SIGCHLD`. The new thread
shares the creator's parent, and `CLONE_THREAD` requires `CLONE_SIGHAND` (and hence
`CLONE_VM`).

Two further flag groups select **separation** rather than sharing, each documented
where it belongs:

- The `CLONE_NEW*` flags create **namespaces** — separate views of system
  resources (mounts, process IDs, networking, and more), covered under namespaces.
- `CLONE_INTO_CGROUP` places the new process into a **resource group** at creation,
  covered under resource management.

`CLONE_DETACHED` is a historical flag with no effect and is ignored.

## The exec family

An `exec` replaces the program running in the current process with a different one.
The process keeps its PID, its Process GUID, and its identity — only the program
changes (see [Token lifecycle](/peios/security-fundamentals/tokens/lifecycle.md) for the identity detail).

| Call | What it does |
|---|---|
| `execve` | Replace the current program with the one at a given path. |
| `execveat` | Replace it with a program named relative to an open directory, or — with `AT_EMPTY_PATH` — by an open file descriptor directly. `AT_SYMLINK_NOFOLLOW` refuses a final symbolic link. |
| `fexecve` | Replace it with the program referred to by an open file descriptor (a thin wrapper over `execveat`). |

### What survives an exec

A successful `exec` keeps the process itself but replaces the program it runs.
Preserved across the call:

- the PID, the parent PID, and the Process GUID
- the process's identity (its token), its process group and session, and its
  controlling terminal
- open file descriptors — except those marked close-on-exec, which are closed
- the current directory, root directory, file-creation mask, and resource limits
- pending signals, and the dispositions of signals that were ignored or left at
  their default

Reset or discarded:

- all memory — the program's mappings, stack, heap, and data are replaced; memory
  locks are dropped and `MADV_*` region markings are gone
- every thread except the one calling `exec` — the new program starts single-threaded
- handlers for caught signals — reset to the default; the alternate signal stack is
  dropped
- attached System V shared memory, POSIX shared-memory mappings, open POSIX
  message-queue descriptors and named semaphores, and in-process synchronisation
  objects (mutexes, condition variables)
- POSIX timers, outstanding asynchronous I/O, open directory streams, and registered
  exit handlers
- the floating-point environment, and the process name (set to the new program)

What an `exec` does to a process's identity when the program file is marked to run
as another principal is **not** the Linux set-user-ID model — Peios handles that
through the token, covered in
[setuid and uid0](/peios/advanced-peios/linux-compatibility/setuid-and-uid0.md).

Whether a program is allowed to run at all is a separate, execution-policy
question — covered under binary signing — not part of these calls.

## posix_spawn

`posix_spawn()` is the standard library function for the create-then-replace
pattern: it makes a new process and runs a named program in it with a single call,
with controls for arranging the child's file descriptors and a few attributes
first. It is built on the calls above and is the recommended way to launch a
program, in preference to assembling `fork`/`vfork` and `exec` by hand.

## Process handles (the pidfd family)

A **pidfd** is a file descriptor referring to one specific process — the reliable
handle introduced in
[Creating processes](/peios/using-peios/threads-and-processes/creating-processes.md). `CLONE_PIDFD`
hands one back at creation; a few calls work with them afterwards:

| Call | What it does |
|---|---|
| `pidfd_open` | Obtain a pidfd for a process that already exists, given its PID. |
| `pidfd_send_signal` | Send a signal to the process through its pidfd — with no risk of the PID having been recycled for a different process in between. |
| `pidfd_getfd` | Duplicate one of the target process's open file descriptors into the caller. |

`pidfd_getfd` reaches into another process, so it is not something any process may
do to any other: it is gated by the right to act on the target, governed by that
process's [security descriptor](/peios/using-peios/threads-and-processes/the-process-security-block.md).

## See also

- [Creating processes](/peios/using-peios/threads-and-processes/creating-processes.md) — the conceptual counterpart to this page.
- [Process lifecycle reference](/peios/using-peios/threads-and-processes/process-lifecycle-reference.md) — ending a process and collecting its result.
- [Thread operations reference](/peios/using-peios/threads-and-processes/thread-operations-reference.md) — per-thread calls: TIDs, exit notification, TLS.

---

# Process lifecycle reference

_Peios / Using Peios / Threads and processes_

> The precise contract for ending a process and collecting its result — exit, _exit and exit_group, the exit-status encoding, and the wait family.

This is the detailed counterpart to
[Process lifecycle](/peios/using-peios/threads-and-processes/process-lifecycle.md). That page
explains how a process ends and is cleaned up; this one gives the exact calls — how
a process terminates, what its exit status encodes, and how a parent collects it.

## Ending a process

A process can end itself in a few ways, differing in how much tidying happens first:

| Call | What it does |
|---|---|
| `exit()` | The orderly end. Runs every handler registered with `atexit`/`on_exit` (in reverse order of registration), flushes and closes the standard I/O streams, removes temporary files, then terminates. |
| `_exit()` / `_Exit()` | The immediate end. Terminates at once — no registered handlers run and the standard I/O streams are not flushed — though open file descriptors are still closed. Used when a process must stop without running cleanup, such as a child after a failed `exec`. |
| `exit_group()` | Ends the **whole process** — every thread in it — not just the calling one. This is what ordinary termination uses: a normal `exit()`, and a return from the program's entry point, both end the entire process. |

Returning from the program's entry point is equivalent to calling `exit()` with the
returned value.

At the lowest level the raw `_exit` system call ends only the calling thread; the
library `_exit` and ordinary termination route through `exit_group` so the whole
process ends. Ending one thread while the rest of the process keeps running is the
unusual case.

A process can also be ended **from outside**, by a signal rather than by a call of
its own — covered under signals. Either way, it leaves an exit status behind.

## The exit status

When a process ends it leaves a small **exit status** for its parent to read. It
records one of two outcomes:

- **Ended on its own** — carries an exit code: the low 8 bits of the value passed to
  `exit`/`_exit` (so 0–255, with 0 conventionally meaning success).
- **Ended by a signal** — carries the number of the signal that ended it, plus a
  flag for whether the process produced a core dump.

The status is a packed value, read not directly but through a set of macros:

| Macro | Tells you |
|---|---|
| `WIFEXITED` | the process ended on its own — if so, `WEXITSTATUS` gives its exit code |
| `WIFSIGNALED` | the process was ended by a signal — if so, `WTERMSIG` gives the signal number and `WCOREDUMP` whether it dumped core |
| `WIFSTOPPED` | the process was **stopped** (not ended) by a signal — `WSTOPSIG` gives which |
| `WIFCONTINUED` | the process was resumed by a continue signal |

## Collecting the result

A parent collects a finished child's status — and in doing so clears the
[zombie](/peios/using-peios/threads-and-processes/process-lifecycle.md) — with one of the wait
calls. They differ in which child they target and how much they report:

| Call | What it offers |
|---|---|
| `wait` | Wait for **any** child to end; return its PID and status. The simplest form. |
| `waitpid` | Wait for a chosen target (see below). Options adjust the behaviour: `WNOHANG` returns immediately if nothing is ready (a poll rather than a wait), and `WUNTRACED`/`WCONTINUED` also report children that have stopped or resumed, not only ended. |
| `waitid` | The most precise form. It can target a child by a **pidfd** (`P_PIDFD`) — the reliable handle from [Creating processes](/peios/using-peios/threads-and-processes/creating-processes.md) — as well as by PID, by process group, or any child. It reports through a structured record (which child, and what happened: ended, killed, core-dumped, stopped, continued). Which kinds of change to wait for are chosen explicitly — `WEXITED` for children that have ended, `WSTOPPED` for those stopped by a signal, `WCONTINUED` for those resumed — and its `WNOWAIT` option peeks at the result while leaving the child collectable again later. |
| `wait4` | Like `waitpid`, and additionally returns the child's **resource usage** — processor time consumed, peak memory, and so on. |

`waitpid`'s target is chosen by a single number: a positive value waits for that
exact PID; `-1` for any child; `0` for any child in the caller's own process group;
and a value below `-1` for any child in the process group whose ID is its absolute
value.

Two further options matter for threads and other `clone`-created children, which may
not notify their parent with `SIGCHLD` the way an ordinary child does: `__WCLONE`
waits only for such clone children, and `__WALL` waits for every child regardless
of type.

## See also

- [Process lifecycle](/peios/using-peios/threads-and-processes/process-lifecycle.md) — the conceptual counterpart to this page.
- [Process creation reference](/peios/using-peios/threads-and-processes/process-creation-reference.md) — fork, clone, exec, and the pidfd calls.

---

# Thread operations reference

_Peios / Using Peios / Threads and processes_

> The per-thread operations a program uses directly — reading a thread's ID, the exit-notification mechanism behind thread joins, and thread-local storage.

This is the thread-level companion to
[the process and thread model](/peios/using-peios/threads-and-processes/the-process-and-thread-model.md)
— the operations that act on an individual thread rather than on the process as a
whole.

## Thread IDs

Every thread has its own **thread ID** (TID), returned by `gettid`. Within a
multi-threaded process all threads share one PID — the value `getpid` returns,
which is really the **thread-group ID** (TGID) — but each thread's TID is unique.
In a single-threaded process the TID, PID, and TGID are all the same number.

The TID is how the system targets one specific thread rather than the whole
process: sending a signal to a single thread, setting one thread's CPU affinity, or
addressing it under `/proc`. It names a thread the way a PID names a process — and,
like a PID, it is a reference, not an identity. What a thread is acting as is its
token, never its TID.

The TID is also distinct from the opaque handle a threading library returns when it
creates a thread; that is a library-level identifier, not the kernel's TID.

## Thread-exit notification

A thread can have the kernel notify waiters when it exits — the mechanism threading
libraries build thread join on. `set_tid_address` sets a **clear-tid address** for
the calling thread (and returns its TID). When that thread later terminates, if it
shares memory with other threads, the kernel writes 0 to the integer at that address
and wakes one thread waiting on it (a single futex wake).

A thread waiting for another to finish blocks on that address; the kernel clears it
and wakes the waiter at the exact moment the other thread ends. This is the same
machinery as the `CLONE_CHILD_CLEARTID` flag in
[process creation](/peios/using-peios/threads-and-processes/process-creation-reference.md) — that
flag arranges the clear-tid address at thread creation, where `set_tid_address` sets
it afterwards.

## Thread-local storage

**Thread-local storage** (TLS) gives each thread its own private copy of a variable
— a value that is per-thread rather than shared across the process, even though the
threads share the same memory. The per-thread data lives in an ordinary block of
memory; what makes it per-thread is a register pointing at this thread's block, so
the same code reaches a different copy depending on which thread is running.

The kernel's part is maintaining that per-thread pointer. It is set when the thread
is created (the `CLONE_SETTLS` flag carries the address) and, on x86-64, a running
thread can change it through `arch_prctl` with `ARCH_SET_FS` (the older
`set_thread_area` serves the same purpose on 32-bit). Everything above that — how
thread-local variables are laid out and reached in code — is handled by the compiler
and the threading library.

## See also

- [The process and thread model](/peios/using-peios/threads-and-processes/the-process-and-thread-model.md) — the conceptual counterpart to this page.
- [Process creation reference](/peios/using-peios/threads-and-processes/process-creation-reference.md) — the clone flags that create threads and arrange TLS and clear-tid addresses.
