A numeric PID names a process through a namespace lookup. That number can later be reused after the process exits and is reaped. Linux PID file descriptors change the boundary: a pidfd is a file descriptor referring to a particular task, so later operations can target that reference instead of resolving the numeric PID again.

This is Linux-specific process-management behavior. It is not a property of POSIX process identifiers or of the C language.

Numeric PIDs are names, not durable handles

A common control path records a PID, waits for some external condition, then calls kill(pid, sig). The two operations are separated in time. If the original process has exited and its PID has been recycled before the signal operation, a fresh numeric lookup can designate a different process.

Checking existence with kill(pid, 0) does not make a later kill(pid, sig) refer to the same process. Each operation performs its own lookup. Any gap between a check and a later action leaves process identity dependent on the state at that later lookup.

A pidfd changes this from a name-based protocol to a handle-based one. pidfd_open(pid, 0) asks the kernel for a descriptor referring to the task currently designated by pid. If successful, subsequent pidfd-aware operations use that descriptor as the identity reference.

int pfd = syscall(SYS_pidfd_open, pid, 0);
if (pfd == -1)
    /* handle lookup failure */;

if (syscall(SYS_pidfd_send_signal, pfd, SIGTERM, NULL, 0) == -1)
    /* handle signaling failure */;

The initial pidfd_open() still resolves a numeric PID. The stability property begins after a successful descriptor acquisition; it does not retroactively make discovery of the PID race-free.

Descriptor lifetime and process lifetime are distinct

Holding a pidfd does not keep the target process running. The target can exit while the descriptor remains open. The descriptor preserves a reference suitable for supported process operations and lifecycle observation, not a lease on execution.

This distinction is visible through polling. A process-oriented pidfd can participate in poll(), select(), or epoll(). When the referenced task terminates and becomes a zombie, the descriptor reports readable state. After the task is reaped, polling also exposes hangup state according to Linux pidfd semantics.

The descriptor itself is not a byte stream. A normal read() on a pidfd is not the mechanism for consuming an exit record. Readiness indicates lifecycle state; process status is obtained through interfaces such as waitid() when their relationship and eligibility rules are satisfied.

Signaling uses identity without repeating PID lookup

pidfd_send_signal() directs a signal to the process or thread referenced by a pidfd, subject to Linux permission and namespace checks. This removes the identity race inherent in carrying a numeric PID across a time gap and then resolving it again for signaling.

The call does not weaken signal authorization. A stable reference answers which task is targeted; credentials, capabilities, PID namespaces, and signal-specific rules still determine whether the operation is permitted.

Failure after target termination is also meaningful. A pidfd can remain open after the task is gone, but pidfd_send_signal() cannot deliver a signal to a task that no longer exists. Stable identity prevents accidental retargeting; it does not turn a terminated task into a valid signal recipient.

Creation timing determines the remaining race surface

For an already existing process, pidfd_open() is the direct acquisition interface. There is necessarily a lookup boundary between obtaining a numeric PID from some source and successfully opening its pidfd. Software that creates the child itself can use clone() or clone3() with CLONE_PIDFD where appropriate, receiving process creation and the pidfd through one kernel operation.

That difference matters for supervisors. A design that creates a child and immediately needs a durable process reference can avoid publishing a bare PID as the primary control capability. A design attaching to an independently discovered process still needs to treat discovery and descriptor acquisition as separate phases.

pidfd_open() also has lifecycle conditions around already terminated children. Code that needs precise child creation and reaping behavior should prefer creation-time pidfd acquisition rather than assuming every zombie can always be converted later into an equivalent handle.

File-descriptor rules now apply to process references

Once process identity is represented by a descriptor, descriptor ownership becomes part of process-control design. The pidfd consumes a file-descriptor slot, can be closed, can be passed through descriptor-passing mechanisms where permitted, and is created with close-on-exec set by pidfd_open().

That representation also lets event loops combine process termination with other descriptor readiness. A supervisor can register pidfds beside sockets, pipes, signalfd, or timerfd without translating process exit into a separate numeric-PID polling loop.

Descriptor readiness does not imply that every process operation becomes ordinary I/O. Interfaces such as pidfd_send_signal(), pidfd_getfd(), waitid(), setns(), and process_madvise() retain their own contracts and permission checks. The pidfd supplies a common identity carrier, while each operation defines its own semantics.

Stable identity narrows a specific class of control races

PID reuse is a naming problem created by treating a recyclable number as if it were a durable object reference. PID file descriptors address that problem by letting Linux bind later operations to the task selected when the descriptor was acquired.

The boundary is intentionally narrower than a general process transaction. A pidfd does not freeze credentials, serialize lifecycle transitions, keep a task alive, or make arbitrary multi-step supervision atomic. It gives process control a stable identity handle, after which signaling, waiting, polling, and other supported operations can apply their own contracts without another numeric PID lookup.