Jak używać programów obsługi sygnałów w języku C?

How Use Signal Handlers C Language



W tym artykule pokażemy, jak używać programów obsługi sygnałów w Linuksie przy użyciu języka C. Ale najpierw omówimy, czym jest sygnał, w jaki sposób wygeneruje on niektóre typowe sygnały, które możesz wykorzystać w swoim programie, a następnie przyjrzymy się, jak różne sygnały mogą być obsługiwane przez program podczas wykonywania programu. A więc zacznijmy.

Sygnał

Sygnał to zdarzenie, które jest generowane w celu powiadomienia procesu lub wątku o nadejściu ważnej sytuacji. Gdy proces lub wątek otrzyma sygnał, proces lub wątek zatrzyma swoje działanie i podejmie jakieś działanie. Signal może być przydatny do komunikacji między procesami.







Sygnały standardowe

Sygnały są zdefiniowane w pliku nagłówkowym sygnał.h jako makrostała. Nazwa sygnału zaczyna się od znaku SIG, po którym następuje krótki opis sygnału. Tak więc każdy sygnał ma unikalną wartość liczbową. Twój program powinien zawsze używać nazw sygnałów, a nie ich numerów. Powodem jest to, że numer sygnału może się różnić w zależności od systemu, ale znaczenie nazw będzie standardowe.



Makro NSIG to całkowita liczba zdefiniowanych sygnałów. Wartość NSIG jest o jeden większy niż całkowita liczba zdefiniowanych sygnałów (wszystkie numery sygnałów są przydzielane kolejno).



Poniżej znajdują się standardowe sygnały:





Nazwa sygnału Opis
ZGŁOSZENIE Zakończ proces. Sygnał SIGHUP służy do zgłaszania rozłączenia terminala użytkownika, prawdopodobnie z powodu utraty lub rozłączenia połączenia zdalnego.
PODPIS Przerwij proces. Gdy użytkownik wpisze znak INTR (zwykle Ctrl + C) wysyłany jest sygnał SIGINT.
WYJDŹ Zakończ proces. Gdy użytkownik wpisze znak QUIT (zwykle Ctrl + ) wysyłany jest sygnał SIGQUIT.
FOKA Nielegalna instrukcja. Gdy podjęta zostanie próba wykonania nieużytecznej lub uprzywilejowanej instrukcji, generowany jest sygnał SIGILL. Ponadto SIGILL może zostać wygenerowany, gdy stos się przepełni lub gdy system ma problem z uruchomieniem obsługi sygnału.
SIGTRAP Pułapka śladowa. Instrukcja punktu przerwania i inna instrukcja pułapki wygenerują sygnał SIGTRAP. Debuger używa tego sygnału.
SIGABRT Anulować. Sygnał SIGABRT jest generowany po wywołaniu funkcji abort(). Ten sygnał wskazuje na błąd wykryty przez sam program i zgłoszony przez wywołanie funkcji abort().
SIGFPE Wyjątek zmiennoprzecinkowy. W przypadku wystąpienia krytycznego błędu arytmetycznego generowany jest sygnał SIGFPE.
SIGUSR1 i SIGUSR2 Sygnały SIGUSR1 i SIGUSR2 mogą być używane w dowolny sposób. Przydatne jest napisanie dla nich obsługi sygnału w programie, który odbiera sygnał do prostej komunikacji międzyprocesowej.

Domyślne działanie sygnałów

Każdy sygnał ma domyślną akcję, jedną z następujących:

Semestr: Proces się zakończy.
Rdzeń: Proces zakończy się i utworzy plik zrzutu pamięci.
Podpisz: Proces zignoruje sygnał.
Zatrzymać: Proces się zatrzyma.
Konto: Proces będzie kontynuowany od zatrzymania.



Domyślną akcję można zmienić za pomocą funkcji obsługi. Nie można zmienić domyślnej akcji niektórych sygnałów. SIGKILL oraz SIGABRT domyślne działanie sygnału nie może być zmienione ani zignorowane.

Obsługa sygnału

Jeśli proces odbierze sygnał, proces ma wybór działania dla tego rodzaju sygnału. Proces może zignorować sygnał, określić funkcję obsługi lub zaakceptować domyślną akcję dla tego rodzaju sygnału.

  • Jeśli określona akcja dla sygnału zostanie zignorowana, sygnał jest natychmiast odrzucany.
  • Program może zarejestrować funkcję obsługi za pomocą funkcji takich jak sygnał lub sygacja . Nazywa się to handler łapie sygnał.
  • Jeśli sygnał nie został ani obsłużony, ani zignorowany, ma miejsce jego domyślna akcja.

