Jak mogę zabić proces i mieć pewność, że PID nie został ponownie użyty

40

Załóżmy na przykład, że masz skrypt powłoki podobny do:

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

Powinien załatwić sprawę, prawda? Tyle że proces mógł zostać wcześniej zakończony, a jego PID mógł zostać poddany recyklingowi, co oznacza, że ​​niektóre niewinne prace dostają bombę w kolejce sygnałowej. W praktyce może to mieć znaczenie, ale martwi mnie to. Hakowanie długiego wybiegania, aby samemu upaść lub zachować / usunąć swój PID na FS, wystarczy, ale myślę o ogólnej sytuacji tutaj.

FJL
źródło
3
Dajesz pewność, że jeśli proces docelowy umrze, zabija on zabójcę.
mikeserv
2
Użyj tego, killallco pasuje do nazwy, więc przynajmniej zabijasz proces o tej samej nazwie co longrunningthing. Zakładając, że będziesz mieć tylko jeden z nich naraz.
LawrenceC,
8
Możesz zapisać czas rozpoczęcia oryginalnego procesu, a przed zabiciem sprawdzić, czy czas rozpoczęcia procesu z tym pidem odpowiada zapisanemu. Para pid, czas rozpoczęcia jest unikalnym identyfikatorem procesów w systemie Linux.
Bakuriu
1
Czy mogę zapytać, dlaczego jest to potrzebne? jaka jest podstawowa rzecz, którą próbujesz osiągnąć? (coś, co działa nieprzerwanie, ale jest resetowane co 24 godziny?)
Olivier Dulac
2
@mikeserv Proces nie może zagwarantować, że coś się wydarzy w przypadku własnej śmierci.
kasperd

Odpowiedzi:

29

Najlepiej byłoby użyć timeoutpolecenia, jeśli je masz:

timeout 86400 cmd

Obecna implementacja GNU (8.23) działa przynajmniej przy użyciu alarm()lub równoważnej podczas oczekiwania na proces potomny. Wydaje się, że nie chroni przed SIGALRMdostarczeniem pomiędzy waitpid()powrotem a timeoutwyjściem (skuteczne anulowanie tego alarmu ). Podczas tego małego okna timeoutmoże nawet pisać wiadomości na stderr (na przykład, jeśli dziecko zrzuci rdzeń), co jeszcze bardziej powiększy to okno wyścigu (na czas nieokreślony, jeśli stderr jest na przykład pełną potokiem).

Osobiście mogę żyć z tym ograniczeniem (które prawdopodobnie zostanie naprawione w przyszłej wersji). timeoutdołoży także starań, aby zgłosić poprawny status wyjścia, obsługiwać inne przypadki narożne (takie jak SIGALRM zablokowane / ignorowane przy uruchamianiu, obsługiwać inne sygnały ...) lepiej niż prawdopodobnie robiłbyś to ręcznie.

Dla przybliżenia możesz napisać w następujący sposób 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

Na stronie http://devel.ringlet.net/sysutils/timelimit/ znajduje się timelimitpolecenie (poprzedza GNU o kilka miesięcy).timeout

 timelimit -t 86400 cmd

Ten używa alarm()podobnego mechanizmu, ale instaluje moduł obsługi SIGCHLD(ignorując zatrzymane dzieci) w celu wykrycia śmierci dziecka. Anuluje również alarm przed uruchomieniem waitpid()(nie anuluje dostarczenia, SIGALRMjeśli był w toku, ale sposób, w jaki jest napisany, nie widzę problemu) i zabija przed wywołaniem waitpid()(więc nie mogę zabić ponownie wykorzystanego pid ).

netpipes ma również timelimitpolecenie. To, że wyprzedza wszystkie pozostałe o dziesięciolecia, przyjmuje jeszcze inne podejście, ale nie działa poprawnie dla zatrzymanych poleceń i zwraca 1status wyjścia po upływie limitu czasu.

Jako bardziej bezpośrednią odpowiedź na twoje pytanie możesz zrobić coś takiego:

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

To znaczy, sprawdź, czy proces jest nadal naszym dzieckiem. Znów jest małe okno wyścigu (pomiędzy psodzyskaniem statusu tego procesu a killzabiciem go), podczas którego proces może umrzeć, a jego pid może zostać ponownie wykorzystany przez inny proces.

Z niektórych muszli ( zsh, bash, mksh), można przekazać widowisko pracy zamiast PID.

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

