A numeric PID names a process only while that PID remains assigned to it. After termination and reaping, Linux can reuse the number for another process. A PID file descriptor, or pidfd, instead holds a kernel reference to a specific task, so later operations can target that task without resolving its numeric PID again.
This distinction removes a class of time-of-check/time-of-use races from process management. It does not make a process immortal, grant extra permissions, or turn every process operation into a portable descriptor API.
PID reuse creates an identity boundary
Code that checks a PID and later calls kill(pid, sig) performs two operations against a reusable numeric name. If the original process exits between those operations and the PID is recycled, the later call can refer to a different process.
pidfd_open() changes the second half of that model. Given an existing PID, it returns a file descriptor associated with the referenced task. Once acquired, that descriptor remains tied to that task rather than following future reuse of the numeric PID.
int pidfd = syscall(SYS_pidfd_open, pid, 0);
if (pidfd == -1)
handle_error();For newly created children, clone() or clone3() with CLONE_PIDFD can return a pidfd as part of creation. That route avoids the acquisition interval that exists when a caller first creates or identifies a process and only later calls pidfd_open().
Signaling uses the descriptor identity
pidfd_send_signal() sends a signal through a pidfd. The kernel resolves the target from the descriptor, not from a fresh lookup of a numeric PID.
if (syscall(SYS_pidfd_send_signal, pidfd, SIGTERM, NULL, 0) == -1)
handle_error();If the referenced process has terminated and can no longer receive the signal, the call fails rather than redirecting the operation to a later process that happens to own the same numeric PID. Normal signal permission checks still apply.
This property is narrower than a lifetime guarantee. Holding a pidfd does not keep the task running. It preserves target identity for interfaces that accept the descriptor.
Exit state integrates with descriptor polling
A process pidfd can be monitored with poll(), select(), or epoll(). Without PIDFD_THREAD, readiness is associated with termination of the process represented by the thread group; current Linux exposes EPOLLIN when that task becomes a zombie and EPOLLHUP after it is reaped.
struct pollfd pfd = {
.fd = pidfd,
.events = POLLIN
};
int rc = poll(&pfd, 1, -1);The descriptor is an event source, not a byte stream. read() on a pidfd is not the interface for retrieving exit status.
For a child process, waitid() accepts P_PIDFD and the pidfd value. This joins stable identity with the normal child-status mechanism.
siginfo_t si = {0};
if (waitid(P_PIDFD, pidfd, &si, WEXITED) == -1)
handle_error();A pidfd for an unrelated process can still be polled for exit, but waitid(P_PIDFD, ...) does not erase the parent-child requirement of process waiting.
Thread references have a separate mode
Linux 6.9 added PIDFD_THREAD to pidfd_open(). With that flag, the descriptor refers to a specific thread rather than the process represented by its thread-group leader.
That distinction affects polling and signaling. A thread pidfd can become readable when that particular thread exits, even while other threads remain. pidfd_send_signal() can also target the scope represented by the pidfd, with Linux 6.9 flags providing explicit thread, thread-group, and process-group scopes.
Code that needs process lifetime rather than individual-thread lifetime should not treat these forms as interchangeable.
A pidfd is not a capability bypass
Descriptor identity and authorization are separate properties. Operations such as pidfd_send_signal() still enforce their permission rules, and other pidfd-consuming interfaces impose their own checks.
Passing a pidfd over a UNIX domain socket can transfer a reference to the same task, just as descriptor passing transfers other kernel objects. The receiver does not automatically gain every operation that could conceptually apply to that task.
The close-on-exec flag is set on a pidfd returned by pidfd_open(), limiting accidental inheritance across execve() unless the application deliberately changes descriptor flags.
Stable identity is the central property
pidfds replace repeated numeric-PID resolution with a descriptor reference acquired at a specific point in time. That reference can participate in signaling, readiness polling, child waiting, and several other Linux process APIs.
The useful guarantee is identity stability, not continued existence. A process may exit while its pidfd remains open, and operations then report state or failure according to their own contracts. That boundary lets process supervisors separate the question of which task an operation names from the question of whether that task is still able to satisfy the operation.