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.
killall
co pasuje do nazwy, więc przynajmniej zabijasz proces o tej samej nazwie colongrunningthing
. Zakładając, że będziesz mieć tylko jeden z nich naraz.Odpowiedzi:
Najlepiej byłoby użyć
timeout
polecenia, jeśli je masz: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 przedSIGALRM
dostarczeniem pomiędzywaitpid()
powrotem atimeout
wyjściem (skuteczne anulowanie tego alarmu ). Podczas tego małego oknatimeout
moż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).
timeout
doł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
:Na stronie http://devel.ringlet.net/sysutils/timelimit/ znajduje się
timelimit
polecenie (poprzedza GNU o kilka miesięcy).timeout
Ten używa
alarm()
podobnego mechanizmu, ale instaluje moduł obsługiSIGCHLD
(ignorując zatrzymane dzieci) w celu wykrycia śmierci dziecka. Anuluje również alarm przed uruchomieniemwaitpid()
(nie anuluje dostarczenia,SIGALRM
jeśli był w toku, ale sposób, w jaki jest napisany, nie widzę problemu) i zabija przed wywołaniemwaitpid()
(więc nie mogę zabić ponownie wykorzystanego pid ).netpipes ma również
timelimit
polecenie. To, że wyprzedza wszystkie pozostałe o dziesięciolecia, przyjmuje jeszcze inne podejście, ale nie działa poprawnie dla zatrzymanych poleceń i zwraca1
status wyjścia po upływie limitu czasu.Jako bardziej bezpośrednią odpowiedź na twoje pytanie możesz zrobić coś takiego:
To znaczy, sprawdź, czy proces jest nadal naszym dzieckiem. Znów jest małe okno wyścigu (pomiędzy
ps
odzyskaniem statusu tego procesu akill
zabiciem 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.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:
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 (ikill
zgłosiłoby błąd).bash
„skill
co najmniej bloków SIGCHLD zanim dostęp swoją tabelę pracy, aby rozwinąć%
i odblokowuje to pokill()
.Innym rozwiązaniem, aby uniknąć tego
sleep
procesu wiszące wokół nawet pocmd
umarł, zbash
lubksh93
jest użycie ruręread -t
zamiastsleep
: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
:(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:
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
.źródło
timeout
nie jest przenośny, w odpowiedzi wspomniano najpierw o przenośnym rozwiązaniu.jobs
a 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])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
SIGTERM
lubSIGQUIT
ponad pty.Jeszcze innym sposobem, który jest wygodniejszy w skryptowaniu, jest użycie nazwanej
screen
sesji 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ódło
Podczas uruchamiania procesu oszczędzaj jego czas rozpoczęcia:
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ć)
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ć:
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
psutil
który automatycznie obsługuje ponowne użycie PID:źródło
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 woliszTZ=UTC0 ps -o lstart=
.lstart
,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/pid
pliku.man namespaces
-init
.man pid_namespaces
-util-linux
Pakiet zawiera wiele przydatnych narzędzi do manipulowania nazw. Na przykład jestunshare
jednak tak, że jeśli nie ustawiłeś jeszcze swoich praw w przestrzeni nazw użytkownika, będzie to wymagało uprawnień administratora:Jeśli nie ustawiłeś przestrzeni nazw użytkownika, możesz nadal bezpiecznie wykonywać dowolne polecenia, natychmiast porzucając uprawnienia.
runuser
Polecenia jest inny (nie setuid) binarny dostarczane przezutil-linux
pakiet i wprowadzenie może wyglądać następująco:...i tak dalej.
W powyższym przykładzie dwa przełączniki są przekazywane do
unshare(1)
tej--fork
flagi, która sprawia, że wywołanysh -c
proces pierwsze dziecko utworzony i zapewnia jegoinit
stan, a--pid
flaga, która nakazujeunshare(1)
, aby stworzyć przestrzeń nazw PID.Proces
sh -c
ten tworzy pięć potomnych powłok w tle - każdą nieskończonąwhile
pętlę, która będzie dołączała dane wyjściowedate
do końcalog
tak długo, jak długosleep 1
zwraca wartość true. Po spawnowaniu procesy tesh
wymagająsleep
dodatkowych 5 sekund, a następnie kończą się.Warto zauważyć, że gdyby
-f
flaga nie była używana, żadna zwhile
pętli w tle nie zakończyłaby się, ale wraz z nią ...WYDAJNOŚĆ:
źródło
Zastanów się nad tym, aby
longrunningthing
zachować 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: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ś takiegoopakowanie upewni się, że plik pid został usunięty.
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:
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
longrunningthing
moż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.
źródło
longrunningthing
to, ż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!timeout
to nie wbudowanym poleceniem powłoki. Istnieją różne implementacjetimeout
komendy 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.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 :
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.
źródło
cat pidfile
wynik. Nie pamiętam czystego sposobu, aby to zrobić tylko w skorupkach. Proponowana odpowiedź na przestrzeń nazw wydaje się jednak interesująca ...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]/maps
i 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]/maps
je, 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]/stat
zawiera 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 uruchomieniulongrunningthing
należy wyodrębnić zstat
pliku 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
stat
pliku 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.źródło
/proc/[pid]/maps
zmiany 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ć?md5sum
na plikach map. Pozwolę, by działało to przez dzień lub dwa, i przekażę raport z wynikami.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ć
if
warunek na tej podstawie przed zabiciem procesu.Ten
if
warunek sprawdzi, czy identyfikator procesu$p
jest 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
źródło
mtime
Z/proc/$p
nie ma nic wspólnego z czasem rozpoczęcia procesu.if
warunek. Prosimy o komentarz, jeśli jest wadliwy.Po zabiciu tego procesu robię to jeszcze raz. Za każdym razem, gdy to robię, odpowiedź powraca: „nie ma takiego procesu”
Nie może być prościej i robię to od lat bez żadnych problemów.
źródło