Działa to tylko wtedy, gdy spawnujesz tylko jedno zadanie w tle (w przeciwnym razie uzyskanie właściwego rodzaju zadania nie zawsze będzie możliwe w sposób niezawodny).

Jeśli to jest problem, po prostu uruchom nową instancję powłoki:

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

Działa to, ponieważ powłoka usuwa zadanie ze stołu zadań po śmierci dziecka. Tutaj nie powinno być żadnego okna wyścigu, ponieważ do czasu wywołania powłoki kill()albo sygnał SIGCHLD nie został obsłużony, a pid nie mógł zostać ponownie użyty (ponieważ nie był oczekiwany), lub został obsłużony, a zadanie zostało usunięte z tabeli procesów (i killzgłosiłoby błąd). bash„s killco najmniej bloków SIGCHLD zanim dostęp swoją tabelę pracy, aby rozwinąć %i odblokowuje to po kill().

Innym rozwiązaniem, aby uniknąć tego sleepprocesu wiszące wokół nawet po cmdumarł, z bashlub ksh93jest użycie rurę read -tzamiast sleep:

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

Ten nadal ma warunki wyścigu i tracisz status wyjścia z polecenia. Zakłada również, cmdże nie zamyka swojego fd 4.

Możesz spróbować wdrożyć rozwiązanie bez wyścigu w 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...

(choć należałoby go ulepszyć, aby obsługiwał inne typy skrzynek narożnych).

Inną bez rasową metodą może być użycie grup procesów:

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

Należy jednak pamiętać, że korzystanie z grup procesów może mieć skutki uboczne, jeśli zaangażowane jest we / wy do urządzenia końcowego. Ma jednak tę dodatkową zaletę, że zabija wszystkie inne dodatkowe procesy odradzane przez cmd.

Stéphane Chazelas
źródło
4
Dlaczego nie wspomnieć najpierw o najlepszej metodzie?
deltab
2
@deltab: timeoutnie jest przenośny, w odpowiedzi wspomniano najpierw o przenośnym rozwiązaniu.
cuonglm
1
@deltab: daje wgląd w to, jak działają rzeczy, a zwłaszcza w jaki sposób podejście „zdrowego rozsądku” może zawieść (Stephane woli nauczyć najpierw łowienia ryb, co lubię). Oczekuje się, że przeczyta się całą odpowiedź
Olivier Dulac
@Stephane: ponieważ „uzyskanie właściwego rodzaju zadania nie zawsze jest możliwe niezawodnie”: czy nie możesz najpierw policzyć wyników, jobsa następnie wiedzieć, że (ponieważ jest to twoja własna powłoka, w której masz kontrolę nad tym, co będzie dalej), następne praca będzie wynosić N + 1? [wtedy możesz uratować N, a później zabić% N + 1])
Olivier Dulac
1
@OlivierDulac, który zakładałby, że żadne wcześniejsze zadanie nie zostało zakończone do czasu rozpoczęcia nowego (powłoki ponownie wykorzystują numery zadań).
Stéphane Chazelas,
28

Ogólnie nie możesz. Wszystkie dotychczasowe odpowiedzi to błędna heurystyka. Jest tylko jeden przypadek, w którym możesz bezpiecznie używać pid do wysyłania sygnałów: gdy proces docelowy jest bezpośrednim potomkiem procesu, który będzie wysyłał sygnał, a rodzic jeszcze na niego nie czekał. W takim przypadku, nawet jeśli wyszedł, pid jest zarezerwowany (tak właśnie jest „procesem zombie”), dopóki rodzic na niego nie poczeka. Nie znam żadnego sposobu, aby zrobić to czysto za pomocą powłoki.

Alternatywnym bezpiecznym sposobem na zabicie procesów jest uruchomienie ich ze sterującym zestawem tty na pseudo-terminalu, dla którego jesteś właścicielem strony głównej. Następnie możesz wysyłać sygnały przez terminal, np. Zapisując znak za SIGTERMlub SIGQUITponad pty.

Jeszcze innym sposobem, który jest wygodniejszy w skryptowaniu, jest użycie nazwanej screensesji i wysłanie poleceń do sesji ekranowej, aby ją zakończyć. Proces ten odbywa się za pomocą potoku lub gniazda unix o nazwie zgodnej z sesją ekranową, która nie zostanie automatycznie ponownie użyta, jeśli wybierzesz bezpieczną unikalną nazwę.

