467 Stimmen

Wie kann man am besten ein Signal an alle Mitglieder einer Prozessgruppe senden?

Ich möchte einen ganzen Prozessbaum töten. Wie kann ich das am besten mit einer der gängigen Skriptsprachen erreichen? Ich bin auf der Suche nach einer einfachen Lösung.

1voto

Whinger Punkte 79

Eine alte Frage, ich weiß, aber alle Antworten scheinen immer wieder ps zu nennen, was mir nicht gefällt.

Diese awk-basierte Lösung kommt ohne Rekursion aus und ruft ps nur einmal auf.

awk 'BEGIN {
  p=1390
  while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2
  o=1
  while (o==1) {
    o=0
    split(p, q, " ")
    for (i in q) if (a[q[i]]!="") {
      p=p""a[q[i]]
      o=1
      a[q[i]]=""
    }
  }
  system("kill -TERM "p)
}'

Oder in einer einzigen Zeile:

awk 'BEGIN {p=1390;while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2;o=1;while (o==1) {o=0;split(p, q, " ");for (i in q) {if (a[q[i]]!="") {p=p""a[q[i]];o=1;a[q[i]]=""}}}system("kill -TERM "p)}'

Grundsätzlich ist die Idee, dass wir ein Array (a) von Eltern:Kind-Einträgen erstellen und dann in einer Schleife die Kinder für unsere passenden Eltern suchen und sie nach und nach zu unserer Elternliste (p) hinzufügen.

Wenn Sie den Top-Level-Prozess nicht beenden wollen, dann machen Sie

sub(/[0-9]*/, "", p)

kurz vor der system()-Zeile würde es aus dem Kill-Set entfernen.

Beachten Sie, dass hier eine Wettlaufbedingung vorliegt, aber das gilt (soweit ich sehen kann) für alle Lösungen. Es tut, was ich brauchte, weil das Skript ich brauchte es für nicht viele kurzlebige Kinder erstellen.

Eine Übung für den Leser wäre es, daraus eine Schleife mit zwei Durchläufen zu machen: nach dem ersten Durchlauf senden Sie SIGSTOP an alle Prozesse in der p-Liste, dann eine Schleife, um ps erneut auszuführen, und nach dem zweiten Durchlauf senden Sie SIGTERM und dann SIGCONT. Wenn Sie sich nicht um schöne Enden kümmern, könnte der zweite Durchlauf einfach SIGKILL sein, nehme ich an.

0voto

Hemant Thorat Punkte 2106

Töten eines untergeordneten Prozesses in einem Shell-Skript:

Oft müssen wir Kindprozesse beenden, die aus irgendeinem Grund hängen oder blockiert sind, z.B. bei FTP-Verbindungen.

Es gibt zwei Ansätze,

1) Erstellung eines separaten neuen Elternteils für jedes Kind, der den Kindprozess überwacht und beendet, sobald die Zeitüberschreitung erreicht ist.

Erstellen Sie test.sh wie folgt,

#!/bin/bash

declare -a CMDs=("AAA" "BBB" "CCC" "DDD")
for CMD in ${CMDs[*]}; do
    (sleep 10 & PID=$!; echo "Started $CMD => $PID"; sleep 5; echo "Killing $CMD => $PID"; kill $PID; echo "$CMD Completed.") &
done
exit;

und beobachten Sie die Prozesse, die den Namen 'test' tragen, in einem anderen Terminal mit folgendem Befehl.

watch -n1 'ps x -o "%p %r %c" | grep "test" '

Das obige Skript erstellt 4 neue Kindprozesse und ihre Eltern. Jeder Kindprozess wird 10 Sekunden lang laufen. Aber sobald die Zeitüberschreitung von 5 Sekunden erreicht ist, werden die entsprechenden Elternprozesse die Kindprozesse beenden. Die Child-Prozesse werden also nicht in der Lage sein, ihre Ausführung zu beenden (10sec). Spielen Sie mit diesen Zeiten (wechseln Sie 10 und 5), um ein anderes Verhalten zu sehen. In diesem Fall wird der Child-Prozess die Ausführung in 5 Sekunden beenden, bevor er die Zeitüberschreitung von 10 Sekunden erreicht.

2) Lassen Sie den aktuellen Elternprozess überwachen und beenden Sie den Kindprozess, sobald die Zeitüberschreitung erreicht ist. Dadurch wird kein separater Elternprozess zur Überwachung jedes Kindprozesses erstellt. Außerdem können Sie alle Child-Prozesse innerhalb desselben Parents ordnungsgemäß verwalten.

Erstellen Sie test.sh wie folgt,

#!/bin/bash

declare -A CPIDs;
declare -a CMDs=("AAA" "BBB" "CCC" "DDD")

