Как лучше всего отправить сигнал всем членам группы процессов? - PullRequest
403 голосов
/ 24 декабря 2008

Я хочу убить целое дерево процессов. Каков наилучший способ сделать это с использованием любых распространенных языков сценариев? Я ищу простое решение.

Ответы [ 32 ]

283 голосов
/ 24 декабря 2008

Вы не говорите, является ли дерево, которое вы хотите уничтожить, одной группой процессов. (Это часто имеет место, если дерево является результатом разветвления от запуска сервера или командной строки оболочки.) Вы можете обнаружить группы процессов, используя GNU ps следующим образом:

 ps x -o  "%p %r %y %x %c "

Если вы хотите уничтожить группу процессов, просто используйте команду kill(1), но вместо того, чтобы присвоить ей номер процесса, присвойте ей отрицание номера группы. Например, чтобы убить каждый процесс в группе 5112, используйте kill -TERM -- -5112.

188 голосов
/ 28 февраля 2013

Убить все процессы, принадлежащие одному и тому же дереву процессов , используя ID группы процессов (PGID)

  • kill -- -$PGID Использовать сигнал по умолчанию (TERM = 15)
  • kill -9 -$PGID Использовать сигнал KILL (9)

Вы можете извлечь PGID из любого идентификатора процесса (PID) того же дерева процессов

  • kill -- -$(ps -o pgid= $PID | grep -o '[0-9]*') (сигнал TERM)
  • kill -9 -$(ps -o pgid= $PID | grep -o '[0-9]*') (сигнал KILL)

Особая благодарность tanager и Speakus за вклад в $PID оставшиеся пробелы и совместимость с OSX.

Объяснение

  • kill -9 -"$PGID" => Послать сигнал 9 (KILL) всем детям и внукам ...
  • PGID=$(ps opgid= "$PID") => Получить ID группы процессов из любого ИД процесса дерева, а не только Процесс- родитель-ID . Вариант ps opgid= $PID равен ps -o pgid --no-headers $PID, где pgid можно заменить на pgrp.
    Но:
    • ps вставляет начальные пробелы, когда PID меньше пяти цифр и выровнено по правому краю, как заметил tanager . Вы можете использовать:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • ps из OSX всегда печатает заголовок, поэтому Speakus предлагает:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* печатает только последовательные цифры (не печатает пробелы или алфавитные заголовки).

Другие командные строки

PGID=$(ps -o pgid= $PID | grep -o [0-9]*)
kill -TERM -"$PGID"  # kill -15
kill -INT  -"$PGID"  # correspond to [CRTL+C] from keyboard
kill -QUIT -"$PGID"  # correspond to [CRTL+\] from keyboard
kill -CONT -"$PGID"  # restart a stopped process (above signals do not kill it)
sleep 2              # wait terminate process (more time if required)
kill -KILL -"$PGID"  # kill -9 if it does not intercept signals (or buggy)

Ограничение

  • Как отметили Давид и Хьюберт Карио , когда kill вызывается процессом, принадлежащим тому же дереву, kill рискует покончить с собой, прежде чем прекратить уничтожение всего дерева.
  • Поэтому обязательно запустите команду, используя процесс с другим Process-Group-ID .

Длинная история

> cat run-many-processes.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./child.sh background &
./child.sh foreground
echo "ProcessID=$$ ends ($0)"

> cat child.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./grandchild.sh background &
./grandchild.sh foreground
echo "ProcessID=$$ ends ($0)"

> cat grandchild.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
sleep 9999
echo "ProcessID=$$ ends ($0)"

Запустить дерево процессов в фоновом режиме, используя '&'

> ./run-many-processes.sh &    
ProcessID=28957 begins (./run-many-processes.sh)
ProcessID=28959 begins (./child.sh)
ProcessID=28958 begins (./child.sh)
ProcessID=28960 begins (./grandchild.sh)
ProcessID=28961 begins (./grandchild.sh)
ProcessID=28962 begins (./grandchild.sh)
ProcessID=28963 begins (./grandchild.sh)

> PID=$!                    # get the Parent Process ID
> PGID=$(ps opgid= "$PID")  # get the Process Group ID

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    28969 Ss   33021   0:00 -bash
28349 28957 28957 28349 pts/3    28969 S    33021   0:00  \_ /bin/sh ./run-many-processes.sh
28957 28958 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh background
28958 28961 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3    28969 S    33021   0:00  |   |   |   \_ sleep 9999
28958 28963 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3    28969 S    33021   0:00  |   |       \_ sleep 9999
28957 28959 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh foreground
28959 28960 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3    28969 S    33021   0:00  |       |   \_ sleep 9999
28959 28962 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3    28969 S    33021   0:00  |           \_ sleep 9999
28349 28969 28969 28349 pts/3    28969 R+   33021   0:00  \_ ps fj

Команда pkill -P $PID не убивает внука:

