Wie kann ich einen Prozess beenden und sicherstellen, dass die PID nicht wiederverwendet wurde?

40

Angenommen, Sie haben beispielsweise ein Shell-Skript ähnlich dem Folgenden:

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

Sollte den Trick machen, sollte es nicht? Abgesehen davon, dass der Prozess möglicherweise vorzeitig beendet wurde und die PID wiederverwendet wurde, bedeutet dies, dass ein unschuldiger Job stattdessen eine Bombe in der Signalwarteschlange hat. In der Praxis spielt dies möglicherweise eine Rolle, aber es macht mir trotzdem Sorgen. Langfristig etwas zu hacken, um von selbst tot zu fallen oder seine PID auf dem FS zu behalten / zu entfernen, würde genügen, aber ich denke an die allgemeine Situation hier.

FJL
quelle
3
Sie stellen sicher, dass der Mörder des Zielprozesses getötet wird, wenn er stirbt.
mikeserv
2
Verwenden Sie, killallwelche Übereinstimmungen mit dem Namen übereinstimmen. Zumindest beenden Sie also nur einen Prozess mit demselben Namen wie longrunningthing. Vorausgesetzt, Sie haben immer nur einen davon zur gleichen Zeit am Laufen.
LawrenceC
8
Sie können die Startzeit des ursprünglichen Prozesses speichern und vor dem Beenden überprüfen, ob die Startzeit des Prozesses mit dieser PID mit der von Ihnen gespeicherten übereinstimmt. Das Paar pid, start-time ist eine eindeutige Kennung für die Prozesse in Linux.
Bakuriu
1
Darf ich fragen, warum Sie das brauchen? Was ist das Grundlegende, was Sie erreichen wollen? (Etwas, das ununterbrochen läuft, aber alle 24 Stunden zurückgesetzt wird?)
Olivier Dulac
2
@mikeserv Ein Prozess kann nicht garantieren, dass im Falle seines eigenen Todes etwas passiert.
Kasperd

Antworten:

29

Am besten verwenden Sie den timeoutBefehl, wenn Sie ihn haben, der dafür vorgesehen ist:

timeout 86400 cmd

Die aktuelle (8.23) GNU-Implementierung funktioniert zumindest mit alarm()oder gleichwertig, während auf den untergeordneten Prozess gewartet wird. Es scheint nicht zu verhindern, dass der AlarmSIGALRM zwischen der waitpid()Rückkehr und dem timeoutVerlassen ausgelöst wird ( wodurch dieser Alarm effektiv gelöscht wird ). Während dieses kleinen Fensters timeoutkönnen sogar Nachrichten auf stderr geschrieben werden (zum Beispiel, wenn das Kind einen Core gelöscht hat), wodurch das Race-Fenster weiter vergrößert wird (auf unbestimmte Zeit, wenn stderr zum Beispiel eine vollständige Pipe ist).

Ich persönlich kann mit dieser Einschränkung leben (die wahrscheinlich in einer zukünftigen Version behoben wird). timeoutAußerdem wird besonders darauf geachtet, den korrekten Ausgangsstatus zu melden, andere Eckfälle (wie SIGALRM beim Start blockiert / ignoriert, andere Signale ...) besser zu behandeln, als Sie es wahrscheinlich von Hand schaffen würden.

Als Annäherung könnte man es so schreiben 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

Es gibt einen timelimitBefehl unter http://devel.ringlet.net/sysutils/timelimit/ ( timeouteinige Monate vor GNU ).

 timelimit -t 86400 cmd

Dieser verwendet einen alarm()ähnlichen Mechanismus, installiert jedoch einen Handler SIGCHLD(ignoriert angehaltene Kinder), um das sterbende Kind zu erkennen. Außerdem wird der Alarm vor dem Ausführen abgebrochen waitpid()(dh die Zustellung wird nicht abgebrochen, SIGALRMwenn er aussteht, aber ich sehe kein Problem darin, dass er so geschrieben ist) und vor dem Aufrufen abgebrochen waitpid()(eine wiederverwendete PID kann nicht getötet werden) ).

