Co to jest proces nieprzerywalny?


156

Czasami, gdy piszę program w Linuksie i ulega awarii z powodu jakiegoś błędu, stanie się procesem nieprzerywalnym i będzie działał wiecznie, dopóki nie uruchomię ponownie komputera (nawet jeśli się wyloguję). Moje pytania to:

  • Co powoduje, że proces staje się nieprzerywalny?
  • Jak mogę temu zapobiec?
  • To prawdopodobnie głupie pytanie, ale czy istnieje sposób, aby je przerwać bez ponownego uruchamiania komputera?

Czy jest możliwe, że można napisać program w celu zainicjowania procesu, który przechodzi w TASK_UNINTERUPTIBLEstan, gdy system nie jest w stanie bezczynności, zbierając w ten sposób na siłę dane i czekając na przesłanie, gdy superużytkownik wyjdzie? Byłaby to kopalnia złota dla hakerów do pobierania informacji, powrotu do stanu zombie i przesyłania informacji przez sieć w trybie bezczynności. Niektórzy mogą argumentować, że jest to jeden ze sposobów tworzenia Blackdooruprawnień, które są, aby wchodzić i wychodzić z dowolnego systemu zgodnie z życzeniem. Jestem głęboko przekonany, że tę lukę można zapieczętować na dobre, eliminując `` TASK_UNINTERUPTIB
Nuuwski

2
byłoby proszę udostępnić kod?
ponownie

Odpowiedzi:


198

Proces nieprzerywalny to proces znajdujący się w wywołaniu systemowym (funkcja jądra), którego nie może przerwać sygnał.

Aby zrozumieć, co to oznacza, musisz zrozumieć koncepcję przerywanego wywołania systemowego. Klasycznym przykładem jest read(). Jest to wywołanie systemowe, które może zająć dużo czasu (sekundy), ponieważ może potencjalnie obejmować obracanie się dysku twardego lub poruszanie głowami. Przez większość tego czasu proces będzie spał, blokując się na sprzęcie.

Gdy proces śpi w wywołaniu systemowym, może odebrać sygnał asynchroniczny Unix (powiedzmy SIGTERM), wtedy dzieje się co następuje:

  • Wywołania systemowe kończą pracę przedwcześnie i są ustawione na powrót -EINTR do przestrzeni użytkownika.
  • Procedura obsługi sygnału jest wykonywana.
  • Jeśli proces nadal działa, pobiera wartość zwracaną z wywołania systemowego i może ponownie wykonać to samo wywołanie.

Wczesny powrót z wywołania systemowego umożliwia kodowi przestrzeni użytkownika natychmiastową zmianę jego zachowania w odpowiedzi na sygnał. Na przykład czyste zakończenie w reakcji na SIGINT lub SIGTERM.

Z drugiej strony, niektórych wywołań systemowych nie można w ten sposób przerywać. Jeśli z jakiegoś powodu system wywoła blokady, proces może pozostawać w tym stanie nie do zabicia na czas nieokreślony.

LWN opublikował w lipcu fajny artykuł, który poruszył ten temat.

Aby odpowiedzieć na pierwotne pytanie:

  • Jak temu zapobiec: dowiedz się, który sterownik powoduje problemy i albo przestań go używać, albo zostań hakerem jądra i napraw go.

  • Jak zabić nieprzerywalny proces bez ponownego uruchamiania: w jakiś sposób zakończyć wywołanie systemowe. Często najskuteczniejszym sposobem zrobienia tego bez naciskania wyłącznika zasilania jest pociągnięcie za przewód zasilający. Możesz także zostać hakerem jądra i zmusić sterownik do używania TASK_KILLABLE, jak wyjaśniono w artykule LWN.


31
Wyciągnąłem kabel zasilający z laptopa i niestety nie działa. ;-)
thecarpy 19.02.14

1
Czy nie jest to EINTR zamiast EAGAIN? Również read () zwraca -1, a errno jest ustawiane na błąd.
lethalman