> pkill -P "$PID"
./run-many-processes.sh: line 4: 28958 Terminated              ./child.sh background
./run-many-processes.sh: line 4: 28959 Terminated              ./child.sh foreground
ProcessID=28957 ends (./run-many-processes.sh)
[1]+  Done                    ./run-many-processes.sh

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    28987 Ss   33021   0:00 -bash
28349 28987 28987 28349 pts/3    28987 R+   33021   0:00  \_ ps fj
    1 28963 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28962 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28961 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28960 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999

Команда kill -- -$PGID убивает все процессы, включая внука.

> kill --    -"$PGID"  # default signal is TERM (kill -15)
> kill -CONT -"$PGID"  # awake stopped processes
> kill -KILL -"$PGID"  # kill -9 to be sure

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    29039 Ss   33021   0:00 -bash
28349 29039 29039 28349 pts/3    29039 R+   33021   0:00  \_ ps fj

Заключение

Я заметил, что в этом примере PID и PGID равны (28957).
Вот почему я изначально думал, что kill -- -$PID достаточно. Но в случае, если процесс запускается в Makefile, ID процесса отличается от ID группы .

Я думаю, kill -- -$(ps -o pgid= $PID | grep -o [0-9]*) - лучший простой способ убить все дерево процессов при вызове из другого ID группы (другое дерево процессов).

164 голосов
/ 26 июня 2011
pkill -TERM -P 27888

Это приведет к уничтожению всех процессов с идентификатором родительского процесса 27888.

или более надежный:

CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS

, который запланирует убийство через 33 секунды и вежливо попросит прекратить процессы.

См. этот ответ для уничтожения всех потомков.

99 голосов
/ 09 июля 2010

Чтобы рекурсивно уничтожить дерево процессов, используйте killtree ():

#!/bin/bash

killtree() {
    local _pid=$1
    local _sig=${2:--TERM}
    kill -stop ${_pid} # needed to stop quickly forking parent from producing children between child killing and parent killing
    for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
        killtree ${_child} ${_sig}
    done
    kill -${_sig} ${_pid}
}

if [ $# -eq 0 -o $# -gt 2 ]; then
    echo "Usage: $(basename $0) <pid> [signal]"
    exit 1
fi

killtree $@
16 голосов
/ 06 ноября 2015

rkill команда из pslist пакет отправляет данный сигнал (или SIGTERM по умолчанию) указанному процессу и всем его потомкам :

rkill [-SIG] pid/name...
11 голосов
/ 24 декабря 2008
Ответ

Брэда - то, что я бы тоже порекомендовал, за исключением того, что вы можете полностью отказаться от awk, если вы используете опцию --ppid для ps.

for child in $(ps -o pid -ax --ppid $PPID) do ....... done
9 голосов
/ 21 ноября 2012

Модифицированная версия ответа Жиганга:

#!/usr/bin/env bash
set -eu

killtree() {
    local pid
    for pid; do
        kill -stop $pid
        local cpid
        for cpid in $(pgrep -P $pid); do
            killtree $cpid
        done
        kill $pid
        kill -cont $pid
        wait $pid 2>/dev/null || true
   done
}

cpids() {
    local pid=$1 options=${2:-} space=${3:-}
    local cpid
    for cpid in $(pgrep -P $pid); do
        echo "$space$cpid"
        if [[ "${options/a/}" != "$options" ]]; then
            cpids $cpid "$options" "$space  "
        fi
    done
}

while true; do sleep 1; done &
cpid=$!
for i in $(seq 1 2); do
    cpids $$ a
    sleep 1
done
killtree $cpid
echo ---
cpids $$ a
9 голосов
/ 17 декабря 2011

Я использую немного модифицированную версию метода, описанного здесь: https://stackoverflow.com/a/5311362/563175

Так это выглядит так:

kill `pstree -p 24901 | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*/\1/' | tr "\n" " "`

, где 24901 - PID родителя.

Это выглядит довольно уродливо, но отлично справляется со своей работой.

9 голосов
/ 24 декабря 2008

если вы знаете, передайте pid родительского процесса, вот сценарий оболочки, который должен работать:

for child in $(ps -o pid,ppid -ax | \
   awk "{ if ( \$2 == $pid ) { print \$1 }}")
do
  echo "Killing child process $child because ppid = $pid"
  kill $child
done
8 голосов
/ 28 июня 2011

Чтобы добавить ответ Нормана Рэмси, возможно, стоит взглянуть на setsid, если вы хотите создать группу процессов.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

Функция setsid () должна создать новый сеанс, если вызывающий процесс не лидер группы процессов. на возвращение вызывающего процесса должно быть лидер сессии этого нового сессия, должна быть группа процессов лидер новой группы процессов, и не должно иметь управляющего терминала. Идентификатор группы процессов вызывающего Процесс должен быть установлен равным идентификатор процесса вызывающего процесса. вызывающий процесс должен быть единственным процесс в новой группе процессов и единственный процесс в новом сеансе.

Под этим я понимаю, что вы можете создать группу из начального процесса. Я использовал это в php, чтобы иметь возможность убить целое дерево процессов после его запуска.

Это может быть плохой идеей. Я был бы заинтересован в комментариях.

...