netpipes hat auch einen timelimitBefehl. Diese Methode ist um Jahrzehnte älter als alle anderen, hat jedoch einen anderen Ansatz, funktioniert jedoch bei gestoppten Befehlen nicht ordnungsgemäß und gibt 1bei Zeitüberschreitung einen Beendigungsstatus zurück.

Als direktere Antwort auf Ihre Frage können Sie Folgendes tun:

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

Überprüfen Sie also, ob der Prozess noch ein Kind von uns ist. Wieder gibt es ein kleines Wettlauffenster (zwischen dem psAbrufen des Status dieses Prozesses und dem killBeenden), in dem der Prozess sterben und seine PID von einem anderen Prozess wiederverwendet werden kann.

Mit einigen Muscheln ( zsh, bash, mksh), können Sie Job - Spezifikationen statt pids passieren.

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

Das funktioniert nur, wenn Sie nur einen Hintergrundjob erzeugen (andernfalls ist es nicht immer zuverlässig möglich, die richtige Jobspezifikation zu erhalten).

Wenn das ein Problem ist, starten Sie einfach eine neue Shell-Instanz:

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

Das funktioniert, weil die Shell den Job vom Job-Tisch entfernt, wenn das Kind stirbt. Hier sollte es kein Rennfenster geben, da zum Zeitpunkt des Shell-Aufrufs kill()entweder das SIGCHLD-Signal nicht verarbeitet wurde und die PID nicht wiederverwendet werden kann (da nicht darauf gewartet wurde) oder es verarbeitet wurde und Job wurde aus der Prozesstabelle entfernt (und killwürde einen Fehler melden). bash‚s killzumindest blockiert SIGCHLD , bevor er seine Arbeit Tabelle greift das zu erweitern %und deblockiert es nach dem kill().

Eine weitere Möglichkeit zu vermeiden , dass mit sleephängenden Prozess auch nach dem cmdgestorben ist , mit bashoder ksh93ist ein Rohr zu verwenden , mit read -tstatt sleep:

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

Dieser hat immer noch Rennbedingungen und du verlierst den Exit-Status des Befehls. Es wird auch davon cmdausgegangen, dass fd 4 nicht geschlossen wird.

Sie können versuchen, eine rennfreie Lösung perlwie folgt zu implementieren :

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...

(obwohl es verbessert werden müsste, um andere Arten von Eckkoffern zu handhaben).

Eine andere rennfreie Methode könnte die Verwendung von Prozessgruppen sein:

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

Beachten Sie jedoch, dass die Verwendung von Prozessgruppen Nebenwirkungen haben kann, wenn ein Endgerät über E / A verfügt. Es hat jedoch den zusätzlichen Vorteil, alle anderen zusätzlichen Prozesse, die durch hervorgerufen werden, zu beenden cmd.

Stéphane Chazelas
quelle
4
Warum nicht zuerst die beste Methode nennen?
DeltaB
2
@deltab: timeoutist nicht portabel, die Antwort erwähnte zuerst eine portable Lösung.
Donnerstag,
1
@deltab: Es gibt einen Einblick darüber, wie Dinge funktionieren und vor allem, wie der "Common Sense" -Ansatz scheitern kann. Es wird erwartet, dass einer die ganze Antwort liest
Olivier Dulac
@Stephane: Für die "immer die richtige Jobspezifikation ist nicht immer zuverlässig möglich": Kannst du nicht zuerst die Ausgabe von zählen jobsund dann wissen, dass (wie es deine eigene Shell ist, in der du die Kontrolle darüber hast, was als nächstes passiert) der nächste Hintergrund Job wird N + 1 sein? [dann kannst du N retten und später% N + 1 töten])
Olivier Dulac
1
@OlivierDulac, dies würde voraussetzen, dass kein früherer Job beendet ist, wenn Sie einen neuen starten (Shells verwenden Jobnummern erneut).
Stéphane Chazelas
28