2
@Dexter: Naprawdę nie rozumiesz. Przeczytaj artykuł LWN: lwn.net/Articles/288056 . Te problemy są powodowane przez leniwych programistów sterowników urządzeń i należy je naprawić w kodzie sterownika urządzenia.
ddaa

4
@ddaa "Tradycja uniksowa (a więc prawie wszystkie aplikacje) uważa, że ​​zapis w magazynie plików nie jest przerywany. Zmiana tej gwarancji nie byłaby bezpieczna ani praktyczna." -> To jest dokładnie najbardziej zła część całej tej IMO. Wystarczy przerwać żądanie odczytu / zapisu sterownika, a gdy rzeczywiste urządzenie (dysk twardy / karta sieciowa / itp.) Dostarcza dane, zignoruj ​​je. Jądro systemu operacyjnego powinno być wykonane w taki sposób, aby ŻADEN programiści nie mogli go zepsuć.
Dexter,

2
@ddaa Wiem, że Linux nie jest mikrojądrem, chociaż nie jestem pewien, która część mojego komentarza się do niego odnosi ... A czy twój komentarz oznacza, że ​​system operacyjny z mikrojądrem nie ma problemu z tymi „nieprzerywalnymi” procesami? Bo jeśli tak się nie stanie, to może czas, żebym został fanem mikrojądra ...: D
Dexter

49

Gdy proces jest w trybie użytkownika, można go przerwać w dowolnym momencie (przełączenie do trybu jądra). Kiedy jądro powraca do trybu użytkownika, sprawdza, czy są jakieś oczekujące sygnały (w tym te, które są używane do zabicia procesu, takie jak SIGTERMi SIGKILL). Oznacza to, że proces można zabić tylko po powrocie do trybu użytkownika.

Powodem, dla którego procesu nie można zabić w trybie jądra, jest to, że może on potencjalnie uszkodzić struktury jądra używane przez wszystkie inne procesy na tej samej maszynie (w ten sam sposób zabicie wątku może potencjalnie uszkodzić struktury danych używane przez inne wątki w tym samym procesie) .

Kiedy jądro musi zrobić coś, co może zająć dużo czasu (na przykład czekając na potok napisany przez inny proces lub czekając, aż sprzęt coś zrobi), zasypia, oznaczając siebie jako śpiącego i wywołując harmonogram, aby przełączył się na inny proces (jeśli nie ma procesu, który nie jest uśpiony, przełącza się na proces „fikcyjny”, który mówi procesorowi, aby nieco zwolnił i siedzi w pętli - pętli bezczynności).

Jeśli sygnał jest wysyłany do procesu uśpienia, należy go obudzić, zanim powróci do przestrzeni użytkownika, a tym samym przetworzy oczekujący sygnał. Tutaj mamy różnicę między dwoma głównymi rodzajami snu:

  • TASK_INTERRUPTIBLE, przerywany sen. Jeśli zadanie jest oznaczone tą flagą, śpi, ale można je obudzić sygnałami. Oznacza to, że kod, który oznaczył zadanie jako uśpione oczekuje na możliwy sygnał, a po przebudzeniu sprawdzi go i wróci z wywołania systemowego. Po obsłużeniu sygnału wywołanie systemowe może zostać automatycznie ponownie uruchomione (i nie będę wchodził w szczegóły, jak to działa).
  • TASK_UNINTERRUPTIBLE, nieprzerwany sen. Jeśli zadanie jest oznaczone tą flagą, nie spodziewa się, że zostanie obudzone przez coś innego niż to, na co czeka, ponieważ nie można go łatwo ponownie uruchomić lub programy oczekują, że wywołanie systemowe będzie atomowe. Można to również wykorzystać do snu, o którym wiadomo, że jest bardzo krótki.

TASK_KILLABLE (wspomniany w artykule LWN, do którego prowadzi odpowiedź ddaa) to nowy wariant.

