W jaki sposób Linux „zabija” proces?


91

Często zaskakuje mnie to, że chociaż pracuję zawodowo z komputerami od kilku dziesięcioleci, a Linux od dekady, tak naprawdę większość funkcjonalności systemu operacyjnego traktuję jak czarną skrzynkę, a nie magię.

Dzisiaj myślałem o killpoleceniu i chociaż używam go wiele razy dziennie (zarówno w jego „normalnym”, jak i -9smaku), muszę przyznać, że absolutnie nie mam pojęcia, jak to działa za kulisami.

Z mojego punktu widzenia, jeśli uruchomiony proces jest „zawieszony”, wywołuję killjego PID, a następnie nagle przestaje działać. Magia!

Co tam naprawdę się dzieje? Strony mówią o „sygnałach”, ale na pewno to tylko abstrakcja. Wysyłanie kill -9do procesu nie wymaga współpracy procesu (jak obsługa sygnału), po prostu go zabija.

  • W jaki sposób Linux powstrzymuje proces od zajmowania czasu procesora?
  • Czy zostało to usunięte z harmonogramu?
  • Czy odłącza proces od otwartych uchwytów plików?
  • Jak zwalniana jest pamięć wirtualna procesu?
  • Czy istnieje coś takiego jak globalna tabela w pamięci, w której Linux przechowuje odniesienia do wszystkich zasobów zajętych przez proces, a kiedy „zabijam” proces, Linux po prostu przechodzi przez tę tabelę i uwalnia zasoby jeden po drugim?

Naprawdę chciałbym to wszystko wiedzieć!


Odpowiedzi:


71

Wysłanie kill -9 do procesu nie wymaga współpracy procesu (np. Obsługi sygnału), po prostu go zabija.

Zakładasz, że ponieważ niektóre sygnały można wychwycić i zignorować, wszystkie wymagają współpracy. Ale zgodnie z man 2 signalsygnałami SIGKILL i SIGSTOP nie można złapać ani zignorować”. SIGTERM może zostać złapany, dlatego zwykły killnie zawsze jest skuteczny - ogólnie oznacza to, że coś w module obsługi procesu poszło nie tak. 1

Jeśli proces nie definiuje modułu obsługi dla danego sygnału (lub nie może go zdefiniować), jądro wykonuje akcję domyślną. W przypadku SIGTERM i SIGKILL ma to zakończyć proces (chyba, że ​​jego pid wynosi 1; jądro się nie zakończy init) 2, co oznacza, że ​​jego uchwyty plików są zamknięte, jego pamięć jest zwrócona do puli systemowej, jego rodzic otrzymuje SIGCHILD, jego osierocone dzieci są dziedziczone przez init itp., tak jakby to wywołało exit(patrz man 2 exit). Proces już nie istnieje - chyba że skończy jako zombie, w którym to przypadku nadal jest wymieniony w tabeli procesów jądra z pewnymi informacjami; dzieje się tak, gdy jego rodzic niewaiti odpowiednio postępować z tymi informacjami. Jednak procesy zombie nie mają już przydzielonej pamięci i dlatego nie mogą kontynuować działania.

Czy istnieje coś takiego jak globalna tabela w pamięci, w której Linux przechowuje odniesienia do wszystkich zasobów zajętych przez proces, a kiedy „zabijam” proces, Linux po prostu przechodzi przez tę tabelę i uwalnia zasoby jeden po drugim?

Myślę, że to wystarczająco dokładne. Pamięć fizyczna jest śledzona według strony (jedna strona zwykle ma wielkość 4 KB), a strony te są pobierane i zwracane do globalnej puli. Jest to trochę bardziej skomplikowane, ponieważ niektóre bezpłatne strony są buforowane na wypadek, gdyby dane, które zawierają, były ponownie wymagane (to znaczy dane, które zostały odczytane z wciąż istniejącego pliku).

Strony mówią o „sygnałach”, ale na pewno to tylko abstrakcja.

Jasne, wszystkie sygnały są abstrakcją. Są konceptualne, podobnie jak „procesy”. Gram trochę w semantykę, ale jeśli masz na myśli, że SIGKILL różni się jakościowo od SIGTERM, to tak i nie. Tak w tym sensie, że nie można go złapać, ale nie w tym sensie, że oba są sygnałami. Analogicznie jabłko nie jest pomarańczą, ale jabłka i pomarańcze, zgodnie z przyjętą definicją, są owocami. SIGKILL wydaje się bardziej abstrakcyjny, ponieważ nie można go złapać, ale wciąż jest to sygnał. Oto przykład obsługi SIGTERM, jestem pewien, że widziałeś je już wcześniej:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void sighandler (int signum, siginfo_t *info, void *context) {
    fprintf (
        stderr,
        "Recieved %d from pid %u, uid %u.\n",
        info->si_signo,
        info->si_pid,
        info->si_uid
    );
}

int main (void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = sighandler;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGTERM, &sa, NULL);
    while (1) sleep(10);
    return 0;
}             

Ten proces będzie po prostu spał na zawsze. Możesz uruchomić go w terminalu i wysłać SIGTERM za pomocą kill. Wyrzuca rzeczy takie jak:

Recieved 15 from pid 25331, uid 1066.

1066 jest moim UID. Pid będzie odpowiadał powłoce, z której killjest wykonywany, lub pid zabicia, jeśli rozwidlisz go ( kill 25309 & echo $?).