Im Allgemeinen können Sie nicht. Alle bisher gegebenen Antworten sind fehlerhafte Heuristiken. Es gibt nur einen Fall, in dem Sie die pid sicher zum Senden von Signalen verwenden können: Wenn der Zielprozess ein direktes untergeordnetes Element des Prozesses ist, der das Signal sendet, und das übergeordnete Element noch nicht darauf gewartet hat. In diesem Fall wird die pid reserviert (dies ist ein "Zombie-Prozess"), selbst wenn sie beendet wurde, bis der Elternteil darauf wartet. Mir ist keine Möglichkeit bekannt, das mit der Shell sauber zu machen.

Eine andere sichere Möglichkeit, Prozesse abzubrechen, besteht darin, sie mit einem Kontroll-Tty zu starten, das auf ein Pseudo-Terminal eingestellt ist, für das Sie die Masterseite besitzen. Sie können dann Signale über das Terminal senden, z. B. das Zeichen für SIGTERModer SIGQUITüber die Pty schreiben.

Eine weitere Möglichkeit, die für die Skripterstellung praktischer ist, besteht darin, eine benannte screenSitzung zu verwenden und Befehle an die Bildschirmsitzung zu senden, um sie zu beenden. Dieser Vorgang findet über eine Pipe oder einen Unix-Socket statt, die bzw. der gemäß der Bildschirmsitzung benannt ist. Diese wird nicht automatisch wiederverwendet, wenn Sie einen sicheren eindeutigen Namen auswählen.

R ..
quelle
4
Ich kann nicht verstehen, warum das nicht in Muscheln gemacht werden konnte. Ich habe mehrere Lösungen gegeben.
Stéphane Chazelas
3
Könnten Sie bitte eine Erklärung und eine quantitative Erörterung von Rennfenstern und anderen Nachteilen geben? Ohne das ist "Alle bisher gegebenen Antworten sind fehlerhafte Heuristiken" eine unnötige Auseinandersetzung ohne Nutzen.
Peterph
3
@peterph: Im Allgemeinen ist jede Verwendung einer PID eine TOCTOU-Rasse - egal, wie Sie prüfen, ob sie sich immer noch auf denselben Prozess bezieht, auf den Sie sich beziehen möchten, sie könnte aufhören, sich auf diesen Prozess zu beziehen und auf einige neue verarbeiten Sie in dem Intervall, bevor Sie es verwenden (Senden des Signals). Die einzige Möglichkeit, dies zu verhindern, besteht darin, die Freigabe / Wiederverwendung der PID zu blockieren. Der einzige Prozess, der dies tun kann, ist der direkte übergeordnete Prozess.
R ..
2
@ StéphaneChazelas: Wie verhindert man, dass die Shell auf eine PID eines Hintergrundprozesses wartet, der beendet wurde? Wenn Sie dies tun können, ist das Problem leicht zu lösen, falls OP dies erfordert.
R ..
5
@peterph: "Das Rennfenster ist klein" ist keine Lösung. Und die Seltenheit des Rennens hängt von der sequentiellen PID-Zuordnung ab. Bugs, die dazu führen, dass einmal im Jahr etwas sehr Schlimmes passiert, sind viel schlimmer als Bugs, die die ganze Zeit passieren, weil sie praktisch unmöglich zu diagnostizieren und zu beheben sind.
R ..
10
  1. Wenn Sie den Prozess starten, speichern Sie seine Startzeit:

    longrunningthing &
    p=$!
    stime=$(TZ=UTC0 ps -p "$p" -o lstart=)
    
    echo "Killing longrunningthing on PID $p in 24 hours"
    sleep 86400
    echo Time up!
    
  2. Bevor Sie versuchen, den Prozess zu beenden, stoppen Sie ihn (dies ist nicht unbedingt erforderlich, aber es ist eine Möglichkeit, Rennbedingungen zu vermeiden: Wenn Sie den Prozess stoppen, kann die PID nicht wiederverwendet werden.)

    kill -s STOP "$p"
    
  3. Überprüfen Sie, ob der Prozess mit dieser PID dieselbe Startzeit hat, und beenden Sie ihn, falls ja, andernfalls lassen Sie den Prozess fortfahren:

    cur=$(TZ=UTC0 ps -p "$p" -o lstart=)
    
    if [ "$cur" = "$stime" ]
    then
        # Okay, we can kill that process
        kill "$p"
    else
        # PID was reused. Better unblock the process!
        echo "long running task already completed!"
        kill -s CONT "$p"
    fi
    

