web-dev-qa-db-ja.com

すべての子孫プロセスを強制終了します

アプリケーションを書いています。さまざまな外部プロセスを生成する機能があります。アプリケーションを閉じるときに、アプリケーションが生成されたすべてのプロセスを強制終了してください。

簡単そうですね。私のPIDを調べて、プロセスツリーを再帰的に歩き、見えているすべてをボトムアップスタイルで殺します。

ただし、これはworkではありません。特定のケースでは、fooを生成しますが、foobarを生成し、すぐに終了してbarを実行したままにします。 barがアプリケーションのプロセスツリーの一部であったという事実がレコードなしになりました。したがって、アプリケーションはbarを強制終了する必要があることを知る方法がありません。

私が地球上でこれをやろうとする最初の人物にはなれないと確信しています。では、標準的なソリューションは何ですか?プロセスを生成するプロセスが無条件に同じタグを継承するような方法でプロセスに「タグを付ける」ための方法を本当に探していると思います。

(これまでのところ、私が思いつくことができる最善の方法は、別のユーザーとしてアプリケーションを実行することです。そうすることで、そのユーザーに属するすべてのプロセスを無差別に強制終了できます。しかし、これにはあらゆる種類のアクセス許可の問題があります...)

62

更新

これは、質問をより注意深く読んだ方がいいと思われる質問の1つです(この質問に対するほとんどの回答がそうであるように見えますが)。質問の要点を明らかに逃していても、いくつかの良い情報が得られるため、元の回答はそのままにしておきます。

SIDの使用

ここで最も一般的で堅牢なアプローチ(少なくともLinuxの場合)は、PPIDまたはPGIDではなくSID(セッションID)を使用することだと思います。これは子プロセスによって変更される可能性ははるかに低く、シェルスクリプトの場合、setsidコマンドを使用して新しいセッションを開始できます。シェルの外では、setuidシステムコールを使用できます。

セッションリーダーであるシェルの場合、次のようにしてセッション内の他のすべてのプロセスを強制終了できます(シェルはそれ自体を強制終了しません)。

_kill $(ps -s $$ -o pid=)
_

注:引数_pid=_の末尾の等号は、PID列ヘッダーを削除します。

それ以外の場合は、システムコールを使用して、各プロセスに対してgetsidを呼び出すことが唯一の方法のようです。

PIDネームスペースの使用

これは最も堅牢なアプローチですが、欠点はLinuxのみであり、root権限が必要なことです。また、シェルツール(使用されている場合)は非常に新しく、広く利用できません。

PID名前空間の詳細については、次の質問を参照してください- `nsenter:`を使用して子プロセスをjailする信頼できる方法 。ここでの基本的なアプローチは、cloneシステムコールで_CLONE_NEWPID_フラグを使用して(またはunshareコマンドを使用して)、新しいPID名前空間を作成できることです。

PID名前空間のプロセスが孤立すると(つまり、親プロセスが終了すると)、initではなく、最上位のPID名前空間プロセスの親になります。つまり、プロセスツリーをたどることにより、常に最上位プロセスのすべての子孫を特定できます。シェルスクリプトの場合、以下のPPIDアプローチはすべての子孫を確実に殺します。

PID名前空間の詳細:

元の回答

子プロセスを強制終了する

pkillが利用可能であれば、シェルスクリプトでこれを行う簡単な方法は次のとおりです。

_pkill -P $$
_

これにより、現在の特定のプロセスのすべての子が強制終了されます(_$$_は、現在のシェルのPIDに展開されます)。

pkillが利用できない場合、POSIX互換の方法は次のとおりです。

_kill $(ps -o pid= --ppid $$)
_

すべての子孫プロセスを強制終了する

別の状況では、直接の子だけでなく、現在のシェルプロセスのすべての子孫を強制終了したい場合があります。この場合、以下の再帰的なシェル関数を使用して、すべての子孫PIDをリストしてから、それらをkillの引数として渡します。

_list_descendants ()
{
  local children=$(ps -o pid= --ppid "$1")

  for pid in $children
  do
    list_descendants "$pid"
  done

  echo "$children"
}

kill $(list_descendants $$)
_

ダブルフォーク

注意が必要なことの1つは、上記が期待どおりに機能しない可能性があることです。二重fork()テクニックです。これは、プロセスをデーモン化するときに一般的に使用されます。名前が示すように、開始されるプロセスは、元のプロセスの2番目のフォークで実行されます。プロセスが開始されると、最初のフォークが終了し、プロセスが孤立します。

この場合、元のプロセスではなくinitプロセスの子になります。どのプロセスが元の親であったかを特定する確実な方法はないため、これが当てはまる場合、他の特定の手段(PIDファイルなど)がないとプロセスを強制終了することはできません。ただし、この手法を使用した場合は、正当な理由なしにプロセスを強制終了しないでください。