Możemy obsłużyć sygnał za pomocą sygnał lub sygacja funkcjonować. Tutaj widzimy jak najprostsze sygnał() funkcja służy do obsługi sygnałów.

intsygnał() (intznak, próżnia (*funkcjonować)(int))

ten sygnał() zadzwonię do funkcjonować funkcja, jeśli proces otrzyma sygnał znak . ten sygnał() zwraca wskaźnik do funkcji funkcjonować jeśli się powiedzie lub zwraca błąd do errno i -1 w przeciwnym razie.

ten funkcjonować wskaźnik może mieć trzy wartości:

  1. SIG_DFL : Jest to wskaźnik do domyślnej funkcji systemu SIG_DFL () , zadeklarowany w h plik nagłówkowy. Służy do podejmowania domyślnej akcji sygnału.
  2. SIG_IGN : Jest to wskaźnik do funkcji ignorowania systemu SIG_IGN () , zadeklarowany w h plik nagłówkowy.
  3. Wskaźnik funkcji obsługi zdefiniowanej przez użytkownika : Typ funkcji obsługi zdefiniowanej przez użytkownika to nieważne(*)(wew.) , oznacza, że ​​zwracany typ to void i jeden argument typu int.

Podstawowy przykład obsługi sygnału

#włączać
#włączać
#włączać
próżniasig_handler(intznak){

//Typ zwrotu funkcji obsługi powinien być nieważny
printf (' Funkcja obsługi wewnętrznej ');
}

intGłówny(){
sygnał(PODPIS,sig_handler); // Zarejestruj obsługę sygnału
dla(inti=1;;i++){ //Nieskończona pętla
printf ('%d: Wewnątrz głównej funkcji ',i);
spać(1); // Opóźnienie o 1 sekundę
}
powrót 0;
}

Na zrzucie ekranu wyjścia Example1.c widać, że w głównej funkcji wykonywana jest nieskończona pętla. Gdy użytkownik wciśnie Ctrl+C, wykonywanie funkcji głównej zostaje zatrzymane i wywoływana jest funkcja obsługi sygnału. Po zakończeniu funkcji obsługi wznowiono wykonywanie funkcji głównej. Gdy użytkownik wpisze Ctrl+, proces zostanie zakończony.

Przykład ignorowania sygnałów

#włączać
#włączać
#włączać
intGłówny(){
sygnał(PODPIS,SIG_IGN); // Zarejestruj program obsługi sygnału do ignorowania sygnału

dla(inti=1;;i++){ //Nieskończona pętla
printf ('%d: Wewnątrz głównej funkcji ',i);
spać(1); // Opóźnienie o 1 sekundę
}
powrót 0;
}

Tutaj funkcja obsługi jest zarejestrowana do SIG_IGN () funkcja ignorowania działania sygnału. Tak więc, gdy użytkownik wcisnął Ctrl + C, PODPIS sygnał jest generowany, ale akcja jest ignorowana.

Przykład ponownej rejestracji sygnału

#włączać
#włączać
#włączać

próżniasig_handler(intznak){
printf (' Funkcja obsługi wewnętrznej ');
sygnał(PODPIS,SIG_DFL); // Ponownie zarejestruj procedurę obsługi sygnału dla domyślnej akcji
}

intGłówny(){
sygnał(PODPIS,sig_handler); // Zarejestruj obsługę sygnału
dla(inti=1;;i++){ //Nieskończona pętla
printf ('%d: Wewnątrz głównej funkcji ',i);
spać(1); // Opóźnienie o 1 sekundę
}
powrót 0;
}

Na zrzucie ekranu danych wyjściowych Example3.c widzimy, że gdy użytkownik po raz pierwszy wcisnął Ctrl+C, wywołała funkcję obsługi. W funkcji obsługi, obsługa sygnału ponownie zarejestruje się w SIG_DFL dla domyślnego działania sygnału. Gdy użytkownik wciśnie Ctrl+C po raz drugi, proces zostaje przerwany, co jest domyślną akcją PODPIS sygnał.

Wysyłanie sygnałów:

Proces może również jawnie wysyłać sygnały do ​​siebie lub do innego procesu. Do wysyłania sygnałów można użyć funkcji raise() i kill(). Obie funkcje są zadeklarowane w pliku nagłówkowym signal.h.