R ..
źródło
4
Nie rozumiem, dlaczego nie można tego zrobić w skorupkach. Dałem kilka rozwiązań.
Stéphane Chazelas
3
Czy mógłbyś podać wyjaśnienie i jakieś ilościowe omówienie okien wyścigu i innych wad? Bez tego „wszystkie dotychczasowe odpowiedzi to błędna heurystyka” jest niepotrzebnie konfrontacyjna bez żadnych korzyści.
Peter
3
@peterph: Ogólnie rzecz biorąc, każde użycie pid jest wyścigiem TOCTOU - bez względu na to, jak sprawdzisz, czy nadal odnosi się do tego samego procesu, do którego się spodziewasz, może przestać odnosić się do tego procesu i odnosić się do niektórych nowych przetwarzaj w interwale przed użyciem (wysyłanie sygnału). Jedynym sposobem, aby temu zapobiec, jest możliwość zablokowania zwalniania / ponownego użycia pid, a jedynym procesem, który może to zrobić, jest bezpośredni rodzic.
R ..
2
@ StéphaneChazelas: Jak zapobiec oczekiwaniu powłoki na pid procesu, który zakończył się w tle? Jeśli możesz to zrobić, problem można łatwo rozwiązać w razie potrzeby OP.
R ..
5
@peterph: „Okno wyścigu jest małe” nie jest rozwiązaniem. A rzadkość wyścigu zależy od sekwencyjnego przypisywania pid. Błędy, które powodują, że coś złego dzieje się raz w roku, są znacznie gorsze niż błędy, które zdarzają się cały czas, ponieważ są praktycznie niemożliwe do zdiagnozowania i naprawienia.
R ..
10
  1. Podczas uruchamiania procesu oszczędzaj jego czas rozpoczęcia:

    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. Zanim spróbujesz zabić proces, zatrzymaj go (nie jest to naprawdę konieczne, ale jest to sposób na uniknięcie warunków wyścigu: jeśli zatrzymasz proces, nie będzie można go ponownie wykorzystać)

    kill -s STOP "$p"
    
  3. Sprawdź, czy proces z tym PID ma ten sam czas rozpoczęcia, a jeśli tak, zabij go, w przeciwnym razie pozwól procesowi kontynuować:

    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
    

Działa to, ponieważ w danym systemie operacyjnym może istnieć tylko jeden proces z tym samym PID i czasem rozpoczęcia.

Zatrzymanie procesu podczas kontroli sprawia, że ​​warunki wyścigu nie stanowią problemu. Oczywiście ma to problem polegający na tym, że niektóre losowe procesy mogą zostać zatrzymane na kilka milisekund. W zależności od rodzaju procesu może to stanowić problem.


Osobiście po prostu użyłbym Pythona i psutilktóry automatycznie obsługuje ponowne użycie PID:

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
źródło
Python rządzi w UNIX ... Nie jestem pewien, dlaczego więcej odpowiedzi nie zaczyna się tam, ponieważ jestem pewien, że większość systemów nie zabrania korzystania z niego.
Pan Mascaro,
Użyłem wcześniej podobnego schematu (używając czasu rozpoczęcia), ale twoje umiejętności pisania skryptów sh są starsze niż moje! Dzięki.
FJL,
Oznacza to, że potencjalnie zatrzymujesz niewłaściwy proces. Pamiętaj, że ps -o start=format zmienia się po pewnym czasie z 18:12 na Jan26. Uważaj również na zmiany DST. Jeśli w systemie Linux, prawdopodobnie wolisz TZ=UTC0 ps -o lstart=.
Stéphane Chazelas,
@ StéphaneChazelas Tak, ale pozwalasz mu kontynuować. Powiedziałem wyraźnie: w zależności od rodzaju zadania, które wykonuje ten proces, możesz mieć problemy z zatrzymaniem go na kilka milisekund. Dzięki za podpowiedź lstart,
zredaguję
Zauważ, że (o ile twój system nie ogranicza liczby procesów na użytkownika), każdemu łatwo jest wypełnić tabelę procesów zombie. Gdy zostaną tylko 3 dostępne stawki, każdy może rozpocząć setki różnych procesów z tym samym wskaźnikiem w ciągu jednej sekundy. Tak więc, ściśle mówiąc, twój „może istnieć tylko jeden proces z tym samym PID i czasem rozpoczęcia w danym systemie operacyjnym” niekoniecznie jest prawdziwy.
Stéphane Chazelas
7

