停止したいプロセスのPIDを調べ、少し後でその番号へシグナルを送る。この短い間にも、相手が終了し、同じPIDが別のプロセスに割り当てられる可能性があります。番号が同じでも、相手まで同じとは限りません。
Linuxのpidfdは、特定のプロセスを指すファイルディスクリプタです。PIDという番号で相手を探し直す代わりに、取得した参照を使い続けます。ただし、いつ・どの相手の参照を取得したかという問題まで、自動で解決するわけではありません。
簡単にまとめると?
- 数値のPIDは、元のプロセスが終了して回収された後に再利用されることがあります。古い番号だけを頼りにすると、別の相手へ操作する危険があります。
- pidfdは取得した特定のプロセスへの安定した参照です。pidfd_send_signalは、同じPIDを後で使う別のプロセスへ参照を切り替えません。
- 取得前の相手の取り違え、シグナルを送る権限、子プロセスの回収は別に考えます。「終了を監視できる」と「そのプロセスをwaitできる」も同じではありません。
番号を覚えることと、相手を参照すること
PIDは、実行中のプロセスを識別する番号です。しかし、その番号が永久に一つのプロセスへ固定されるわけではありません。例えば、監視プログラムがPID 42のプロセスAを確認した後、Aが終了して回収され、プロセスBへ42が再利用されたとします。ここでkill(42, ...)を呼ぶと、操作の宛先はBになります。実際にシグナルが送れるかは権限などにも依存しますが、Aを指すはずだった番号の意味は変わっています。
pidfd_open(pid, 0)は、呼び出した時点でそのPIDが指すプロセスへのファイルディスクリプタを返します。Aが存在する間にAのpidfdを確保しておけば、その参照が後からBへすり替わることはありません。

このpidfdをpidfd_send_signalへ渡すと、参照先のプロセスにシグナルを送れます。参照先が終了して回収済みなら、存在しないことを示すESRCHになります。同じ番号のBを探して送り直す動作にはなりません。
pidfdを開く前の競合は残る
ここで大事なのは、「Aのpidfdを取得できている」という前提です。外部から読んだ古いPIDを長時間保持し、その後でpidfd_openを呼んだなら、その時点ですでに番号がBへ再利用されている可能性があります。この場合に取得されるのはBの参照です。
pidfdは、取得後の参照を安定させる仕組みです。古いPID一覧から本来の相手を推測してくれる仕組みではありません。相手を発見した時点と、安定した参照を得る時点の間に何が起きうるかを確認する必要があります。
自分がforkした子なら、終了後にも回収待ちのゾンビ状態が残り、その間はPIDが再利用されない性質を使える場合があります。ただし、マニュアルは保証の条件を明示しています。SIGCHLDを明示的にSIG_IGNへ設定していないこと、SA_NOCLDWAITを指定していないこと、別スレッドやハンドラなどが先に子を回収していないことです。
この保証が成り立たない設計では、生成時にCLONE_PIDFDを使って子と参照を一緒に得る方法が案内されています。単にforkの直後ならいつでも安全、と一般化せず、終了と回収を誰が担当するかまで含めて選びます。
終了の監視はできても、普通のファイルのようには読めない
pidfdはpoll、select、epollの監視対象にできます。今回扱うpidfd_open(pid, 0)で取得したプロセス単位の参照では、スレッドグループの最後のスレッドが終了したときに、終了を読み取り可能という形で通知します。マニュアル6.19では、ゾンビ化でEPOLLIN、回収でEPOLLHUPになると説明されています。
ただし、この「読み取り可能」は、ファイルから終了理由のデータをreadできるという意味ではありません。同マニュアルの実装説明では、pidfdへのreadはEINVALで失敗します。終了状態を得る経路と、イベントループへ通知する経路を分けて理解する必要があります。
pidfdが呼出し側の子プロセスを指していれば、waitidで待つことができます。任意の他人のプロセスについて、pidfdを持つだけで親のように終了状態を回収できるわけではありません。
安全な宛先と、操作の成功は別の条件
pidfdを持っていても、相手にシグナルを送る権限が追加されるわけではありません。権限がなければEPERMとなり、PID名前空間に関する条件もあります。また、シグナル送信の成功を、相手の後処理や終了まで完了した証拠とみなさないことも大切です。終了が必要な操作なら、その後の終了通知を別に確認します。
監視プログラムを見直すなら、まず「相手を何で識別しているか」、次に「安定した参照をいつ取得するか」、最後に「送信・終了検知・子の回収をそれぞれどう確認するか」を分けて追うと、競合が隠れる場所を見つけやすくなります。
pidfd_openはLinux 5.3、pidfd_send_signalはLinux 5.1で導入されています。新しいフラグにはそれぞれ追加時期があり、この記事はflagsを0としたプロセス単位の扱いを中心にしています。スレッド単位のPIDFD_THREADや、プロセスグループへ送る新しいフラグまで同じ条件だとみなさず、利用するカーネルの対応を確認してください。
出典
- Linux man-pages 6.19: pidfd_open(2) — 取得、子の回収条件、終了監視、waitid、導入時期。
- Linux man-pages 6.19: pidfd_send_signal(2) — PID再利用との競合、安定した参照、エラーと権限。両資料は同じマニュアルプロジェクトに属します。