int wznosić (intznak)

Funkcja raise() używana do wysyłania sygnału znak do procesu wywołującego (samego). Zwraca zero, jeśli się powiedzie, i wartość niezerową, jeśli się nie powiedzie.

intzabić(pid_t pid, intznak)

Funkcja zabijania używana do wysyłania sygnału znak do procesu lub grupy procesów określonej przez pid .

Przykład obsługi sygnału SIGUSR1

#włączać
#włączać

próżniasig_handler(intznak){
printf ('Funkcja obsługi wewnętrznej ');
}

intGłówny(){
sygnał(SIGUSR1,sig_handler); // Zarejestruj obsługę sygnału
printf ('Wewnątrz główna funkcja ');
wznosić (SIGUSR1);
printf ('Wewnątrz główna funkcja ');
powrót 0;
}

Tutaj proces wysyła do siebie sygnał SIGUSR1 za pomocą funkcji raise().

Podbicie za pomocą przykładowego programu Kill

#włączać
#włączać
#włączać
próżniasig_handler(intznak){
printf ('Funkcja obsługi wewnętrznej ');
}

intGłówny(){
pid_t pid;
sygnał(SIGUSR1,sig_handler); // Zarejestruj obsługę sygnału
printf ('Wewnątrz główna funkcja ');
pid=getpid(); //Identyfikator procesu samego siebie
zabić(pid,SIGUSR1); // Wyślij SIGUSR1 do siebie
printf ('Wewnątrz główna funkcja ');
powrót 0;
}

Tutaj proces wyślij SIGUSR1 sygnał do siebie za pomocą zabić() funkcjonować. getpid() służy do uzyskania samego identyfikatora procesu.

W następnym przykładzie zobaczymy, jak komunikują się procesy rodzica i dziecka (komunikacja między procesami) za pomocą zabić() i funkcja sygnału.

Komunikacja rodzic-dziecko za pomocą sygnałów

#włączać
#włączać
#włączać
#włączać
próżniasig_handler_parent(intznak){
printf ('Rodzic: otrzymał sygnał odpowiedzi od dziecka ');
}

próżniasig_handler_child(intznak){
printf („Dziecko: otrzymało sygnał od rodzica” ');
spać(1);
zabić(getppid(),SIGUSR1);
}

intGłówny(){
pid_t pid;
Jeśli((pid=widelec())<0){
printf („Widelec nie powiódł się” ');
Wyjście (1);
}
/* Proces potomny */
w przeciwnym razie Jeśli(pid==0){
sygnał(SIGUSR1,sig_handler_child); // Zarejestruj obsługę sygnału
printf („Dziecko: czekam na sygnał” ');
pauza();
}
/* Proces nadrzędny */
w przeciwnym razie{
sygnał(SIGUSR1,sig_handler_parent); // Zarejestruj obsługę sygnału
spać(1);
printf („Rodzic: wysyła sygnał do Dziecka” ');
zabić(pid,SIGUSR1);
printf ('Rodzic: czekam na odpowiedź ');
pauza();
}
powrót 0;
}

Tutaj, widelec() funkcja tworzy proces podrzędny i zwraca zero do procesu podrzędnego i identyfikator procesu podrzędnego do procesu nadrzędnego. Sprawdzono więc pid, aby zdecydować o procesie nadrzędnym i podrzędnym. W procesie rodzica jest on uśpiony przez 1 sekundę, aby proces potomny mógł zarejestrować funkcję obsługi sygnału i czekać na sygnał od rodzica. Po 1 sekundzie procesu nadrzędnego wyślij SIGUSR1 sygnał do procesu potomnego i czekaj na sygnał odpowiedzi od dziecka. W procesie potomnym najpierw czeka na sygnał od rodzica, a po otrzymaniu sygnału wywoływana jest funkcja obsługi. Z funkcji obsługi proces potomny wysyła kolejny SIGUSR1 sygnał do rodzica. Tutaj getppid() Funkcja służy do uzyskania identyfikatora procesu nadrzędnego.

Wniosek

Signal w Linuksie to duży temat. W tym artykule zobaczyliśmy, jak radzić sobie z sygnałem od podstaw, a także dowiedzieć się, jak generuje sygnał, jak proces może wysyłać sygnał do siebie i innego procesu, jak sygnał może być wykorzystany do komunikacji między procesami.