A numeric Linux PID can be reused after its process exits. A PID file descriptor instead refers to a specific task, so later operations through that descriptor do not silently retarget a different process that receives the same numeric PID.
This changes process identity from a lookup repeated at each operation into a kernel-held reference with descriptor semantics. The distinction matters for signaling, exit monitoring, and event loops that retain process handles across asynchronous work.
Numeric PIDs are names in a reusable namespace
Interfaces such as kill(pid, sig) resolve a numeric PID when the operation executes. If software records a PID, waits while other work occurs, and later uses that number, the original process may already have exited and the number may have been assigned again.
Existence checks do not make a later PID-based operation atomic with the check. A successful check followed by a separate signal still leaves a state transition between the two calls.
A pidfd removes that particular identity race. pidfd_open() obtains a descriptor referring to the task selected at acquisition time. If that task later exits, the descriptor does not begin referring to a replacement process.
Signaling uses the retained reference
pidfd_send_signal() targets the task represented by a pidfd rather than performing a fresh numeric-PID lookup.
int pidfd = syscall(SYS_pidfd_open, pid, 0);
if (pidfd == -1)
return -1;
if (syscall(SYS_pidfd_send_signal, pidfd, SIGTERM, NULL, 0) == -1)
return -1;Permission checks still apply. A stable reference does not grant authority that the caller lacks.
If the referenced process has terminated, pidfd_send_signal() fails with ESRCH; it does not redirect the signal to a later process that happens to carry the same PID. This is the core identity property supplied by the descriptor.
Exit state is pollable
A pidfd can participate in poll(), select(), and epoll. For a process pidfd, readiness is reported when the referenced process terminates and becomes a zombie. After the process is reaped, polling can additionally report hangup state.
struct pollfd pfd = {
.fd = pidfd,
.events = POLLIN,
};
int rc = poll(&pfd, 1, -1);
if (rc > 0 && (pfd.revents & POLLIN)) {
/* referenced process has terminated */
}The descriptor is not a byte stream carrying an exit record. read() on a pidfd fails with EINVAL in the documented implementation. Readiness is a process-state notification attached to the descriptor.
That shape fits event loops already multiplexing sockets, timerfds, eventfds, or signalfds. Process termination can occupy the same readiness set without periodic numeric-PID probes.
Readiness and reaping are separate operations
A readable pidfd indicates termination; it does not by itself reap a child. When the pidfd refers to a child of the caller, waitid() can use P_PIDFD to obtain child status and perform the corresponding wait operation.
This separation preserves the existing child-reaping model. The pidfd supplies stable identity and pollability, while waitid() supplies wait semantics and status collection.
For a process that is not the caller’s child, possessing a pidfd does not turn it into a waitable child. Descriptor identity does not rewrite process ancestry.
Descriptor lifetime is independent of PID reuse
Closing the pidfd releases the caller’s descriptor reference. Keeping it open preserves a handle to the same task identity even after the numeric PID namespace can move on.
The descriptor therefore has a different lifetime model from storing an integer in application memory. The integer remains syntactically usable after its original referent disappears; the pidfd remains semantically tied to the original referent and reports operations against that state.
pidfd_open() also sets close-on-exec on the returned descriptor. Retention across an image replacement requires an explicit change to that descriptor flag.
Thread pidfds add a narrower identity scope
Linux also supports pidfds referring to individual threads through interfaces and flags that request thread scope. That scope changes termination readiness: a thread pidfd can become readable when the referenced thread exits even while other threads in the thread group remain alive.
Process-scoped and thread-scoped pidfds therefore should not be treated as interchangeable handles. Both provide stable identity, but the object whose lifetime they track is different.
Signal scope can also depend on the pidfd type and signal flags. Code that stores pidfds as generic task handles needs to retain the intended scope as part of its own state model.
Stable identity does not imply unrestricted inspection
A pidfd is a reference, not a capability that bypasses Linux permission boundaries. Operations performed through pidfd-related system calls retain their own access checks, namespace constraints, and target-state requirements.
It also does not freeze the target process. Credentials, namespaces, memory mappings, file descriptors, and execution state can change while the process remains the same identity. The guarantee concerns which task the descriptor denotes, not immutability of that task.
The useful boundary is precise: numeric PID reuse can change the referent of a later PID lookup, while an acquired pidfd stays attached to the task selected at acquisition. Linux can then expose signaling and termination readiness against that retained identity without converting process state into a conventional data stream.