Dies funktioniert, weil es auf einem bestimmten Betriebssystem nur einen Prozess mit derselben PID und Startzeit geben kann.

Wenn Sie den Prozess während der Überprüfung anhalten, sind die Rennbedingungen kein Problem. Dies hat offensichtlich das Problem, dass ein zufälliger Prozess für einige Millisekunden angehalten werden kann. Abhängig von der Art des Prozesses kann dies ein Problem sein oder auch nicht.


Persönlich würde ich einfach Python verwenden und psutilPID automatisch wiederverwenden:

import time

import psutil

# note: it would be better if you were able to avoid using
#       shell=True here.
proc = psutil.Process('longrunningtask', shell=True)
time.sleep(86400)

# PID reuse handled by the library, no need to worry.
proc.terminate()   # or: proc.kill()
Bakuriu
quelle
Python-Regeln in UNIX ... Ich bin mir nicht sicher, warum dort nicht mehr Antworten beginnen, da ich sicher bin, dass die meisten Systeme die Verwendung nicht verbieten.
Mr. Mascaro
Ich habe zuvor ein ähnliches Schema (unter Verwendung der Startzeit) verwendet, aber Ihre Skriptfähigkeiten sind besser als meine! Vielen Dank.
FJL
Das bedeutet, dass Sie möglicherweise den falschen Prozess stoppen. Beachten Sie, dass sich das ps -o start=Format nach einer Weile von 18:12 auf Jan26 ändert. Hüten Sie sich auch vor DST-Änderungen. Wenn Sie unter Linux arbeiten, werden Sie es wahrscheinlich vorziehen TZ=UTC0 ps -o lstart=.
Stéphane Chazelas
@StéphaneChazelas Ja, aber du lässt es danach weitergehen. Ich habe ausdrücklich gesagt: Abhängig von der Art der Aufgabe, die dieser Prozess ausführt, kann es einige Millisekunden dauern, bis Sie ihn angehalten haben. Danke für den Tipp lstart, ich bearbeite ihn in.
Bakuriu
Beachten Sie, dass es für jeden leicht ist, die Prozesstabelle mit Zombies zu füllen, es sei denn, Ihr System begrenzt die Anzahl der Prozesse pro Benutzer. Sobald nur noch 3 Pids verfügbar sind, kann jeder innerhalb einer Sekunde Hunderte verschiedener Prozesse mit derselben Pid starten. Genau genommen ist Ihre Aussage, dass es auf einem bestimmten Betriebssystem nur einen Prozess mit derselben PID und Startzeit geben kann, nicht unbedingt wahr.
Stéphane Chazelas
7

