A numeric PID can name one process now and a different process later. Linux PID file descriptors change that boundary: a pidfd is a file descriptor that refers to a task, so process operations can remain attached to the intended kernel object instead of repeating a lookup by numeric PID.

This distinction matters in supervisors, service managers, container runtimes, and other software that observes process lifecycles. A PID is useful for naming, but it is not a durable capability. A pidfd can participate in file-descriptor APIs and can be retained across the interval between identifying a process and acting on it.

PID reuse creates an identity race

A common process-control sequence stores a PID and uses it later:

observe PID 4812
      ├── target exits
      ├── PID 4812 becomes reusable
      └── another task receives PID 4812
          later operation by PID

The dangerous part is not PID reuse itself. Reuse is normal kernel behavior. The problem appears when an application assumes that a number observed earlier still identifies the same task at a later operation.

Checking /proc/4812, then issuing kill(4812, SIGTERM), does not turn the two operations into one atomic identity check. The target can exit between them. A fresh process can subsequently receive that PID.

A pidfd moves the identity reference into a kernel-managed file descriptor:

PID 4812 ── pidfd_open() ──► fd 7
                              ├── poll / epoll
                              ├── pidfd_send_signal()
                              └── close()

Once obtained, fd 7 refers to the task associated with that pidfd rather than asking later operations to resolve PID 4812 again.

pidfd_open attaches a descriptor to an existing task

pidfd_open() obtains a PID file descriptor for an existing task. The returned descriptor has close-on-exec set. Linux also supports creating a child and obtaining its pidfd as part of process creation through clone() or clone3() with CLONE_PIDFD.

Those two paths address different moments in a process lifecycle:

existing task:
    numeric PID → pidfd_open() → pidfd

new child:
    clone3(CLONE_PIDFD) → child + pidfd

For a task that already exists, pidfd_open() is the direct interface. For code that creates the child itself, obtaining the descriptor during creation avoids a separate post-creation PID lookup.

A pidfd is still subject to normal file-descriptor lifecycle rules. Duplicating it with dup() creates another descriptor for the same underlying open file description, and close() releases a descriptor reference.

Process exit becomes an event-loop input

PID file descriptors are pollable. poll(), select(), and epoll can report readiness when the referenced task terminates and becomes a zombie. After the task is reaped, polling can report a hangup event.

That lets process lifecycle state share an event loop with sockets, timers, and other pollable objects:

epoll
  ├── listening socket
  ├── client socket
  ├── timerfd
  └── pidfd ──────────► child exit

The readiness event is a lifecycle signal, not a byte stream. Reading a pidfd does not return an exit record; read() on the descriptor fails with EINVAL in the current Linux implementation.

For child processes, waitid() can use P_PIDFD with a suitable pidfd. The pidfd provides stable identity, while the wait interface provides child status and performs the process-waiting semantics.

Signaling through a pidfd avoids a second PID lookup

pidfd_send_signal() sends a signal to the task referenced by a pidfd. Its main identity property differs from kill():

kill(pid, sig)
    numeric PID lookup at operation time

pidfd_send_signal(pidfd, sig, ...)
    operation through retained task reference

Permission checks still apply. A pidfd is not a bypass around Linux signal authorization. It changes target identification, not the security policy governing signal delivery.

This separation is important for privileged supervisors. Holding a stable reference prevents accidental retargeting caused by PID reuse, but it does not grant arbitrary authority over the referenced task.

A proc directory descriptor is not equivalent

Opening /proc/<pid> can also retain a reference related to a process, and some pidfd operations accept descriptors obtained from procfs. It is not a full replacement for a pidfd created by the dedicated interfaces.

A descriptor for /proc/<pid> depends on procfs being mounted. It is also not pollable for process exit and cannot be used with waitid() as a PID file descriptor in the same manner as a descriptor from pidfd_open().

The dedicated pidfd API therefore provides more than a persistent-looking pathname handle. It integrates process identity with the file-descriptor event and control model.

pidfds do not make process control atomic as a whole

Stable target identity removes an important race, but it does not make a multi-step control policy atomic. A supervisor can still observe application state, make a decision, and find that other state changed before its next action.

For example:

inspect service state
        ├── policy decision
        └── pidfd_send_signal()

The pidfd keeps the signal attached to the intended task. It does not freeze that task, its credentials, its namespaces, or external service state between the inspection and signal delivery.

The distinction keeps the guarantee precise: pidfds stabilize process identity across supported operations. Higher-level synchronization and policy consistency remain application concerns.

File-descriptor identity fits Linux event-driven supervision

Linux exposes many asynchronous mechanisms as file descriptors. pidfds extend that model to process lifecycle observation and selected process operations.

A supervisor can retain a pidfd, register it with epoll, react to exit readiness, use child-wait interfaces where applicable, and direct supported operations through the same stable task reference. Numeric PIDs remain useful for display, logs, and interfaces that require them, but they no longer need to carry the full burden of process identity.

The practical boundary is concise: a PID is a reusable number in a PID namespace; a pidfd is a retained kernel reference that can be polled and used by pidfd-aware system calls.