A numeric PID is a name from a reusable kernel namespace. Once a process exits and its PID becomes available for reuse, a later process can receive the same number. Linux pidfds add a different form of reference: a file descriptor tied to a specific task rather than a number that must be resolved again at each operation.
That distinction changes the boundary between process discovery and later process control. A program can resolve a PID once with pidfd_open(), retain the resulting descriptor, and use pidfd-aware interfaces without treating the numeric PID as permanent identity.
The descriptor captures a task reference
pidfd_open(pid, flags) asks the kernel for a file descriptor referring to the task identified at that call. On success, the descriptor has close-on-exec set. Linux introduced the system call in 5.3.
The important property is not that the PID stops existing or stops being reusable. The pidfd keeps a reference to the particular task that was resolved. If that task terminates, a pidfd-aware operation does not silently retarget a later process that happens to receive the same numeric PID.
This differs from storing an integer PID and later calling an interface that resolves that integer again. The integer remains useful for display, logging, and APIs that require numeric identifiers, but it does not by itself preserve task identity across time.
Exit becomes pollable state
A process pidfd can participate in poll(), select(), and epoll. When the referenced process terminates and becomes a zombie, polling reports readable state. After the process is reaped, polling can additionally report hangup state.
The descriptor is pollable, but it is not a byte stream. In the current Linux implementation, read() on a pidfd fails with EINVAL. Readiness therefore reports a lifecycle transition associated with the referenced task; it does not indicate buffered payload waiting to be consumed.
This fits process termination into descriptor-oriented event loops without converting exit into a synthetic pipe message. The event loop can monitor sockets, timers, signals, and process references through compatible readiness interfaces while each descriptor retains its own object semantics.
Signaling through the reference closes a PID-reuse race
Traditional kill(pid, sig) names its target with a numeric PID. A design that first checks a process and later calls kill() has a time interval in which the original process can exit and the PID can be recycled.
pidfd_send_signal() accepts a pidfd instead. If the task referenced by that descriptor has terminated, signaling fails rather than selecting another process through numeric PID reuse. Permission checks still apply; a stable reference does not grant authority.
The distinction is identity, not delivery semantics. Signals still follow Linux signal rules, and successful submission does not imply that application-level work associated with a signal has completed.
Acquisition timing still has a boundary
pidfd_open() resolves an already existing task, so the interval before that call is outside the protection supplied by the resulting descriptor. If software obtains a PID from an external source and waits before calling pidfd_open(), the numeric name can become stale before acquisition.
For a direct child, zombie retention can keep the PID from being recycled long enough for the parent to obtain a pidfd, but that depends on process-reaping conditions. Linux also supports creating a child together with a pidfd through clone() or clone3() using CLONE_PIDFD, which removes a separate post-creation lookup from that lifecycle.
The broader constraint is precise: pidfds stabilize identity after a task reference has been acquired. They do not retroactively make an earlier numeric PID observation race-free.
Waiting remains subject to parent-child rules
A pidfd that refers to a child can be supplied to waitid() with P_PIDFD. This connects stable identity to the normal child-waiting machinery.
Holding a pidfd does not make an arbitrary process waitable. Parent-child relationships and the rules of the wait interfaces still govern whether exit status can be collected. Pollability and waitability are related capabilities, not interchangeable ones.
This separation matters for supervisors that observe processes they did not create. A pidfd can provide stable identity and exit readiness even when the caller has no child relationship that permits it to reap the target.
Descriptor lifetime is not process lifetime control
Closing a pidfd releases that reference. It does not terminate the referenced process. Likewise, keeping a pidfd open does not keep a process running after it exits.
The descriptor follows ordinary descriptor ownership rules: it can be duplicated, inherited when close-on-exec behavior permits, passed between processes using descriptor-passing mechanisms, and closed independently of other references. Such transfers can move a stable process reference across a process boundary, but operations performed through it remain constrained by the permissions of the caller and the specific pidfd-aware API.
A pidfd therefore behaves as a capability-shaped reference without being an unconditional capability grant. It identifies a kernel task object stably; authorization remains a separate decision.
Stable identity narrows the process-control contract
Pidfds do not replace every numeric-PID interface, process namespace rule, or parent-child lifecycle. Their narrower contribution is to separate task identity from repeated lookup of a recyclable integer.
That separation has observable consequences. Exit can appear as descriptor readiness, signaling can target the captured task reference, and waiting can use the same reference where child-wait semantics permit it. Once acquisition succeeds, later PID reuse does not redirect those pidfd-aware operations to a different process.
The remaining boundaries stay explicit: acquisition can race before the descriptor exists, permissions still govern control operations, poll readiness carries lifecycle state rather than readable bytes, and descriptor lifetime does not control process lifetime.