Jak sygnały działają wewnętrznie?


31

Ogólnie rzecz biorąc, na kończenie procesów możemy generować sygnały takie jak SIGKILL, SIGTSTPetc.

Ale skąd wiadomo, kto zamówił ten konkretny sygnał, kto wysłał go do określonego procesu i ogólnie, w jaki sposób sygnały wykonują swoje operacje? Jak działają sygnały wewnętrznie?


Pytanie jest nieco trudne do zrozumienia. Przepraszam i mam na myśli brak szacunku. Czy chcesz wiedzieć, kto mógł wykonać polecenie, które zabiło proces, czy chcesz dowiedzieć się więcej o SIGKILL i SIGSTP?
pullsumo,

@ mististister Chcę wiedzieć, kto mógł wykonać polecenie, które zabiło proces i jak?
Varun Chhangani,

Odpowiedzi:


35

Widok 50 000 stóp jest taki:

  1. Sygnał jest generowany albo przez jądro wewnętrznie (na przykład, SIGSEGVgdy uzyskiwany jest nieprawidłowy adres lub SIGQUITgdy naciśniesz Ctrl+ \), lub przez program korzystający z killsyscall (lub kilku powiązanych).

  2. Jeśli jest to jeden z wywołań systemowych, jądro potwierdza, że ​​proces wywołujący ma wystarczające uprawnienia do wysłania sygnału. Jeśli nie, zwracany jest błąd (i sygnał nie występuje).

  3. Jeśli jest to jeden z dwóch specjalnych sygnałów, jądro bezwarunkowo na niego działa, bez udziału procesu docelowego. Dwa specjalne sygnały to SIGKILL i SIGSTOP. Wszystkie poniższe informacje dotyczące domyślnych działań, sygnałów blokujących itp. Są nieistotne dla tych dwóch.

  4. Następnie jądro sprawdza, co zrobić z sygnałem:

    1. Dla każdego procesu istnieje działanie związane z każdym sygnałem. Istnieje kilka wartości domyślnych, a programy mogą ustawiać różne za pomocą sigaction, signalitp. Należą do nich takie rzeczy jak „całkowicie zignoruj”, „zabij proces”, „zabij proces za pomocą zrzutu pamięci”, „zatrzymaj proces”, itp.

    2. Programy mogą również wyłączać dostarczanie sygnałów („blokowane”) na zasadzie sygnał po sygnale. Następnie sygnał pozostaje w oczekiwaniu, aż zostanie odblokowany.

    3. Programy mogą żądać, aby zamiast samego jądra podejmowało jakieś działania, dostarczało sygnał do procesu synchronicznie (z sigwait, i in. Lub signalfd) lub asynchronicznie (przerywając cokolwiek, co robi proces i wywołując określoną funkcję).

Istnieje drugi zestaw sygnałów zwany „sygnałami w czasie rzeczywistym”, które nie mają określonego znaczenia, a także umożliwiają kolejkowanie wielu sygnałów (normalne sygnały ustawiają w kolejce tylko jeden z nich, gdy sygnał jest blokowany). Są one używane w programach wielowątkowych, aby wątki mogły się ze sobą komunikować. Niektóre z nich są używane na przykład w implementacji wątków POSIX glibc. Można ich również używać do komunikacji między różnymi procesami (na przykład można użyć kilku sygnałów w czasie rzeczywistym, aby program fooctl wysłał wiadomość do demona foo).

Aby uzyskać widok nie większy niż 50 000 stóp, spróbuj man 7 signaltakże dokumentację wewnętrzną jądra (lub źródło).


„Dwa specjalne sygnały to SIGKILL i SIGSTOP”, więc czym może być SIGCONT ...
Hauke ​​Laging

@HaukeLaging SIGCONT to sygnał, który cofa SIGSTOP. Dokumentacja nie wymienia tego jako specjalnego ... Więc nie jestem pewien, czy technicznie proces może ustawić ignorowanie, to nie będziesz mógł go wznowić (tylko SIGKILL to).
derobert

22

Implementacja sygnału jest bardzo złożona i jest specyficzna dla jądra. Innymi słowy, różne jądra będą implementowały sygnały w różny sposób. Uproszczone wyjaśnienie jest następujące:

CPU, oparty na specjalnej wartości rejestru, ma adres w pamięci, w którym spodziewa się znaleźć „tablicę deskryptorów przerwań”, która w rzeczywistości jest tablicą wektorową. Istnieje jeden wektor dla każdego możliwego wyjątku, np. Dzielenie przez zero lub pułapka, jak INT 3 (debugowanie). Gdy CPU napotka wyjątek, zapisuje flagi i bieżący wskaźnik instrukcji na stosie, a następnie przeskakuje pod adres określony przez odpowiedni wektor. W Linuksie ten wektor zawsze wskazuje na jądro, gdzie znajduje się moduł obsługi wyjątków. Procesor jest już gotowy i jądro Linuksa przejmuje kontrolę.

Pamiętaj, że możesz również uruchomić wyjątek od oprogramowania. Na przykład użytkownik naciska CTRL- C, a następnie to wywołanie trafia do jądra, które wywołuje własny moduł obsługi wyjątków. Ogólnie rzecz biorąc, istnieją różne sposoby dotarcia do modułu obsługi, ale niezależnie od tego, co dzieje się w tej samej podstawowej sytuacji: kontekst zostaje zapisany na stosie i następuje przejście do modułu obsługi wyjątków jądra.

Program obsługi wyjątków decyduje następnie, który wątek powinien otrzymać sygnał. Jeśli wystąpiło coś takiego jak dzielenie przez zero, to jest łatwe: wątek, który spowodował wyjątek, otrzymuje sygnał, ale w przypadku innych rodzajów sygnałów decyzja może być bardzo złożona, aw niektórych nietypowych przypadkach bardziej lub mniej losowy wątek może dostać sygnał.

Aby wysłać sygnał, to co robi jądro, najpierw ustaw wartość wskazującą typ sygnału SIGHUPlub cokolwiek innego. To tylko liczba całkowita. Każdy proces ma obszar pamięci „sygnał oczekujący”, w którym zapisywana jest ta wartość. Następnie jądro tworzy strukturę danych z informacjami o sygnale. Ta struktura zawiera sygnał „dyspozycji”, który może być domyślny, ignorować lub obsługiwać. Jądro wywołuje następnie własną funkcję do_signal(). Rozpoczyna się kolejna faza.

do_signal()pierwszy decyduje, czy to będzie obsługiwać sygnał. Na przykład, jeśli jest to zabójstwo , to do_signal()po prostu zabija proces, koniec historii. W przeciwnym razie wygląda na usposobienie. Jeśli ustawienie jest domyślne, wówczas do_signal()obsługuje sygnał zgodnie z domyślną polityką zależną od sygnału. Jeśli dyspozycji jest uchwyt, oznacza to, że w programie użytkownika jest funkcja zaprojektowana do obsługi danego sygnału, a wskaźnik do tej funkcji znajdzie się we wspomnianej strukturze danych. W tym przypadku do_signal () wywołuje inną funkcję jądra,handle_signal(), który następnie przechodzi przez proces powrotu do trybu użytkownika i wywołania tej funkcji. Szczegóły tego przekazania są niezwykle złożone. Ten kod w twoim programie jest zwykle automatycznie łączony z twoim programem, gdy używasz funkcji w signal.h.

Poprzez odpowiednie sprawdzenie oczekującej wartości sygnału, jądro może ustalić, czy proces obsługuje wszystkie sygnały, a jeśli nie, podejmie odpowiednie działania, które mogą uśpić proces lub go zabić, lub inne działanie, w zależności od sygnału.


15

Chociaż na to pytanie udzielono odpowiedzi, pozwól mi opublikować szczegółowy przepływ zdarzeń w jądrze Linuksa.
Jest to w całości skopiowane z postów w systemie Linux: Sygnały z systemu Linux - elementy wewnętrzne na blogu „Linux posts” pod adresem sklinuxblog.blogspot.in.

Program Space User C. Program

Zacznijmy od napisania prostego programu C przestrzeni użytkownika sygnału:

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

/* Handler function */
void handler(int sig) {
    printf("Receive signal: %u\n", sig);
};

int main(void) {
    struct sigaction sig_a;

    /* Initialize the signal handler structure */
    sig_a.sa_handler = handler;
    sigemptyset(&sig_a.sa_mask);
    sig_a.sa_flags = 0;

    /* Assign a new handler function to the SIGINT signal */
    sigaction(SIGINT, &sig_a, NULL);

    /* Block and wait until a signal arrives */
    while (1) {
            sigsuspend(&sig_a.sa_mask);
            printf("loop\n");
    }
    return 0;
};