Auf einem Linux-System können Sie sicherstellen, dass eine PID nicht wiederverwendet wird, indem Sie den PID-Namespace beibehalten. Dies kann über die /proc/$pid/ns/pidDatei erfolgen.

  • man namespaces -

    Bind Montag (siehe mount(2)) eine der Dateien in diesem Verzeichnis woanders in dem Dateisystem des entsprechenden Namensraum des Prozesses durch pid lebendig angegeben hält , auch wenn alle Prozesse , die gegenwärtig im Namensraum beenden.

    Wenn Sie eine der Dateien in diesem Verzeichnis öffnen (oder eine Datei, die an eine dieser Dateien gebunden ist), wird ein Datei-Handle für den entsprechenden Namespace des durch pid angegebenen Prozesses zurückgegeben. Solange dieser Dateideskriptor geöffnet bleibt, bleibt der Namespace aktiv, auch wenn alle Prozesse im Namespace beendet werden. Der Dateideskriptor kann übergeben werden setns(2).

Sie können eine Gruppe von Prozessen - im Grunde genommen eine beliebige Anzahl von Prozessen - isolieren, indem Sie deren Namespace festlegen init.

  • man pid_namespaces -

    Der erste Prozess, der in einem neuen Namespace erstellt wurde (dh der Prozess, der clone(2) mit dem CLONE_NEWPID- Flag erstellt wurde, oder das erste untergeordnete Element , das von einem Prozess nach dem Aufruf unshare(2)des CLONE_NEWPID- Flags erstellt wurde), hat die PID 1 und ist der initProzess für den Namespace ( siehe init(1)) . Ein untergeordneter Prozess, der innerhalb des Namespaces verwaist ist, wird an diesen Prozess angepasstinit(1) (es sei denn, einer der Vorfahren des untergeordneten Prozesses im selben PID- Namespace hat den Befehl prctl(2) PR_SET_CHILD_SUBREAPER verwendet , um sich selbst als Reaper verwaister untergeordneter Prozesse zu kennzeichnen) .

    Wenn der initProzess eines PID- Namespaces beendet wird, beendet der Kernel alle Prozesse im Namespace über ein SIGKILL- Signal. Dieses Verhalten spiegelt die Tatsache wider, dass der initProzess für den korrekten Betrieb eines PID- Namespace wesentlich ist .

Das util-linuxPaket enthält viele nützliche Tools zum Bearbeiten von Namespaces. Beispiel: unshareWenn Sie die Rechte in einem Benutzernamensraum noch nicht festgelegt haben, sind Superuser-Rechte erforderlich:

unshare -fp sh -c 'n=
    echo "PID = $$"
    until   [ "$((n+=1))" -gt 5 ]
    do      while   sleep 1
            do      date
            done    >>log 2>/dev/null   &
    done;   sleep 5' >log
cat log; sleep 2
echo 2 secs later...
tail -n1 log

Wenn Sie keinen Benutzernamensraum eingerichtet haben, können Sie dennoch beliebige Befehle sicher ausführen, indem Sie die Berechtigungen sofort löschen. Der runuserBefehl ist eine andere (nicht setuid) Binärdatei, die vom util-linuxPaket bereitgestellt wird und die wie folgt aussehen kann:

sudo unshare -fp runuser -u "$USER" -- sh -c '...'

...und so weiter.

Im obigen Beispiel werden zwei Schalter an unshare(1)das --forkFlag übergeben, das den aufgerufenen sh -cProzess zum ersten erstellten Kind macht und dessen initStatus sichert , sowie an das --pidFlag, das anweist unshare(1), einen PID-Namespace zu erstellen.

Der sh -cProzess erzeugt fünf untergeordnete Shells im Hintergrund - jeweils eine whileEndlosschleife, die die Ausgabe von so lange datean das Ende von anfügt , logwie sleep 1true zurückgegeben wird. Nach dem Laichen dieser Prozesse shAnrufe sleependet für weitere 5 Sekunden , dann.

Es ist vielleicht erwähnenswert, dass, wenn das -fFlag nicht verwendet würde, keine der Hintergrundschleifen whileenden würde, aber damit ...

AUSGABE:

PID = 1
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
2 secs later...
Mon Jan 26 19:17:48 PST 2015
mikeserv
quelle
Interessante Antwort, die robust zu sein scheint. Wahrscheinlich ein bisschen übertrieben für den normalen Gebrauch, aber es lohnt sich, darüber nachzudenken.
Uriel
Ich verstehe nicht, wie oder warum das Beibehalten eines PID-Namespaces die Wiederverwendung einer PID verhindert. Die Manpage, die Sie zitieren - Solange dieser Dateideskriptor geöffnet bleibt, bleibt der Namespace aktiv, auch wenn alle Prozesse im Namespace beendet werden - deutet darauf hin, dass die Prozesse möglicherweise immer noch beendet werden (und daher vermutlich ihre Prozess-ID wiederverwendet wird). Was hat die Aufrechterhaltung des PID-Namespace damit zu tun, dass die PID selbst nicht von einem anderen Prozess wiederverwendet wird?
Davmac
5

Überlegen Sie longrunningthing, ob Sie sich ein bisschen besser verhalten, ein bisschen dämonhafter. Sie können beispielsweise eine PID-Datei erstellen , die zumindest eine eingeschränkte Kontrolle über den Prozess ermöglicht. Es gibt verschiedene Möglichkeiten, dies zu tun, ohne die ursprüngliche Binärdatei zu ändern, die alle einen Wrapper beinhalten. Zum Beispiel:

  1. Ein einfaches Wrapper-Skript, das den erforderlichen Job im Hintergrund startet (mit optionaler Ausgabeumleitung), die PID dieses Prozesses in eine Datei schreibt, dann wartet, bis der Prozess abgeschlossen ist (mit wait) und die Datei entfernt. Wenn während des Wartens der Prozess zB durch etwas wie getötet wird

    kill $(cat pidfile)
    

    Der Wrapper wird nur sicherstellen, dass die PID-Datei entfernt wird.

  2. Ein Monitor-Wrapper, der seine eigene PID irgendwo ablegt und die an ihn gesendeten Signale auffängt (und darauf reagiert). Einfaches Beispiel:

    #!/bin/bash
    p=0
    trap killit USR1

    killit () {
        printf "USR1 caught, killing %s\n" "$p"
        kill -9 $p
    }

    printf "monitor $$ is waiting\n"
    therealstuff &
    p=%1
    wait $p
    printf "monitor exiting\n"

Nun, wie @R .. und @ StéphaneChazelas betonten, haben diese Ansätze oft irgendwo eine Racebedingung oder beschränken die Anzahl der Prozesse, die Sie erzeugen können. Außerdem werden die Fälle nicht behandelt, in denen sich die longrunningthingMaygabel und die Kinder lösen (was wahrscheinlich nicht das Problem in der ursprünglichen Frage war).

Mit neueren Linux-Kerneln (lesen Sie ein paar Jahre altes) kann dies gut mit cgroups , dem Freezer , behandelt werden , den einige moderne Linux-Init-Systeme verwenden.

peterph
quelle
Danke und an alle. Ich lese gerade alles. Der springende Punkt longrunningthingist, dass Sie keine Kontrolle darüber haben, was es ist. Ich habe auch ein Shell-Skript-Beispiel angegeben, weil es das Problem erklärt. Ich mag Ihre und all die anderen kreativen Lösungen hier, aber wenn Sie Linux / bash verwenden, gibt es dafür ein "Timeout". Ich nehme an, ich sollte die Quelle dazu bringen und sehen, wie es das macht!
FJL
@FJL, timeoutist keine eingebaute Shell. Es gab verschiedene Implementierungen eines timeoutBefehls für Linux, eine wurde kürzlich (2008) zu GNU coreutils hinzugefügt (also nicht Linux-spezifisch), und das ist, was die meisten Linux-Distributionen heutzutage verwenden.
Stéphane Chazelas
@ Stéphane - Danke - Ich habe später einen Verweis auf GNU coreutils gefunden. Sie können portabel sein, aber es sei denn, es befindet sich im Basissystem, auf das man sich nicht verlassen kann. Ich bin mehr daran interessiert zu wissen, wie es funktioniert, obwohl ich Ihren Kommentar an anderer Stelle notiere, der besagt, dass er nicht 100% zuverlässig ist. Angesichts der Art und Weise, wie dieser Thread verlaufen ist, bin ich nicht überrascht!
FJL
1