参考文献:

57
Graeme

以下を使用できます。

kill -TERM -- -XXX

ここで、XXXは、強制終了するプロセスグループのグループ番号です。次の方法で確認できます。

 $ ps x -o  "%p %r %c"
 PID   PGID COMMAND
 2416  1272 gnome-keyring-d
 2427  2427 gnome-session
 2459  2427 lightdm-session <defunct>
 2467  2467 ssh-agent
 2470  2427 dbus-launch
 2471  2471 dbus-daemon
 2484  2427 gnome-settings-
 2489  2471 gvfsd
 2491  2471 gvfs-Fuse-daemo
 2499  2427 compiz
 2502  2471 gconfd-2
 2508  2427 syndaemon
 2513  2512 pulseaudio
 2517  2512 gconf-helper
 2519  2471 gvfsd-metadata

プロセスグループIDの詳細については、man setpgid

DESCRIPTION
       All  of  these interfaces are available on Linux, and are used for get‐
       ting and setting the process group ID (PGID) of a  process.   The  pre‐
       ferred,  POSIX.1-specified  ways  of doing this are: getpgrp(void), for
       retrieving the calling process's PGID; and  setpgid(),  for  setting  a
       process's PGID.

       setpgid()  sets  the  PGID of the process specified by pid to pgid.  If
       pid is zero, then the process ID of the calling process  is  used.   If
       pgid is zero, then the PGID of the process specified by pid is made the
       same as its process ID.  If setpgid() is used to move  a  process  from
       one  process  group to another (as is done by some shells when creating
       pipelines), both process groups must be part of the same  session  (see
       setsid(2)  and  credentials(7)).   In  this case, the pgid specifies an
       existing process group to be joined and the session ID  of  that  group
       must match the session ID of the joining process.
23
cuonglm

親がPIDを処理していることがわかっている場合は、pkillを使用してこれを実行できます。

$ pkill -TERM -P 27888

ここで、PPIDは27888です。

pkill manからの抜粋

   -P, --parent ppid,...
          Only match processes whose parent process ID is listed.

スクリプトの私のPIDは何ですか?

これはおそらく次の質問なので、Bashスクリプトで上部の$$を使用してスクリプトのPIDを確認できます。

次のスクリプトがあるとします。

$ more somescript.bash 
#!/bin/bash

echo "top: $$"
sleep 5
echo "bottom: $$"

今私はそれをバックグラウンドで実行します:

$ ./somescript.bash &
[2] 28007
top: 28007

pgrepで確認すると、適切なPIDがあることがわかります。

$ pgrep somescript.bash
28007
$ bottom: 28007

[2]+  Done                    ./somescript.bash

プロセスのPGIDを使用する

このpsコマンドを使用すると、代わりに使用して強制終了できるプロセスPGIDを見つけることができます。

このスクリプトを使用して、killies.bash

$ more killies.bash 
#!/bin/bash

sleep 1000 &
sleep 1000 &
sleep 1000 &

sleep 100

次のように実行します。

$ killies.bash &

チェックインする:

$ ps x -o  "%p %r %c"
  PID  PGID COMMAND
28367 28367 killies.bash
28368 28367 sleep
28369 28367 sleep
28370 28367 sleep
28371 28367 sleep

次に、PGIDを強制終了します。

$ pkill -TERM -g 28367
[1]+  Terminated              ./killies.bash

追加の方法

このSO Q&Aをご覧ください。

参考文献

11
slm

これを行う最良の方法は、アプリケーションの起動と停止にsystemd(またはcgroupsを使用した別の方法)を使用することです。プロセスはプロセスグループを離れることができますが、(少なくともroot権限がなければ)cgroupを離れることはできません。したがって、systemdは新しいプロセス用の新しいcgroupを作成し、後で単にcgroup内のすべてを強制終了します。

5
Hauke Laging
${PROC_CMD} &
pid=$!
kids=$(grep -l "PPid.*$$" /proc/*/status | grep -o "[0-9]*"
    for kid in $(cat /proc/$pid/task/*/children); do 
        kids="$kid $kids $(cat /proc/$kid/task/*/children)"
    done
    printf '%s ' $kids)
kill $kids

これは、最初の行でバックグラウンドになっている${PROC_CMD},のすべての子を殺し、その$pidを次の行でキャプチャします。

cat /proc/$pid/task/*/children

上記は、子プロセスをリストするだけです。

プロセスエスケープ可能その$PPID.を覚えておくことが重要です。例えば:

echo $( grep "Pid" /proc/self/status & )

Pid: 349 PPid: 1 TracerPid: 0
2
mikeserv