Prozessübergreifende Kommunikation - Signale

EIN signalist eine Benachrichtigung an einen Prozess, die das Auftreten eines Ereignisses angibt. Signal wird auch genanntsoftware interrupt und es ist nicht vorhersehbar, sein Auftreten zu kennen, daher wird es auch als bezeichnet asynchronous event.

Das Signal kann mit einer Nummer oder einem Namen angegeben werden. Normalerweise beginnen die Signalnamen mit SIG. Die verfügbaren Signale können mit dem Befehl kill –l (l für das Auflisten von Signalnamen) überprüft werden.

Immer wenn ein Signal ausgelöst wird (entweder programmgesteuert oder vom System generiert), wird eine Standardaktion ausgeführt. Was ist, wenn Sie die Standardaktion nicht ausführen möchten, aber beim Empfang des Signals Ihre eigenen Aktionen ausführen möchten? Ist das für alle Signale möglich? Ja, es ist möglich, das Signal zu verarbeiten, jedoch nicht für alle Signale. Was ist möglich, wenn Sie die Signale ignorieren möchten? Ja, das Signal kann ignoriert werden. Das Ignorieren des Signals bedeutet weder das Ausführen der Standardaktion noch das Behandeln des Signals. Es ist möglich, fast alle Signale zu ignorieren oder zu verarbeiten. Die Signale, die weder ignoriert noch behandelt / abgefangen werden können, sind SIGSTOP und SIGKILL.

Zusammenfassend sind die für die Signale ausgeführten Aktionen wie folgt:

  • Standardaktion
  • Behandle das Signal
  • Ignoriere das Signal

Wie bereits erwähnt, kann das Signal so verarbeitet werden, dass die Ausführung der Standardaktion geändert wird. Die Signalverarbeitung kann auf eine der beiden Arten erfolgen, dh über Systemaufrufe, signal () und sigaction ().

#include <signal.h>

typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);

Das Systemaufrufsignal () würde den registrierten Handler bei der Erzeugung des Signals aufrufen, wie in signum erwähnt. Der Handler kann entweder einer der SIG_IGN (Ignorieren des Signals), SIG_DFL (Zurücksetzen des Signals auf den Standardmechanismus) oder ein benutzerdefinierter Signalhandler oder eine Funktionsadresse sein.

Dieser Systemaufruf bei Erfolg gibt die Adresse einer Funktion zurück, die ein ganzzahliges Argument akzeptiert und keinen Rückgabewert hat. Dieser Aufruf gibt im Fehlerfall SIG_ERR zurück.

Obwohl mit signal () der vom Benutzer registrierte jeweilige Signalhandler aufgerufen werden kann, ist eine Feinabstimmung wie das Maskieren der zu blockierenden Signale, das Ändern des Verhaltens eines Signals und andere Funktionen nicht möglich. Dies ist mit dem Systemaufruf sigaction () möglich.

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

Dieser Systemaufruf wird verwendet, um eine Signalaktion zu untersuchen oder zu ändern. Wenn die Handlung nicht null ist, wird die neue Aktion für das Signalsignal von der Handlung installiert. Wenn oldact nicht null ist, wird die vorherige Aktion in oldact gespeichert.

Die Sigaktionsstruktur enthält die folgenden Felder:

Field 1 - Handler, der entweder in sa_handler oder sa_sigaction erwähnt wird.

void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);

Der Handler für sa_handler gibt die auszuführende Aktion basierend auf dem Signal an und wobei SIG_DFL die Standardaktion angibt oder SIG_IGN das Signal oder den Zeiger auf eine Signalbehandlungsfunktion ignoriert.

Der Handler für sa_sigaction gibt die Signalnummer als erstes Argument an, den Zeiger auf die Struktur siginfo_t als zweites Argument und den Zeiger auf den Benutzerkontext (überprüfen Sie getcontext () oder setcontext () für weitere Details) als drittes Argument.

Die Struktur siginfo_t enthält Signalinformationen wie die zu liefernde Signalnummer, den Signalwert, die Prozess-ID, die tatsächliche Benutzer-ID des Sendeprozesses usw.

Field 2 - Satz von zu blockierenden Signalen.

int sa_mask;

Diese Variable gibt die Maske der Signale an, die während der Ausführung des Signalhandlers blockiert werden sollen.

Field 3 - Spezielle Flaggen.

int sa_flags;

Dieses Feld gibt eine Reihe von Flags an, die das Verhalten des Signals ändern.

Field 4 - Handler wiederherstellen.

void (*sa_restorer) (void);

Dieser Systemaufruf gibt bei Erfolg 0 und im Fehlerfall -1 zurück.

Betrachten wir einige Beispielprogramme.

Beginnen wir zunächst mit einem Beispielprogramm, das eine Ausnahme generiert. In diesem Programm versuchen wir, eine Operation zum Teilen durch Null durchzuführen, wodurch das System eine Ausnahme generiert.