Wenn Sie unter Linux (und einigen anderen * Nixen) arbeiten, können Sie überprüfen, ob der Prozess, den Sie beenden möchten, noch verwendet wird und ob die Befehlszeile mit Ihrem langen Prozess übereinstimmt. So etwas wie :

echo Time up!
grep -q longrunningthing /proc/$p/cmdline 2>/dev/null
if [ $? -eq 0 ]
then
  kill $p
fi

Eine Alternative kann darin bestehen, zu überprüfen, wie lange der Prozess, den Sie beenden möchten, mit so etwas wie ausgeführt wird ps -p $p -o etime=. Sie könnten es selbst tun, indem Sie diese Informationen aus extrahieren. Dies /proc/$p/statwäre jedoch schwierig (die Zeit wird in Sekunden gemessen und Sie müssen die Systembetriebszeit auch in verwenden /proc/stat).

In der Regel können Sie jedoch nicht sicherstellen, dass der Prozess nach Ihrer Überprüfung und vor dem Beenden nicht ersetzt wird .

Uriel
quelle
Das ist immer noch nicht richtig, weil es die Rennbedingungen nicht beseitigt.
Strcat
@strcat In der Tat keine Garantie für den Erfolg, aber die meisten Skripte machen sich nicht einmal die Mühe, eine solche Prüfung durchzuführen, und töten ein cat pidfileErgebnis nur unverblümt . Ich kann mich nicht an eine saubere Methode erinnern, die es nur in der Shell macht. Die vorgeschlagene Namespace-Antwort scheint jedoch
Uriel
-1

Das ist eigentlich eine sehr gute Frage.

Die Art und Weise, die Eindeutigkeit eines Prozesses zu bestimmen, besteht darin, (a) zu betrachten, wo er sich im Speicher befindet; und (b) was dieser Speicher enthält. Um genau zu sein, möchten wir wissen, wo im Speicher sich der Programmtext für den ersten Aufruf befindet, da wir wissen, dass der Textbereich jedes Threads eine andere Position im Speicher einnimmt. Wenn der Prozess abbricht und ein anderer Prozess mit derselben PID gestartet wird, belegt der Programmtext für den neuen Prozess nicht denselben Speicherplatz und enthält nicht dieselben Informationen.

Machen Sie also gleich nach dem Start Ihres Prozesses md5sum /proc/[pid]/mapsdas Ergebnis und speichern Sie es. Wenn Sie den Prozess später beenden möchten, führen Sie eine weitere MD5-Summe aus und vergleichen Sie sie. Wenn es passt, dann töte die PID. Wenn nicht, dann nicht.

Um dies selbst zu sehen, starten Sie zwei identische Bash-Shells. Untersuchen Sie die /proc/[pid]/mapsfür sie und Sie werden feststellen, dass sie unterschiedlich sind. Warum? Denn obwohl es sich um dasselbe Programm handelt, belegen sie unterschiedliche Speicherorte und die Adressen ihres Stapels sind unterschiedlich. Wenn also Ihr Prozess abstürzt und seine PID wiederverwendet wird, obwohl derselbe Befehl mit denselben Argumenten erneut gestartet wird, unterscheidet sich die "Maps" -Datei und Sie werden wissen, dass Sie nicht mit dem ursprünglichen Prozess zu tun haben.

Siehe: proc man page für Details.

Beachten Sie, dass die Datei /proc/[pid]/statbereits alle Informationen enthält, die andere Poster in ihren Antworten angegeben haben: Alter des Prozesses, übergeordnete PID usw. Diese Datei enthält sowohl statische als auch dynamische Informationen. Wenn Sie diese Datei also als Basis verwenden möchten, müssen Sie sie verwenden Nach dem Starten von longrunningthingmüssen Sie die folgenden statischen Felder aus der statDatei extrahieren und zum späteren Vergleich speichern:

PID, Dateiname, PID des übergeordneten Elements, Prozessgruppen-ID, steuerndes Terminal, Zeit, die der Prozess nach dem Systemstart gestartet wurde, Größe des residenten Satzes, Adresse des Starts des Stapels,

Zusammengenommen identifizieren die oben genannten den Prozess eindeutig, und so ist dies ein weiterer Weg. Tatsächlich konnte man nur mit "pid" und "time process started after system boot" mit einem hohen Maß an Sicherheit davonkommen. Extrahieren Sie einfach diese Felder aus der statDatei und speichern Sie sie beim Starten Ihres Prozesses. Später, bevor Sie es töten, extrahieren Sie es erneut und vergleichen Sie. Wenn sie übereinstimmen, können Sie sicher sein, dass Sie sich den ursprünglichen Prozess ansehen.

Michael Martinez
quelle
1
Das funktioniert im Allgemeinen nicht so, wie es /proc/[pid]/mapssich im Laufe der Zeit ändert, wenn zusätzlicher Speicher zugewiesen wird oder der Stapel wächst oder neue Dateien zugeordnet werden ... Und was bedeutet unmittelbar nach dem Start ? Nachdem alle Bibliotheken gemappt wurden? Wie stellen Sie das fest?
Stéphane Chazelas
Ich mache gerade einen Test auf meinem System mit zwei Prozessen, einem Java-App und einem Cfengine-Server. Alle 15 Minuten mache ich md5sumauf ihren Karten Dateien. Ich lasse es ein oder zwei Tage laufen und berichte hier mit den Ergebnissen.
Michael Martinez
@ StéphaneChazelas: Ich habe meine beiden Prozesse jetzt seit 16 Stunden überprüft und es gab keine Änderung in der md5sum
Michael Martinez
-1

Eine andere Möglichkeit wäre, das Alter des Prozesses zu überprüfen, bevor er beendet wird. Auf diese Weise können Sie sicherstellen, dass Sie einen Prozess, der nicht in weniger als 24 Stunden gestartet wurde, nicht beenden. Sie können eine darauf ifbasierende Bedingung hinzufügen , bevor Sie den Prozess beenden.

if [[ $(ps -p $p -o etime=) =~ 1-. ]] ; then
    kill $p
fi

Diese ifBedingung prüft, ob die Prozess-ID $pweniger als 24 Stunden (86400 Sekunden) beträgt.

PS: - Der Befehl ps -p $p -o etime=hat das Format<no.of days>-HH:MM:SS

Sree
quelle
Das mtimevon /proc/$phat nichts mit der Startzeit des Prozesses zu tun.
Stéphane Chazelas
Vielen Dank @ StéphaneChazelas. Du hast recht. Ich habe die Antwort bearbeitet, um den ifZustand zu ändern . Bitte zögern Sie nicht zu kommentieren, wenn es fehlerhaft ist.
Sree
-3

Was ich tue, ist, nachdem ich den Prozess beendet habe, es erneut zu tun. Jedes Mal, wenn ich das tue, kommt die Antwort zurück: "Kein solcher Prozess."

allenb   12084  5473  0 08:12 pts/4    00:00:00 man man
allenb@allenb-P7812 ~ $ kill -9 12084
allenb@allenb-P7812 ~ $ kill -9 12084
bash: kill: (12084) - No such process
allenb@allenb-P7812 ~ $ 

Einfacher geht es nicht und ich mache das seit Jahren ohne Probleme.

Allen
quelle
Das ist die Antwort auf die Frage "Wie kann ich es noch schlimmer machen?" Und nicht auf die Frage "Wie kann ich es beheben?".
Stéphane Chazelas