To odpowiada na twoje pierwsze pytanie. A co do drugiego pytania: nie da się uniknąć nieprzerywalnych uśpień, są one normalne (zdarza się na przykład za każdym razem, gdy proces czyta / zapisuje z / na dysk); jednak powinny trwać tylko ułamek sekundy. Jeśli trwają znacznie dłużej, zwykle oznacza to problem sprzętowy (lub problem ze sterownikiem urządzenia, który wygląda tak samo dla jądra), w którym sterownik urządzenia czeka, aż sprzęt wykona coś, co nigdy się nie wydarzy. Może to również oznaczać, że używasz NFS i serwer NFS jest wyłączony (czeka na przywrócenie serwera; możesz także użyć opcji „intr”, aby uniknąć problemu).

Wreszcie powodem, dla którego nie można odzyskać, jest ten sam powód, dla którego jądro czeka na powrót do trybu użytkownika, aby dostarczyć sygnał lub zabić proces: mogłoby to potencjalnie uszkodzić struktury danych jądra (kod oczekujący na przerwanie uśpienia może otrzymać błąd, który mówi mu aby powrócić do przestrzeni użytkownika, gdzie proces może zostać zabity; kod oczekujący na nieprzerywany tryb uśpienia nie oczekuje żadnego błędu).


1
Prawdopodobną przyczyną jest również błąd blokowania systemu plików, IME.
Tobu

3
Nie rozumiem tego wszystkiego. „nie można uniknąć nieprzerywalnych uśpień” - czy system operacyjny nie może być tak skonstruowany, że nieprzerywalny sen po prostu NIE ISTNIEJE jako stan? Następnie część dotycząca zepsucia - czy część procesu w trybie jądra samego procesu (lub czegokolwiek MOGŁA spowodować uszkodzenie) nie może zostać zakończona, czy po prostu jej kod zmodyfikowany bezpośrednio w pamięci, aby po prostu powrócić? Proszę wyjaśnić, dlaczego jest to tak trudne / niemożliwe, że nawet Linux tego nie zrobił. (Myślałem, że ten problem istnieje tylko w systemie Windows)
Dexter

Jedynym przypadkiem, o którym mogę pomyśleć, że (bezpieczne) zabijanie tych procesów jest naprawdę niemożliwe (a nie tylko, powiedzmy, wyjątkowo trudne), jest to, że sam sprzęt mógłby spowodować uszkodzenie. Sprzęt nie może być kontrolowany; jądro może . Ale to jądro pobiera dane ze sprzętu i modyfikuje pamięć (dlatego nie można go zwolnić przed powrotem procesu do trybu użytkownika i dlaczego mogłoby dojść do uszkodzenia) ... zmienić kod jądra w pamięci i nie ma więcej problemów.
Dexter

@Dexter myśli o jądrze tak, jakby to był pojedynczy proces wielowątkowy, w którym część każdego procesu w trybie jądra jest wątkiem w jądrze. Twoja sugestia byłaby tak samo zła, jak zabicie pojedynczego wątku w programie wielowątkowym: może pozostawić wiszące blokady, struktury danych tymczasowo zmodyfikowane lub w trakcie modyfikowania i tak dalej.
CesarB