W systemie Linux możesz mieć pewność, że pid nie zostanie ponownie użyty, utrzymując przestrzeń nazw pid przy życiu. Można to zrobić za pomocą /proc/$pid/ns/pidpliku.

  • man namespaces -

    Powiązanie montowania (patrz mount(2)) jednego z plików w tym katalogu z innym miejscem w systemie plików utrzymuje odpowiednią przestrzeń nazw procesu określonego przez pid, nawet jeśli wszystkie procesy aktualnie znajdujące się w przestrzeni nazw zakończą się.

    Otwarcie jednego z plików w tym katalogu (lub pliku podłączonego do jednego z tych plików) zwraca uchwyt pliku dla odpowiedniej przestrzeni nazw procesu określonego przez pid. Dopóki ten deskryptor pliku pozostanie otwarty, przestrzeń nazw pozostanie aktywna, nawet jeśli wszystkie procesy w przestrzeni nazw zostaną zakończone. Deskryptor pliku można przekazać setns(2).

Możesz izolować grupę procesów - w zasadzie dowolną liczbę procesów - poprzez ich przestrzeń nazw init.

  • man pid_namespaces -

    Pierwszy proces stworzony w nowej przestrzeni nazw (czyli proces tworzony przy użyciu clone(2) z CLONE_NEWPID flagi, lub pierwszego dziecka stworzonej przez proces po wywołaniu unshare(2)za pomocą CLONE_NEWPID flagę) ma na PID 1 i jest initproces przestrzeni nazw ( patrz init(1)) . Proces potomny, który jest osierocony w przestrzeni nazw, zostanie ponownie powiązany z tym procesem init(1) (chyba że jeden z przodków dziecka w tej samej przestrzeni nazw PID prctl(2) użył polecenia PR_SET_CHILD_SUBREAPER, aby oznaczyć siebie jako żniwiarza osieroconych procesów potomnych) .

    Jeśli initproces przestrzeni nazw PID zakończy się, jądro kończy wszystkie procesy w przestrzeni nazw sygnałem SIGKILL . To zachowanie odzwierciedla fakt, że initproces jest niezbędny do poprawnego działania przestrzeni nazw PID .

util-linuxPakiet zawiera wiele przydatnych narzędzi do manipulowania nazw. Na przykład jest unsharejednak tak, że jeśli nie ustawiłeś jeszcze swoich praw w przestrzeni nazw użytkownika, będzie to wymagało uprawnień administratora:

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

Jeśli nie ustawiłeś przestrzeni nazw użytkownika, możesz nadal bezpiecznie wykonywać dowolne polecenia, natychmiast porzucając uprawnienia. runuserPolecenia jest inny (nie setuid) binarny dostarczane przez util-linuxpakiet i wprowadzenie może wyglądać następująco:

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

...i tak dalej.

W powyższym przykładzie dwa przełączniki są przekazywane do unshare(1)tej --forkflagi, która sprawia, że wywołany sh -cproces pierwsze dziecko utworzony i zapewnia jego initstan, a --pidflaga, która nakazuje unshare(1), aby stworzyć przestrzeń nazw PID.

Proces sh -cten tworzy pięć potomnych powłok w tle - każdą nieskończoną whilepętlę, która będzie dołączała dane wyjściowe datedo końca logtak długo, jak długo sleep 1zwraca wartość true. Po spawnowaniu procesy te shwymagają sleepdodatkowych 5 sekund, a następnie kończą się.

Warto zauważyć, że gdyby -fflaga nie była używana, żadna z whilepętli w tle nie zakończyłaby się, ale wraz z nią ...

WYDAJNOŚĆ:

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
źródło
Interesująca odpowiedź, która wydaje się solidna. Prawdopodobnie trochę przesada w przypadku podstawowego użycia, ale warto się nad tym zastanowić.
Uriel
Nie wiem, jak i dlaczego utrzymanie przestrzeni nazw PID przy życiu uniemożliwia ponowne użycie PID. Podana przez ciebie strona podręczna - dopóki deskryptor pliku pozostanie otwarty, przestrzeń nazw pozostanie aktywna, nawet jeśli wszystkie procesy w przestrzeni nazw zakończą się - sugeruje, że procesy mogą się nadal kończyć (a zatem prawdopodobnie odzyskano ich identyfikator procesu). Co utrzymanie przestrzeni nazw PID przy życiu ma na celu zapobieganie ponownemu wykorzystaniu samego PID w innym procesie?
davmac
5