Ten kod przypisuje nową procedurę obsługi sygnału SIGINT. SIGINT można wysłać do uruchomionego procesu za pomocą kombinacji klawiszy Ctrl+ C. Po naciśnięciu przycisku Ctrl+ Cdo zadania wysyłany jest sygnał asynchroniczny SIGINT. Jest to również równoważne z wysłaniem kill -INT <pid>polecenia do innego terminala.

Jeśli zrobisz kill -l(to mała litera L, co oznacza „listę”), poznasz różne sygnały, które można wysłać do uruchomionego procesu.

[root@linux ~]# kill -l
 1) SIGHUP        2) SIGINT        3) SIGQUIT       4) SIGILL        5) SIGTRAP
 6) SIGABRT       7) SIGBUS        8) SIGFPE        9) SIGKILL      10) SIGUSR1
11) SIGSEGV      12) SIGUSR2      13) SIGPIPE      14) SIGALRM      15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD      18) SIGCONT      19) SIGSTOP      20) SIGTSTP
21) SIGTTIN      22) SIGTTOU      23) SIGURG       24) SIGXCPU      25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF      28) SIGWINCH     29) SIGIO        30) SIGPWR
31) SIGSYS       34) SIGRTMIN     35) SIGRTMIN+1   36) SIGRTMIN+2   37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5   40) SIGRTMIN+6   41) SIGRTMIN+7   42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12  47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14  51) SIGRTMAX-13  52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10  55) SIGRTMAX-9   56) SIGRTMAX-8   57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5   60) SIGRTMAX-4   61) SIGRTMAX-3   62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

Do wysyłania określonych sygnałów można także użyć następującej kombinacji klawiszy:

  • Ctrl+ C- wysyła SIGINT, która domyślna akcja kończy działanie aplikacji.
  • Ctrl+ \  - wysyła SIGQUIT, którego domyślnym działaniem jest zakończenie rdzenia zrzutu aplikacji.
  • Ctrl+ Z- wysyła SIGSTOP, który zawiesza program.

Jeśli skompilujesz i uruchomisz powyższy program C, otrzymasz następujące dane wyjściowe:

[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop

Nawet z Ctrl+ Club kill -2 <pid>proces się nie zakończy. Zamiast tego wykona procedurę obsługi sygnału i powróci.

Jak sygnał jest wysyłany do procesu

Jeśli zobaczymy wewnętrzne sygnały wysyłane do procesu i ustawimy Jprobe z funkcją dump_stack na __send_signalfunkcję, zobaczymy następujący ślad wywołania:

May  5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May  5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May  5 16:18:37 linux kernel: complete_signal+0x205/0x250
May  5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May  5 16:18:37 linux kernel: send_signal+0x3e/0x80
May  5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May  5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May  5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May  5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May  5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May  5 16:18:37 linux kernel:  ? ftrace_ops_list_func+0x106/0x120
May  5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May  5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May  5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May  5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May  5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May  5 16:18:37 linux kernel:  kthread+0xcf/0xe0
May  5 16:18:37 linux kernel:  kthread_create_on_node+0x140/0x140
May  5 16:18:37 linux kernel:  ret_from_fork+0x7c/0xb0
May  5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140

Tak więc główna funkcja wymaga wysłania sygnału:

First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state()  -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.

Teraz wszystko jest skonfigurowane i niezbędne zmiany task_structw procesie.

Obsługa sygnału

Sygnał jest sprawdzany / obsługiwany przez proces, gdy powraca z wywołania systemowego lub gdy nastąpi powrót z przerwania. Powrót z wywołania systemowego jest obecny w pliku entry_64.S.

Wywoływana jest funkcja int_signal, z entry_64.Sktórej wywołuje się funkcję do_notify_resume().

Sprawdźmy funkcję do_notify_resume(). Ta funkcja sprawdza, czy mamy TIF_SIGPENDINGustawioną flagę w task_struct:

 /* deal with pending signal delivery */
 if (thread_info_flags & _TIF_SIGPENDING)
  do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;

Połączenia i sygnały systemowe

„Powolne” wywołania systemowe, np. Blokujące odczyt / zapis, wprowadzające procesy w stan oczekiwania: TASK_INTERRUPTIBLElub TASK_UNINTERRUPTIBLE.

Zadanie w stanie TASK_INTERRUPTIBLEzostanie zmienione na TASK_RUNNINGstan za pomocą sygnału. TASK_RUNNINGoznacza, że ​​można zaplanować proces.

Jeśli zostanie wykonany, jego procedura obsługi sygnału zostanie uruchomiona przed zakończeniem „powolnego” wywołania systemowego. syscallNie zakończy się domyślnie.

Jeśli SA_RESTARTustawiona syscalljest flaga, jest restartowana po zakończeniu procedury obsługi sygnału.

Referencje


Dziękujemy za wysiłek włożenia wkładu w tę witrynę, ale (1) jeśli zamierzasz kopiować materiały z innej witryny (słowo w słowo, list za literę, w tym błędy gramatyczne i interpunkcyjne), powinieneś powiedzieć, że robisz o wiele wyraźniej. Wymienienie źródła jako „źródła”, choć jest konieczne, nie jest wystarczające. O ile nie jesteś autorem bloga (K_K = sk?), W takim przypadku nie musisz linkować do niego - ale jeśli tak, musisz ujawnić (tzn. Powiedzieć), że jest twój. … (Ciąg dalszy)
G-Man mówi „Przywróć Monikę”

(Ciąg dalszy)… (2) Twoje źródło (blog, z którego skopiowałeś) nie jest zbyt dobre. Minęły cztery lata, odkąd pytanie zostało zadane; nie możesz znaleźć lepszego odniesienia do kopiowania? (Jeśli jesteś oryginalnym autorem, przepraszam.) Oprócz wyżej wymienionych błędów gramatycznych i interpunkcyjnych (i ogólnie niechlujnego sformułowania i złego formatowania), jest źle. (2a) Ctrl + Z wysyła SIGTSTP, a nie SIGSTOP. (SIGTSTP, podobnie jak SIGTERM, może zostać złapany; SIGSTOP, podobnie jak SIGKILL, nie może.)… (Ciąg dalszy)
G-Man mówi „Przywróć Monikę”

(Ciąg dalszy)… (2b) Powłoka nie wysyła sygnału Ctrl + C. Powłoka nie odgrywa żadnej roli w wysyłaniu sygnałów (z wyjątkiem sytuacji, gdy użytkownik korzysta z killpolecenia, które jest wbudowane w powłokę). (2c) Chociaż średniki po zamknięciu }funkcji nie są, ściśle mówiąc, błędami, są niepotrzebne i wysoce niekonwencjonalne. (3) Nawet gdyby wszystko było prawidłowe, nie byłaby to bardzo dobra odpowiedź na pytanie. (3a) Pytanie, choć nieco niejasne, wydaje się koncentrować na tym, w jaki sposób aktorzy (użytkownicy i proces) inicjują (tj. Wysyłają ) sygnały. … (Ciąg dalszy)
G-Man mówi „Przywróć Monikę”

(Ciąg dalszy)… Wydaje się, że odpowiedź skupia się na sygnałach generowanych przez jądro (w szczególności na sygnałach generowanych z klawiatury) i na tym, jak proces odbiorcy reaguje na sygnały. (3b) Pytanie wydaje się być na poziomie „Ktoś zabił mój proces - kto to zrobił i jak?” Odpowiedź omawia API obsługi sygnałów, procedury jądra, debugowanie jądra (Jprobe?), Ślady stosu jądra i struktury danych jądra. IMO, to niewłaściwie niski poziom - zwłaszcza, że ​​nie zawiera żadnych odniesień, w których czytelnik mógłby dowiedzieć się więcej o tych wewnętrznych działaniach.
G-Man mówi „Przywróć Monikę”

1
To jest mój własny blog ... moje własne ślady ... to jest to, czego chcę .. każdy powinien znać tak szczegółowy przepływ .. rozmowa w powietrzu nie ma sensu .. nawet jeśli po naruszeniu wytycznych dla społeczności proszę o usunięcie mojej odpowiedzi poprzez właściwe kanał .. to jest wewnętrzna odpowiedź jądra, a nie elementy gramatyczne.
K_K
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.