Ponownie nie ma sensu ustawiać modułu obsługi dla SIGKILL, ponieważ nie będzie on działać. 3 Jeśli ja kill -9 25309proces się zakończy. Ale to wciąż sygnał; jądro ma informację o tym, kto wysłał sygnał , jaki to jest sygnał itp.


1. Jeśli nie przeglądałeś listy możliwych sygnałów , patrz kill -l.

2. Kolejny wyjątek, jak wspomina Tim Post poniżej, dotyczy procesów w nieprzerwanym śnie . Nie można ich obudzić, dopóki problem nie zostanie rozwiązany, dlatego WSZYSTKIE sygnały (w tym SIGKILL) zostaną odłożone na czas trwania. Jednak proces nie może celowo stworzyć tej sytuacji.

3. Nie oznacza to, że używanie kill -9jest lepszą rzeczą w praktyce. Mój przykładowy program obsługi jest zły w tym sensie, że do tego nie prowadzi exit(). Prawdziwym celem procedury obsługi SIGTERM jest umożliwienie temu procesowi wyczyszczenia plików tymczasowych, a następnie dobrowolnego wyjścia. Jeśli użyjesz kill -9, nie ma takiej szansy, więc rób to tylko wtedy, gdy część „dobrowolne wyjście” wydaje się nieudana.


Ok, ale z czym zabija ten proces, -9bo to jest prawdziwy problem, w jaki sposób nikt nie chce tego umrzeć! ;)
Kiwy

@Kiwy: Jądro. Przechodzą przez nią IPC, w tym sygnały; jądro implementuje domyślne akcje.
goldilocks

12
Warto wspomnieć, że tryb uśpienia dysku (D) wyprzedza wszystkie sygnały, gdy proces jest w tym stanie. Dlatego próba ustalenia, czy kill -9pewne procesy związane z operacjami we / wy nie będą działać, przynajmniej nie od razu.
Tim Post

7
Dodam, że ponieważ kill -9nie można go złapać, proces odbierający go nie może wykonać żadnego czyszczenia (np. Usunięcie plików tymczasowych, zwolnienie pamięci współdzielonej itp.) Przed jego wyjściem. Dlatego używaj kill -9(aka kill -kill) tylko w ostateczności. Zacznij od a kill -hupi / lub kill -termnajpierw, a następnie użyj kill -killjako ostatni cios.
JRFerguson

„Proces już nie istnieje - chyba że skończy jako zombie, w którym to przypadku nadal jest wymieniony w tabeli procesów jądra z pewnymi informacjami”, w rzeczywistości wszystkie procesy przechodzą w stan zombie po ich śmierci, a zombie zniknie, gdy rodzic czeka na dziecko, zwykle dzieje się to zbyt szybko, abyś mógł to zobaczyć
sprytne

3

Każdy proces działa przez zaplanowany czas, a następnie jest przerywany przez sprzętowy zegar, aby dać rdzeń procesora do innych zadań. Dlatego możliwe jest posiadanie znacznie większej liczby procesów niż rdzeni procesora, a nawet uruchomienie całego systemu operacyjnego z dużą ilością procesów na jednym rdzeniu procesora.

Po przerwaniu procesu formant powraca do kodu jądra. Kod ten może następnie podjąć decyzję o nie wznawianiu wykonania przerwanego procesu, bez jakiejkolwiek współpracy ze strony procesu. kill -9 może zakończyć się wykonywaniem w dowolnej linii twojego programu.


0

Oto wyidealizowany opis działania zabijania procesu. W praktyce każdy wariant Uniksa będzie miał wiele dodatkowych komplikacji i optymalizacji.

Jądro ma strukturę danych dla każdego procesu, która przechowuje informacje o tym, jaką pamięć mapuje, jakie wątki ma i kiedy są zaplanowane, jakie pliki ma otwarte itp. Jeśli jądro zdecyduje się zabić proces, zapisuje to w struktura danych procesu (i być może w strukturze danych każdego z wątków), że proces ma zostać zabity.

Jeśli jeden z wątków procesu jest obecnie zaplanowany na innym procesorze, jądro może wywołać przerwanie na tym drugim procesorze, aby ten wątek przestał działać szybciej.

Gdy program planujący zauważy, że wątek jest w trakcie procesu, który musi zostać zabity, nie będzie go już planował.

Gdy żaden z wątków procesu nie jest już zaplanowany, jądro zaczyna zwalniać zasoby procesu (pamięć, deskryptory plików,…). Za każdym razem, gdy jądro zwalnia zasób, sprawdza, czy jego właściciel nadal ma aktywne zasoby. Gdy proces nie będzie już posiadał zasobu na żywo (mapowanie pamięci, otwarty deskryptor pliku,…), strukturę danych dla samego procesu można zwolnić, a odpowiedni wpis można usunąć z tabeli procesów.

Niektóre zasoby można natychmiast zwolnić (np. Zwolnienie pamięci, która nie jest używana przez operację We / Wy). Inne zasoby muszą poczekać, na przykład dane opisujące operację we / wy nie mogą zostać zwolnione, gdy operacja we / wy jest w toku (podczas działania DMA , używana jest pamięć, do której uzyskuje dostęp, a anulowanie DMA wymaga kontakt z urządzeniem peryferyjnym). Kierowca takiego zasobu zostanie powiadomiony i może spróbować przyspieszyć anulowanie; gdy operacja nie będzie już w toku, sterownik zakończy proces zwalniania tego zasobu.

(Wpis w tabeli procesów jest w rzeczywistości zasobem należącym do procesu nadrzędnego, który zostaje zwolniony, gdy proces umiera, a rodzic potwierdza zdarzenie ).

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.