/* 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;
}

Kompilierungs- und Ausführungsschritte

Floating point exception (core dumped)

Wenn wir also versuchen, eine arithmetische Operation auszuführen, hat das System eine Gleitkomma-Ausnahme mit Core-Dump generiert, die die Standardaktion des Signals ist.

Lassen Sie uns nun den Code ändern, um dieses bestimmte Signal mithilfe des Systemaufrufs signal () zu verarbeiten.

/* 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;
}

Kompilierungs- und Ausführungsschritte

Received SIGFPE, Divide by Zero Exception

Wie erläutert, werden Signale vom System erzeugt (bei Ausführung bestimmter Operationen wie Teilen durch Null usw.) oder der Benutzer kann das Signal auch programmgesteuert erzeugen. Wenn Sie ein Signal programmgesteuert erzeugen möchten, verwenden Sie die Bibliotheksfunktion Raise ().

Nehmen wir nun ein anderes Programm, um die Handhabung und das Ignorieren des Signals zu demonstrieren.

Angenommen, wir haben mit raise () ein Signal ausgelöst. Was passiert dann? Nach dem Auslösen des Signals wird die Ausführung des aktuellen Prozesses gestoppt. Was passiert dann mit dem gestoppten Prozess? Es kann zwei Szenarien geben: Erstens, setzen Sie die Ausführung bei Bedarf fort. Beenden Sie zweitens den Prozess (mit dem Befehl kill).

Senden Sie SIGCONT an diesen bestimmten Prozess, um die Ausführung des gestoppten Prozesses fortzusetzen. Sie können auch die Befehle fg (Vordergrund) oder bg (Hintergrund) ausgeben, um die Ausführung fortzusetzen. Hier würden die Befehle nur die Ausführung des letzten Prozesses neu starten. Wenn mehr als ein Prozess gestoppt wird, wird nur der letzte Prozess fortgesetzt. Wenn Sie den zuvor gestoppten Prozess fortsetzen möchten, setzen Sie die Jobs (mit fg / bg) zusammen mit der Jobnummer fort.

Das folgende Programm wird verwendet, um das Signal SIGSTOP mit der Funktion raise () zu erhöhen. Das Signal SIGSTOP kann auch durch Drücken der STRG + Z-Taste (Strg + Z) erzeugt werden. Nach dem Ausgeben dieses Signals wird die Ausführung des Programms beendet. Senden Sie das Signal (SIGCONT), um die Ausführung fortzusetzen.

Im folgenden Beispiel setzen wir den gestoppten Prozess mit dem Befehl fg fort.

/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

int main() {
   printf("Testing SIGSTOP\n");
   raise(SIGSTOP);
   return 0;
}

Kompilierungs- und Ausführungsschritte

Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out

Erweitern Sie nun das vorherige Programm, um die Ausführung des gestoppten Prozesses fortzusetzen, indem Sie SIGCONT von einem anderen Terminal aus ausgeben.

/* 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;
}

Kompilierungs- und Ausführungsschritte

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

In einem anderen Terminal

kill -SIGCONT 30379

Bisher haben wir das Programm gesehen, das das vom System erzeugte Signal verarbeitet. Lassen Sie uns nun das Signal sehen, das durch das Programm (mit der Funktion raise () oder mit dem Befehl kill) erzeugt wurde. Dieses Programm erzeugt das Signal SIGTSTP (Terminal Stop), dessen Standardaktion darin besteht, die Ausführung zu stoppen. Da wir das Signal jetzt anstelle der Standardaktion verarbeiten, wird es an den definierten Handler gesendet. In diesem Fall drucken wir nur die Nachricht und beenden sie.

/* 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;
}

Kompilierungs- und Ausführungsschritte

Testing SIGTSTP
Received SIGTSTP

Wir haben die Fälle gesehen, in denen Standardaktionen ausgeführt oder das Signal verarbeitet wurden. Jetzt ist es Zeit, das Signal zu ignorieren. Hier in diesem Beispielprogramm registrieren wir das Signal SIGTSTP, um es über SIG_IGN zu ignorieren, und erhöhen dann das Signal SIGTSTP (Terminal Stop). Wenn das Signal SIGTSTP erzeugt wird, wird dies ignoriert.

/* 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;
}

Kompilierungs- und Ausführungsschritte

Testing SIGTSTP
Signal SIGTSTP is ignored

Bisher haben wir beobachtet, dass wir einen Signalhandler haben, der ein Signal verarbeitet. Können wir einen einzigen Handler haben, der mehrere Signale verarbeitet? Die Antwort ist ja. Betrachten wir dies mit einem Programm.

Das folgende Programm führt Folgendes aus:

Step 1 - Registriert einen Handler (handleSignals), um die Signale SIGINT (STRG + C) oder SIGQUIT (STRG + \) abzufangen oder zu verarbeiten.

Step 2 - Wenn der Benutzer das Signal SIGQUIT generiert (entweder durch Kill-Befehl oder Tastatursteuerung mit STRG + \), druckt der Handler die Nachricht einfach als Rückgabe.

Step 3 - Wenn der Benutzer zum ersten Mal das Signal SIGINT (entweder durch Kill-Befehl oder Tastatursteuerung mit STRG + C) generiert, ändert er das Signal, um ab dem nächsten Mal eine Standardaktion (mit SIG_DFL) auszuführen.

Step 4 - Wenn der Benutzer das zweite Mal das Signal SIGINT generiert, führt er eine Standardaktion aus, bei der das Programm beendet wird.

/* 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;
}

Kompilierungs- und Ausführungsschritte

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

Ein weiteres Terminal

kill 71

Zweite Methode

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

Wir wissen, dass wir zur Verarbeitung eines Signals zwei Systemaufrufe haben, entweder signal () oder sigaction (). Bis jetzt haben wir mit signal () Systemaufruf gesehen, jetzt ist es Zeit für sigaction () Systemaufruf. Lassen Sie uns das obige Programm so ändern, dass es mit sigaction () wie folgt ausgeführt wird:

/* 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;
}

Lassen Sie uns den Kompilierungs- und Ausführungsprozess sehen. Lassen Sie uns während des Ausführungsprozesses zweimal STRG + C ausgeben. Die verbleibenden Überprüfungen / Methoden (wie oben) können Sie auch für dieses Programm versuchen.

Kompilierungs- und Ausführungsschritte

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