@CesarB, masz rację, jeśli chodzi o zabijanie wątku ... Ale czy "główny" wątek (na przykład jądro systemu operacyjnego i inne wątki byłyby sterownikami) jakoś sobie z tym poradzi? Chociaż te struktury "w trakcie modyfikowania" wydają się być jednym naprawdę trudnym problemem ... może naprawdę nigdy nie zobaczymy systemu operacyjnego, w którym nieprzerwane procesy byłyby niemożliwe :(
Dexter

23

Nieprzerwane procesy ZWYKLE oczekują na operacje we / wy po wystąpieniu błędu strony.

Rozważ to:

  • Wątek próbuje uzyskać dostęp do strony, która nie jest w rdzeniu (albo plik wykonywalny, który jest ładowany na żądanie, strona anonimowej pamięci, która została wymieniona, albo plik mmap () 'd, który jest ładowany na żądanie, które są w większości ta sama rzecz)
  • Jądro teraz (próbuje) go załadować
  • Proces nie może być kontynuowany, dopóki strona nie będzie dostępna.

Proces / zadanie nie może zostać przerwane w tym stanie, ponieważ nie obsługuje żadnych sygnałów; gdyby tak się stało, wystąpiłby kolejny błąd strony i wróciłby tam, gdzie był.

Kiedy mówię „proces”, naprawdę mam na myśli „zadanie”, co w Linuksie (2.6) z grubsza tłumaczy się na „wątek”, który może mieć indywidualny wpis „grupy wątków” w / proc lub nie

W niektórych przypadkach może to długo czekać. Typowym przykładem może być sytuacja, w której plik wykonywalny lub plik mmap znajduje się w sieciowym systemie plików, w którym wystąpiła awaria serwera. Jeśli I / O w końcu się powiedzie, zadanie będzie kontynuowane. Jeśli w końcu się nie powiedzie, zadanie zwykle otrzyma SIGBUS lub coś takiego.


1
Jeśli w końcu się nie powiedzie, zadanie zwykle otrzyma SIGBUS lub coś takiego. Czekaj, czy nie można zrobić jądra tak, aby podczas zabijania tych „nieprzerywalnych” procesów po prostu MÓWIŁ im o niepowodzeniu operacji we / wy? Wtedy proces wróciłby do trybu użytkownika i zniknąłby? MUSI istnieć sposób na bezpieczne zabicie tych procesów w stanie „D”. Myślę, że to po prostu nie jest łatwe i dlatego ani Windows, ani Linux nie mają jeszcze takiej możliwości. Z drugiej strony chciałbym mieć możliwość zabijania tych procesów przynajmniej w niebezpieczny sposób. Nie obchodzi mnie ewentualna awaria systemu czy coś takiego ...
Dexter

@Dexter hmm, nigdy nie spotkałem się z tym problemem w systemie Windows. Jak można to tam odtworzyć? Przynajmniej zgodnie z tym postem , wszystkie żądania we / wy mogą zostać przerwane w systemie Windows.
Ruslan

1

Na twoje trzecie pytanie: myślę, że możesz zabić nieprzerwane procesy, uruchamiając sudo kill -HUP 1. Zrestartuje init bez kończenia działających procesów, a po jego uruchomieniu moje nieprzerwane procesy zniknęły.


-3

Jeśli mówisz o procesie "zombie" (który jest oznaczony jako "zombie" w wyjściu ps), to jest to nieszkodliwy zapis na liście procesów, który czeka, aż ktoś zbierze jego kod powrotu i można go bezpiecznie zignorować.

Czy mógłbyś opisać, czym jest dla Ciebie „nieprzerwany proces”? Czy przetrwa „zabij -9” i radośnie się ugrzęźnie? Jeśli tak jest, to utknęło w jakimś wywołaniu systemowym, które utknęło w jakimś sterowniku, i utkniesz z tym procesem do ponownego uruchomienia (a czasami lepiej jest zrestartować wkrótce) lub rozładowania odpowiedniego sterownika (co jest mało prawdopodobne) . Możesz spróbować użyć „strace”, aby dowiedzieć się, gdzie utknął twój proces i uniknąć tego w przyszłości.


Czy sterowniki nie mogą być wyładowywane na siłę w ten sam sposób, w jaki można zabić proces? Wiem, że tryb jądra ma bardziej uprzywilejowany dostęp niż tryb użytkownika, ale nigdy nie może być bardziej uprzywilejowany niż sam system operacyjny. Wszystko, co wykonuje się w trybie jądra, zawsze może ingerować w cokolwiek innego wykonującego się w trybie jądra - po prostu nie ma kontroli.
Dexter
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.