Komunikacja między procesami - sygnały
ZA signaljest powiadomieniem do procesu wskazującym na wystąpienie zdarzenia. Sygnał jest również nazywanysoftware interrupt i nie można go przewidzieć, aby poznać jego wystąpienie, dlatego jest również nazywany asynchronous event.
Sygnał można określić za pomocą numeru lub nazwy, zwykle nazwy sygnałów rozpoczynają się od SIG. Dostępne sygnały można sprawdzić za pomocą polecenia kill –l (l dla list nazw sygnałów), które jest następujące -
Za każdym razem, gdy zostanie podniesiony sygnał (wygenerowany programowo lub przez system), wykonywana jest akcja domyślna. Co jeśli nie chcesz wykonywać domyślnej akcji, ale chcesz wykonać własne czynności po odebraniu sygnału? Czy jest to możliwe dla wszystkich sygnałów? Tak, można obsłużyć sygnał, ale nie dla wszystkich sygnałów. A jeśli chcesz zignorować sygnały, czy jest to możliwe? Tak, możliwe jest zignorowanie sygnału. Ignorowanie sygnału nie oznacza wykonania domyślnej akcji ani obsługi sygnału. Prawie wszystkie sygnały można zignorować lub obsłużyć. Sygnały, których nie można zignorować ani obsłużyć / przechwycić, to SIGSTOP i SIGKILL.
Podsumowując, działania wykonywane dla sygnałów są następujące -
- Akcja domyślna
- Obsługuj sygnał
- Zignoruj sygnał
Jak omówiono, sygnał można obsłużyć zmieniając wykonanie domyślnej akcji. Obsługę sygnału można przeprowadzić na dwa sposoby, tj. Za pomocą wywołań systemowych signal () i sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
System wywołania signal () wywoła zarejestrowany program obsługi po wygenerowaniu sygnału, jak wspomniano w signum. Obsługą może być SIG_IGN (ignorowanie sygnału), SIG_DFL (ustawianie sygnału z powrotem na domyślny mechanizm) lub zdefiniowana przez użytkownika obsługa sygnału lub adres funkcji.
To wywołanie systemowe w przypadku sukcesu zwraca adres funkcji, która przyjmuje argument będący liczbą całkowitą i nie ma zwracanej wartości. To wywołanie zwraca SIG_ERR w przypadku błędu.
Chociaż za pomocą signal () można wywołać odpowiednią obsługę sygnału zarejestrowaną przez użytkownika, precyzyjne dostrojenie, takie jak maskowanie sygnałów, które powinny być blokowane, modyfikowanie zachowania sygnału i inne funkcje nie są możliwe. Jest to możliwe przy użyciu funkcji systemowej sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
To wywołanie systemowe jest używane do badania lub zmiany działania sygnału. Jeśli akt nie jest zerowy, nowa akcja dla sygnału signum jest instalowana z aktu. Jeśli oldact nie ma wartości null, poprzednia akcja jest zapisywana w oldact.
Struktura sigaction zawiera następujące pola -
Field 1 - Handler wymieniony w sa_handler lub sa_sigaction.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
Procedura obsługi dla sa_handler określa akcję, która ma zostać wykonana na podstawie znaku signum, a SIG_DFL wskazuje akcję domyślną lub SIG_IGN w celu zignorowania sygnału lub wskaźnika do funkcji obsługi sygnału.
Procedura obsługi dla sa_sigaction określa numer sygnału jako pierwszy argument, wskaźnik do struktury siginfo_t jako drugi argument i wskaźnik do kontekstu użytkownika (sprawdź getcontext () lub setcontext () w celu uzyskania dalszych szczegółów) jako trzeci argument.
Struktura siginfo_t zawiera informacje o sygnale, takie jak numer dostarczanego sygnału, wartość sygnału, identyfikator procesu, rzeczywisty identyfikator użytkownika procesu wysyłania itp.
Field 2 - Zestaw sygnałów do zablokowania.
int sa_mask;
Ta zmienna określa maskę sygnałów, które powinny być blokowane podczas wykonywania procedury obsługi sygnału.
Field 3 - Specjalne flagi.
int sa_flags;
To pole określa zestaw flag, które modyfikują zachowanie sygnału.
Field 4 - Przywróć obsługę.
void (*sa_restorer) (void);
To wywołanie systemowe zwraca 0 w przypadku sukcesu i -1 w przypadku niepowodzenia.
Rozważmy kilka przykładowych programów.
Najpierw zacznijmy od przykładowego programu, który generuje wyjątek. W tym programie próbujemy wykonać operację dzielenia przez zero, co powoduje, że system generuje wyjątek.
/* signal_fpe.c */
#include<stdio.h>
int main() {
int result;
int v1, v2;
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
Kroki kompilacji i wykonywania
Floating point exception (core dumped)
Tak więc, gdy próbujemy wykonać operację arytmetyczną, system wygenerował wyjątek zmiennoprzecinkowy ze zrzutem rdzenia, który jest domyślną akcją sygnału.
Teraz zmodyfikujmy kod, aby obsłużyć ten konkretny sygnał za pomocą wywołania systemowego signal ().
/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_dividebyzero(int signum);
int main() {
int result;
int v1, v2;
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
void handler_dividebyzero(int signum) {
if (signum == SIGFPE) {
printf("Received SIGFPE, Divide by Zero Exception\n");
exit (0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Kroki kompilacji i wykonywania
Received SIGFPE, Divide by Zero Exception
Jak wspomniano, sygnały są generowane przez system (po wykonaniu pewnych operacji, takich jak dzielenie przez zero itp.) Lub użytkownik może również generować sygnał programowo. Jeśli chcesz programowo generować sygnał, użyj funkcji biblioteki raise ().
Teraz weźmy inny program, aby zademonstrować obsługę i ignorowanie sygnału.
Załóżmy, że podnieśliśmy sygnał za pomocą metody raise (), co się wtedy dzieje? Po podniesieniu sygnału wykonywanie bieżącego procesu zostaje zatrzymane. Co się dzieje z zatrzymanym procesem? Mogą istnieć dwa scenariusze - Po pierwsze, kontynuuj wykonywanie w razie potrzeby. Po drugie, zakończ proces (poleceniem kill).
Aby kontynuować wykonywanie zatrzymanego procesu, wyślij SIGCONT do tego konkretnego procesu. Możesz także wydać polecenia fg (pierwszy plan) lub bg (tło), aby kontynuować wykonywanie. W tym przypadku polecenia wznowiłyby tylko wykonanie ostatniego procesu. Jeśli zatrzymany zostanie więcej niż jeden proces, wznawiany jest tylko ostatni proces. Jeśli chcesz wznowić poprzednio zatrzymany proces, wznów zadania (używając fg / bg) wraz z numerem zadania.
Poniższy program służy do podniesienia sygnału SIGSTOP za pomocą funkcji raise (). Sygnał SIGSTOP można również wygenerować przez naciśnięcie klawisza CTRL + Z (Control + Z). Po wysłaniu tego sygnału program przestanie działać. Wyślij sygnał (SIGCONT), aby kontynuować wykonywanie.
W poniższym przykładzie wznawiamy zatrzymany proces poleceniem fg.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
Kroki kompilacji i wykonywania
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
Teraz ulepsz poprzedni program, aby kontynuować wykonywanie zatrzymanego procesu, wysyłając SIGCONT z innego terminala.
/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler_sigtstp(int signum);
int main() {
pid_t pid;
printf("Testing SIGSTOP\n");
pid = getpid();
printf("Open Another Terminal and issue following command\n");
printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
raise(SIGSTOP);
printf("Received signal SIGCONT\n");
return 0;
}
Kroki kompilacji i wykonywania
Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out
Received signal SIGCONT
[1]+ Done ./a.out
W innym terminalu
kill -SIGCONT 30379
Do tej pory widzieliśmy program, który obsługuje sygnał generowany przez system. Przyjrzyjmy się teraz sygnałowi wygenerowanemu przez program (za pomocą funkcji raise () lub polecenia kill). Ten program generuje sygnał SIGTSTP (zatrzymanie terminala), którego domyślną akcją jest zatrzymanie wykonywania. Ponieważ jednak obsługujemy teraz sygnał zamiast domyślnej akcji, trafi on do zdefiniowanej procedury obsługi. W tym przypadku drukujemy wiadomość i wychodzimy.
/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
return 0;
}
void handler_sigtstp(int signum) {
if (signum == SIGTSTP) {
printf("Received SIGTSTP\n");
exit(0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Kroki kompilacji i wykonywania
Testing SIGTSTP
Received SIGTSTP
Widzieliśmy przypadki wykonywania domyślnej akcji lub obsługi sygnału. Teraz nadszedł czas, aby zignorować sygnał. Tutaj, w tym przykładowym programie, rejestrujemy sygnał SIGTSTP do zignorowania poprzez SIG_IGN, a następnie zwiększamy sygnał SIGTSTP (zatrzymanie terminala). Gdy generowany jest sygnał SIGTSTP, zostałby zignorowany.
/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
printf("Signal SIGTSTP is ignored\n");
return 0;
}
Kroki kompilacji i wykonywania
Testing SIGTSTP
Signal SIGTSTP is ignored
Do tej pory zauważyliśmy, że mamy jeden program obsługi sygnału do obsługi jednego sygnału. Czy możemy mieć jedną procedurę obsługi do obsługi wielu sygnałów? Odpowiedź brzmi tak. Rozważmy to w programie.
Poniższy program wykonuje następujące czynności -
Step 1 - Rejestruje handler (handleSignals) do przechwytywania lub obsługi sygnałów SIGINT (CTRL + C) lub SIGQUIT (CTRL + \)
Step 2 - Jeśli użytkownik generuje sygnał SIGQUIT (albo przez komendę kill lub sterowanie klawiaturą z CTRL + \), program obsługi po prostu drukuje komunikat jako powrót.
Step 3 - Jeśli użytkownik generuje sygnał SIGINT (za pomocą komendy kill lub sterowania z klawiatury za pomocą CTRL + C), to modyfikuje sygnał, aby wykonać domyślną akcję (z SIG_DFL) od następnego razu.
Step 4 - Jeżeli użytkownik drugi raz wygeneruje sygnał SIGINT, to wykonuje domyślną akcję, jaką jest zakończenie programu.
/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerInterrupt)(int);
void (*sigHandlerQuit)(int);
void (*sigHandlerReturn)(int);
sigHandlerInterrupt = sigHandlerQuit = handleSignals;
sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (1) {
printf("\nTo terminate this program, perform the following: \n");
printf("1. Open another terminal\n");
printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou pressed CTRL+C \n");
printf("Now reverting SIGINT signal to default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou pressed CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Kroki kompilacji i wykonywania
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated
Inny terminal
kill 71
Druga metoda
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
Wiemy, że aby obsłużyć sygnał, mamy dwa wywołania systemowe, tj. Signal () lub sigaction (). Do tej pory widzieliśmy wywołanie systemowe signal (), teraz jest czas na wywołanie systemowe sigaction (). Zmodyfikujmy powyższy program, aby działał za pomocą sigaction () w następujący sposób -
/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerReturn)(int);
struct sigaction mysigaction;
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGINT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGQUIT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (-1) {
printf("\nTo terminate this program, perform either of the following: \n");
printf("1. Open another terminal and issue command: kill %d\n", getpid());
printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou have entered CTRL+C \n");
printf("Now reverting SIGINT signal to perform default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou have entered CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Przyjrzyjmy się procesowi kompilacji i wykonywania. W procesie wykonywania zobaczmy dwukrotnie problem CTRL + C, pozostałe kontrole / sposoby (jak wyżej) możesz wypróbować również dla tego programu.
Kroki kompilacji i wykonywania
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C