プロセスを終了してPIDが再利用されないようにする方法

プロセスを終了してPIDが再利用されないようにする方法

たとえば、次のようなシェルスクリプトがあるとします。

longrunningthing &
p=$!
echo Killing longrunningthing on PID $p in 24 hours
sleep 86400
echo Time up!
kill $p

それはうまくいくでしょうか?プロセスが早期に終了し、対応するPIDがリサイクルされた可能性があるという事実に加えて、これは、いくつかの無実の作業が信号キューから爆弾を受けたことを意味します。実際、これは重要かもしれませんが、まだ心配です。長時間実行されているアイテムをハックして自分で終了したり、FSでPIDを維持/削除したりすることは大丈夫ですが、ここでは一般的なケースを考えています。

ベストアンサー1

次のコマンドを使用することをお勧めしますtimeout(利用可能な場合)。

timeout 86400 cmd

現在(8.23)GNU実装は、少なくともalarm()サブプロセスを待っている間、または同等の機能を使用して動作します。SIGALRMリターンとシャットダウンの間の転送を妨げないようです(効果的にキャンセルwaitpid()timeoutアラーム)。この小さなウィンドウの間、timeoutメッセージはstderrに書き込まれる可能性があります(たとえば、子プロセスがコアをダンプする場合)、競合ウィンドウはさらに広くなります(stderrがパイプ全体の場合は無制限)。

個人的にこの制限を受けることができます(将来のバージョンでは修正される可能性があります)。timeout正しい終了状態を報告し、他の特別な場合(たとえば、起動時にSIGALRMがブロック/無視され、他の信号を処理するなど)を手動で実行するよりも、より良い処理を行うために特別な注意が必要です。

おおよそ次のように書くことができますperl

perl -MPOSIX -e '
  $p = fork();
  die "fork: $!\n" unless defined($p);
  if ($p) {
    $SIG{ALRM} = sub {
      kill "TERM", $p;
      exit 124;
    };
    alarm(86400);
    wait;
    exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
  } else {exec @ARGV}' cmd

timelimitコマンドがありますhttp://devel.ringlet.net/sysutils/timelimit/timeoutGNUより数ヶ月早い)。

 timelimit -t 86400 cmd

この方法は同様のメカニズムを使用しますが、子の死を検出するためにハンドラalarm()(停止した子を無視)をインストールします。SIGCHLDまた、実行する前に警告をキャンセルしwaitpid()(保留中の場合は転送をキャンセルしませんが、作成されSIGALRMた方法では問題ありません)、終了します。今後呼び出されますwaitpid()(したがって再利用されたPIDは終了できません)。

ネットワークパイプコマンドがもう1つありますtimelimit。他のすべての方法よりも数十年前のこの方法は代替アプローチをとりますが、停止したコマンドに対しては正しく機能せず、タイムアウト時に終了ステータスを1返します。

あなたの質問に対するより直接的な答えで、次のことができます。

if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
  kill "$p"
fi

つまり、そのプロセスがまだ子プロセスであることを確認してください。同様に、プロセスが終了し、そのpidが他のプロセスで再利用される可能性がある小さな競合期間(psプロセス状態の検索とkill終了の間)があります。

一部のシェル(zsh、、、、 )を使用すると、pidbashmksh代わりに作業仕様を渡すことができます。

cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status

これは、1つのバックグラウンドジョブのみを作成する場合にのみ機能します(そうでなければ、正しいジョブ仕様を確実に取得することは必ずしも可能ではありません)。

これが問題の場合は、新しいシェルインスタンスを起動してください。

bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd

これは、子が死んだときにシェルが割り当てリストから割り当てを削除するために機能します。ここでは競合ウィンドウがあってはなりません。これは、シェルが呼び出されたときにkill()SIGCHLDシグナルがまだ処理されておらず、pidを再利用できないか(待機していないため)、すでに処理されており、pidを使用できないためです。ジョブがプロセステーブルから削除されました(killエラーが報告されています)。拡張のために作業テーブルにアクセスする前に、少なくともSIGCHLDをブロックしてから後でbashブロックを解除してください。kill%kill()

死後も保留中のプロセスを防ぐsleepもう1つのオプションは、orの代わりにパイプを使用することです。cmdbashksh93read -tsleep

{
  {
    cmd 4>&1 >&3 3>&- &
    printf '%d\n.' "$!"
  } | {
    read p
    read -t 86400 || kill "$p"
  }
} 3>&1

コマンドにはまだ競合状態があるため、コマンドの終了状態が失われます。また、cmdfd 4を閉じないと仮定します。

次の競合のないソリューションを実装してみてくださいperl

perl -MPOSIX -e '
   $p = fork();
   die "fork: $!\n" unless defined($p);
   if ($p) {
     $SIG{CHLD} = sub {
       $ss = POSIX::SigSet->new(SIGALRM); $oss = POSIX::SigSet->new;
       sigprocmask(SIG_BLOCK, $ss, $oss);
       waitpid($p,WNOHANG);
       exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
           unless $? == -1;
       sigprocmask(SIG_UNBLOCK, $oss);
     };
     $SIG{ALRM} = sub {
       kill "TERM", $p;
       exit 124;
     };
     alarm(86400);
     pause while 1;
   } else {exec @ARGV}' cmd args...

(他のタイプのコーナーケースを処理するには改善が必要ですが)

別の競合のないアプローチは、プロセスグループを使用することです。

set -m
((sleep 86400; kill 0) & exec cmd)

ただし、端末デバイスへの入出力が関連している場合は、プロセスグループを使用すると副作用が発生する可能性があります。また、によって作成された他のすべての追加プロセスを終了できるという利点もありますcmd

おすすめ記事