CMD_TIME=15;
for CMD in ${CMDs[*]}; do
    (echo "Started..$CMD"; sleep $CMD_TIME; echo "$CMD Done";) &
    CPIDs[$!]="$RN";
    sleep 1;
done

GPID=$(ps -o pgid= $$);
CNT_TIME_OUT=10;
CNT=0;
while (true); do
    declare -A TMP_CPIDs;

    for PID in "${!CPIDs[@]}"; do
        echo "Checking "${CPIDs[$PID]}"=>"$PID;

        if ps -p $PID > /dev/null ; then
          echo "-->"${CPIDs[$PID]}"=>"$PID" is running..";
          TMP_CPIDs[$PID]=${CPIDs[$PID]};
        else
          echo "-->"${CPIDs[$PID]}"=>"$PID" is completed.";
        fi
    done

    if [ ${#TMP_CPIDs[@]} == 0 ]; then
        echo "All commands completed.";
        break;
    else
        unset CPIDs;
        declare -A CPIDs;
        for PID in "${!TMP_CPIDs[@]}"; do
            CPIDs[$PID]=${TMP_CPIDs[$PID]};
        done
        unset TMP_CPIDs;

        if [ $CNT -gt $CNT_TIME_OUT ]; then
            echo ${CPIDs[@]}"PIDs not reponding. Timeout reached $CNT sec. killing all childern with GPID $GPID..";
            kill -- -$GPID;
        fi
    fi

    CNT=$((CNT+1));
    echo "waiting since $b secs..";
    sleep 1;
done

exit;

und beobachten Sie die Prozesse, die den Namen 'test' tragen, in einem anderen Terminal mit folgendem Befehl.

watch -n1 'ps x -o "%p %r %c" | grep "test" '

Das obige Skript erstellt 4 neue Kindprozesse. Wir speichern die Pids aller Kindprozesse und führen eine Schleife durch, um zu prüfen, ob sie ihre Ausführung beendet haben oder noch laufen. Der Kindprozess wird bis zur CMD_TIME Zeit ausgeführt. Aber wenn CNT_TIME_OUT timeout erreicht wird, werden alle Kindprozesse vom Elternprozess beendet. Sie können das Timing ändern und mit dem Skript herumspielen, um das Verhalten zu sehen. Ein Nachteil dieses Ansatzes ist, dass er die Gruppen-ID zum Töten aller Kindprozesse verwendet. Aber der Elternprozess selbst gehört zur gleichen Gruppe, so dass er auch getötet wird.

Möglicherweise müssen Sie dem übergeordneten Prozess eine andere Gruppen-ID zuweisen, wenn Sie nicht wollen, dass der übergeordnete Prozess beendet wird.

Weitere Einzelheiten finden Sie hier,

Beenden eines untergeordneten Prozesses in einem Shell-Skript

0voto

Anthony Rutledge Punkte 5794

Und jetzt noch ein bisschen clevere Shell-Programmierung.

Diese Lösung ist mit Kosten verbunden, aber zumindest basiert sie auf der täglichen Iteration und Rekursion. Dies kann in Bash umgewandelt werden, indem man sorgfältig auf die typeset Befehle und konvertiert sie in declare o local wo es angebracht ist.

Diskussion

Wenn man einen Prozess tötet, muss man damit rechnen, dass er Elternteil vieler Kinder sein könnte, und dass jedes Kind der Elternteil von noch mehr Kindern sein könnte, und so weiter und so fort.

Was ist zu tun?

Wenn es nur eine Funktion gäbe, die prüft, ob ein Prozess Kinder hat, und eine weitere Funktion, die die Kind-PIDs eines Elternprozesses zurückgibt.

Dann wäre das Spiel viel einfacher, weil man eine Schleife erstellen könnte, die eine Liste von PIDs durchläuft und jede einzelne auf Kinder überprüft, bevor sie getötet wird. Wenn es keine Kinder gibt, wird der Prozess einfach beendet. Wenn es Kinder gibt, ruft man die Treiberfunktion rekursiv auf und übergibt ihr die Ergebnisse einer Funktion, die die PIDs der Kinder eines Elternteils ermittelt.

Die Basisfall-Aktion (Prozess hat nicht Kinder).

#!/bin/ksh

function killProcess ()
{
    typeset -r PID=$1

    if [[ ! isProcess $PID ]]
    then
        echo -e "Process $PID cannot be terminated because it does not exist.\n" 1>&2
        return 1
    elif [[ kill -s TERM $PID ]] && [[ ! isProcess $PID ]]
    then
        echo -e "Process $PID was terminated.\n" 1>&2
        return 0
    elif kill -s KILL $PID
        echo -e "Process $PID killed with SIGKILL (9) signal. No time to clean up potential files.\n" 1>&2
        return 0
    elif isZombie $PID
    then
        echo -e "Process $PID in the zombie status.\n" 1>&2 
        return 2
    else
        echo -e "Process $PID is alive. SIGTERM and SIGKILL had no effect. It is not a zombie.\n" 1>&2
    fi

    return 3
}

function attemptToKillPid ()
{
    typeset -r PID=$1

    if killProcess $PID
    then 
        return 0
    fi

    ppid=$(getParentPid $pid)
    echo -e "Process $pid of parent $ppid was not able to be killed.\n" 1>&2
    return 1
}

Der allgemeine Fall Aktion (Prozess hat Kinder).

function killPidFamily ()
{
    typeset -r PROCESSES=$*
    typeset -ir NUM_PROCESSES_TO_KILL=$(countLines $PROCESSES)
    typeset -i numKilledProcesses=0
    typeset ppid

    for pid in $PROCESSES
    do
        pid=$(trim $pid)

        if ! hasChildPids $pid
        then
            attemptToKillPid $pid && (( numKilledProcesses++ ))
        else
            killPidFamily $(getChildPids $pid) && attemptToKillPid $pid && (( numKilledProcesses++ ))
        fi
    done

    (( numKilledProcesses == NUM_PROCESSES_TO_KILL ))
    return $?
}

Bibliothek mit unterstützenden Funktionen.

#!/bin/ksh

function trim ()
{
    echo -n "$1" | tr -d [:space:]
}

function countLines ()
{
    typeset -r $LIST=$*
    trim $(echo $LIST | wc -l | awk {'print $1'})
}

function getProcesses ()
{
    # NOTE: -o pgid below would be $4 in awk.

    ps -e -o comm,pid,ppid,pgid,user,ruid,euid,group,rgid,egid,etime,etimes,stat --no-headers
}

function getProcess ()
{
   typeset -r PID=$1
   ps -p $PID -o comm,pid,ppid,pgid,user,ruid,euid,group,rgid,egid,etime,etimes,stat --no-headers
}

function isProcess ()
{
    typeset -r PID=$1

    ps -p $PID -o pid --no-headers 1>&2
    return $?
}

function getProcessStatus ()
{
    typeset -r PID=$1
    trim $(ps -p $PID -o stat --no-headers)
}

function isZombie ()
{
    typeset -r PID=$1
    typeset processStatus

    processStatus=$(getProcessStatus $PID)

    [[ "$processStatus" == "Z" ]]
    return $?
}

function hasChildPids ()
{
    typeset -r PPID=$1
    echo $(getProcesses) | awk '{print $3}' | sort -n | uniq | grep "^${PPID}$"
    return $?
}

function getChildPids ()
{
    typeset -r PPID=$1
    echo $(getProcesses) | awk '{print $2, $3}' | sort -k 2 | awk "\$2 == $PPID {print \$1}" | sort -n
}

function getParentPid ()
{
    typeset -r PID=$1
    trim $(echo $(getProcess $PID) | awk '{print $3}')
}

Auf diese Weise können Sie sicher sein, dass der Prozessbaum von den Zweigen bis hin zur Wurzel zerstört . Dadurch wird vermieden, dass Zombies und andere unerwünschte Situationen entstehen.

Nun, da Sie die am teuersten (einen Prozess nach dem anderen zu beenden), untersuchen Sie, wie Sie diese Lösung ändern können, um die PGID (Prozessgruppen-ID) zu verwenden. Die getProcesses () Funktion gibt bereits die PGID aus ( $4 en awk ), also lernen Sie, damit umzugehen, oder lassen Sie es bleiben.

0voto

CoderBlue Punkte 406

Die Gruppe nur mit dem Namen des Prozesses töten, der zur Gruppe gehört:

kill -- -$(ps -ae o pid,pgrp,cmd | grep "[e]xample.py" | awk '{print $2}' | tail -1)

Dies ist eine Abwandlung der Antwort von olibre, aber Sie müssen nicht die PID kennen, sondern nur den Namen eines Mitglieds der Gruppe.

Erläuterung:

Um die Gruppennummer zu erhalten, führen Sie den Befehl ps mit den angegebenen Argumenten aus, formatieren Sie example.py mit Anführungszeichen und verwenden Sie die Klammer für den ersten Buchstaben (dadurch wird der Befehl grep selbst herausgefiltert) und filtern Sie die Datei mit awk, um das zweite Feld zu erhalten, das die Gruppennummer ist. Mit tail -1 werden doppelte Gruppennummern entfernt. Das alles geben Sie in eine Variable ein, indem Sie die $()-Syntax verwenden, und voila - Sie erhalten die Gruppennummer. Ersetzen Sie also das $(mess) durch das obige -groupid.

0voto

Ich weiß, das ist alt, aber das ist die bessere Lösung, die ich gefunden habe:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X