Zastanów się nad tym, aby longrunningthingzachować się nieco lepiej, bardziej przypominając demona. Na przykład możesz zmusić go do utworzenia pliku pid , który pozwoli przynajmniej na ograniczoną kontrolę procesu. Istnieje kilka sposobów na zrobienie tego bez modyfikowania oryginalnego pliku binarnego, z których wszystkie obejmują opakowanie. Na przykład:

  1. prosty skrypt otoki, który uruchomi wymagane zadanie w tle (z opcjonalnym przekierowaniem wyjścia), zapisz PID tego procesu do pliku, a następnie poczekaj, aż proces się zakończy (za pomocą wait) i usunie plik. Jeśli podczas oczekiwania proces zostanie zabity np. Przez coś takiego

    kill $(cat pidfile)
    

    opakowanie upewni się, że plik pid został usunięty.

  2. opakowanie monitora, które umieści gdzieś swój własny PID i przechwytuje (i reaguje na) wysyłane do niego sygnały. Prosty przykład:

    #!/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"

Teraz, jak zauważyli @R .. i @ StéphaneChazelas, podejścia te często mają gdzieś warunek wyścigu lub nakładają ograniczenia na liczbę procesów, które możesz spawnować. Ponadto nie obsługuje przypadków, w których longrunningthingmoże rozwidlać się, a dzieci zostają odłączone (co prawdopodobnie nie stanowiło problemu w pierwotnym pytaniu).

W przypadku najnowszych (przeczytanych kilka lat) jąder Linuksa można to ładnie potraktować za pomocą cgroups , a mianowicie zamrażarki - co, jak sądzę, jest tym, czego używają niektóre nowoczesne systemy inicjujące Linuksa.

Peter
źródło
Dziękuję i wszystkim. Teraz wszystko czytam. Chodzi o longrunningthingto, że nie masz kontroli nad tym, co to jest. Podałem również przykład skryptu powłoki, ponieważ wyjaśnił problem. Lubię twoje i wszystkie inne kreatywne rozwiązania tutaj, ale jeśli używasz Linuksa / bash, masz wbudowaną funkcję „limitu czasu”. Przypuszczam, że powinienem znaleźć źródło tego i zobaczyć, jak to działa!
FJL
@FJL, timeoutto nie wbudowanym poleceniem powłoki. Istnieją różne implementacje timeoutkomendy dla Linuksa, jedna została niedawno (2008) dodana do jądra GNU (więc nie jest specyficzna dla Linuksa) i właśnie z tego korzysta obecnie większość dystrybucji Linuksa.
Stéphane Chazelas
@ Stéphane - Dzięki - później znalazłem odniesienie do coreutils GNU. Mogą być przenośne, ale jeśli nie jest to system podstawowy, nie można na nim polegać. Bardziej interesuje mnie wiedza o tym, jak to działa, chociaż odnotowuję twój komentarz w innym miejscu sugerujący, że nie jest w 100% wiarygodny. Biorąc pod uwagę sposób, w jaki ten wątek poszedł, nie jestem zaskoczony!
FJL
1

Jeśli używasz Linuksa (i kilku innych * nixów), możesz sprawdzić, czy proces, który chcesz zabić, jest nadal używany i czy wiersz poleceń pasuje do twojego długiego procesu. Coś jak :

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

Alternatywą może być sprawdzenie, jak długo trwa proces, który chcesz zabić, za pomocą czegoś takiego ps -p $p -o etime=. Możesz to zrobić samodzielnie, wyodrębniając te informacje /proc/$p/stat, ale byłoby to trudne (czas mierzony jest w jiffies i będziesz musiał również wykorzystać czas pracy systemu /proc/stat).

W każdym razie zazwyczaj nie możesz upewnić się, że proces nie zostanie zastąpiony po sprawdzeniu i przed jego zabiciem.

Uriel
źródło
To wciąż nie jest poprawne, ponieważ nie pozbywa się warunków wyścigu.
strcat,
@strcat Rzeczywiście, nie ma gwarancji sukcesu, ale większość skryptów nawet nie zadaje sobie trudu, aby wykonać taką kontrolę i tylko tępo zabija cat pidfilewynik. Nie pamiętam czystego sposobu, aby to zrobić tylko w skorupkach. Proponowana odpowiedź na przestrzeń nazw wydaje się jednak interesująca ...
Uriel
-1

To właściwie bardzo dobre pytanie.

Sposobem ustalenia wyjątkowości procesu jest przyjrzenie się (a) miejscu w pamięci; oraz (b) co zawiera ta pamięć. Mówiąc konkretnie, chcemy wiedzieć, gdzie w pamięci znajduje się tekst programu do pierwszego wywołania, ponieważ wiemy, że obszar tekstowy każdego wątku zajmie inną lokalizację w pamięci. Jeśli proces umrze, a inny zostanie uruchomiony z tym samym pid, tekst programu nowego procesu nie zajmie tego samego miejsca w pamięci i nie będzie zawierał tych samych informacji.

Więc natychmiast po uruchomieniu procesu wykonaj md5sum /proc/[pid]/mapsi zapisz wynik. Później, gdy chcesz zabić proces, zrób kolejny md5sum i porównaj go. Jeśli pasuje, zabij pid. Jeśli nie, nie rób tego.

aby się przekonać, uruchom dwie identyczne powłoki bash. Sprawdź /proc/[pid]/mapsje, a przekonasz się, że są różne. Czemu? Ponieważ mimo że jest to ten sam program, zajmują one różne miejsca w pamięci, a adresy ich stosów są różne. Tak więc, jeśli twój proces umrze, a jego PID zostanie ponownie użyty, nawet po ponownym uruchomieniu tego samego polecenia z tymi samymi argumentami , plik „map” będzie inny i będziesz wiedział, że nie masz do czynienia z pierwotnym procesem.

Szczegółowe informacje można znaleźć na stronie proc proc .

Zauważ, że plik /proc/[pid]/statzawiera już wszystkie informacje, o których wspominał inny plakat: wiek procesu, pid nadrzędny itp. Plik ten zawiera zarówno informacje statyczne, jak i dynamiczne, więc jeśli wolisz używać tego pliku jako podstawy porównania, a następnie po uruchomieniu longrunningthingnależy wyodrębnić z statpliku następujące pola statyczne i zapisać je do porównania później:

pid, nazwa pliku, pid rodzica, identyfikator grupy procesów, terminal sterujący, proces czasu rozpoczęty po uruchomieniu systemu, rozmiar zestawu rezydenta, adres początku stosu,

wzięte razem, powyższe jednoznacznie identyfikują proces, a zatem stanowi to inną drogę. W rzeczywistości można uciec z niczym więcej niż „pid” i „proces czasu rozpoczęty po uruchomieniu systemu” z wysokim poziomem pewności. Po prostu wyodrębnij te pola z statpliku i zapisz je gdzieś po uruchomieniu procesu. Później przed zabiciem wyodrębnij go ponownie i porównaj. Jeśli się zgadzają, masz pewność, że patrzysz na oryginalny proces.

Michael Martinez
źródło
1
Zasadniczo nie będzie to działało jak /proc/[pid]/mapszmiany w czasie, gdy przydzielana jest dodatkowa pamięć lub rośnie stos lub nowe pliki są mapowane ... A co to znaczy zaraz po uruchomieniu ? Po zmapowaniu wszystkich bibliotek? Jak to określić?
Stéphane Chazelas
Robię teraz test w moim systemie z dwoma procesami, jednym z aplikacji Java i drugim serwerem cfengine. Co 15 minut robię md5sumna plikach map. Pozwolę, by działało to przez dzień lub dwa, i przekażę raport z wynikami.
Michael Martinez
@ StéphaneChazelas: Sprawdzam moje dwa procesy od 16 godzin i nie było żadnych zmian w md5sum
Michael Martinez
-1

Innym sposobem byłoby sprawdzenie wieku procesu przed jego zabiciem. W ten sposób możesz upewnić się, że nie zabijasz procesu, który nie pojawi się w ciągu mniej niż 24 godzin. Możesz dodać ifwarunek na tej podstawie przed zabiciem procesu.

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

Ten ifwarunek sprawdzi, czy identyfikator procesu $pjest krótszy niż 24 godziny (86400 sekund).

PS: - Polecenie ps -p $p -o etime=będzie miało format<no.of days>-HH:MM:SS

Sree
źródło
mtimeZ /proc/$pnie ma nic wspólnego z czasem rozpoczęcia procesu.
Stéphane Chazelas
Dzięki @ StéphaneChazelas. Masz rację. Zredagowałem odpowiedź, aby zmienić ifwarunek. Prosimy o komentarz, jeśli jest wadliwy.
Sree
-3

Po zabiciu tego procesu robię to jeszcze raz. Za każdym razem, gdy to robię, odpowiedź powraca: „nie ma takiego procesu”

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 ~ $ 

Nie może być prościej i robię to od lat bez żadnych problemów.

Allen
źródło
Odpowiada to na pytanie „jak to pogorszyć”, a nie „jak to naprawić”.
Stéphane Chazelas,