GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Grundlagen zu Linux-Signalen – Teil I

Was ist ein Signal? Signale sind Software-Interrupts.

Ein robustes Programm muss mit Signalen umgehen können. Dies liegt daran, dass Signale eine Möglichkeit sind, asynchrone Ereignisse an die Anwendung zu übermitteln.

Ein Benutzer, der Strg+C drückt, ein Prozess, der ein Signal sendet, um einen anderen Prozess zu beenden usw., sind alles solche Fälle, in denen ein Prozess eine Signalverarbeitung durchführen muss.

Linux-Signale

Unter Linux hat jedes Signal einen Namen, der mit den Zeichen SIG beginnt. Zum Beispiel:

  • Ein SIGINT-Signal, das generiert wird, wenn ein Benutzer Strg+C drückt. Dies ist der Weg, um Programme vom Terminal aus zu beenden.
  • Ein SIGALRM  wird generiert, wenn der von der Alarmfunktion eingestellte Timer ertönt.
  • Ein SIGABRT-Signal wird generiert, wenn ein Prozess die Abbruchfunktion aufruft.
  • usw.

Wenn das Signal auftritt, muss der Prozess dem Kernel mitteilen, was damit zu tun ist. Es kann drei Optionen geben, durch die ein Signal entsorgt werden kann:

  1. Das Signal kann ignoriert werden. Mit Ignorieren meinen wir, dass nichts getan wird, wenn ein Signal auftritt. Die meisten Signale können ignoriert werden, aber Signale, die durch Hardware-Ausnahmen wie Teilen durch Null erzeugt werden, können seltsame Folgen haben, wenn sie ignoriert werden. Außerdem können einige Signale wie SIGKILL und SIGSTOP nicht ignoriert werden.
  2. Das Signal kann abgefangen werden. Wenn diese Option gewählt wird, registriert der Prozess eine Funktion beim Kernel. Diese Funktion wird vom Kernel aufgerufen, wenn dieses Signal auftritt. Wenn das Signal für den Prozess nicht fatal ist, dann kann der Prozess in dieser Funktion das Signal richtig handhaben oder sich andernfalls für eine ordnungsgemäße Beendigung entscheiden.
  3. Lassen Sie die Standardaktion gelten. Jedes Signal hat eine Standardaktion. Dies könnte Prozess beenden, ignorieren usw. sein.

Wie bereits erwähnt, können die beiden Signale SIGKILL und SIGSTOP nicht ignoriert werden. Dies liegt daran, dass diese beiden Signale dem Root-Benutzer oder dem Kernel eine Möglichkeit bieten, jeden Prozess in jeder Situation zu beenden oder zu stoppen. Die Standardaktion dieser Signale besteht darin, den Prozess zu beenden. Diese Signale können weder abgefangen noch ignoriert werden.

Was passiert beim Programmstart?

Es hängt alles von dem Prozess ab, der exec aufruft. Wenn der Prozess gestartet wird, ist der Status aller Signale entweder Ignorieren oder Standard. Es ist die letztere Option, die eher eintritt, es sei denn, der Prozess, der exec aufruft, ignoriert die Signale.

Es ist die Eigenschaft von Exec-Funktionen, die Aktion für jedes Signal so zu ändern, dass sie die Standardaktion ist. Einfacher ausgedrückt, wenn der Elternteil eine Signalerfassungsfunktion hat, die beim Auftreten eines Signals aufgerufen wird, dann hat diese Funktion keine Bedeutung im neuen Prozess, wenn dieser Elternteil einen neuen Kindprozess ausführt, und daher wird die Disposition desselben Signals auf den Standardwert gesetzt im neuen Prozess.

Da wir normalerweise Prozesse im Hintergrund ausführen, setzt die Shell einfach die Disposition des Beendigungssignals als ignoriert, da wir nicht möchten, dass die Hintergrundprozesse von einem Benutzer beendet werden, der eine Strg + C-Taste drückt, da dies den Zweck des Erstellens eines Prozesses zunichte macht im Hintergrund laufen.

Warum Signalerfassungsfunktionen ablaufinvariant sein sollten?

Wie wir bereits besprochen haben, besteht eine der Optionen für die Signaldisposition darin, das Signal zu erfassen. Im Prozesscode geschieht dies, indem eine Funktion beim Kernel registriert wird, die der Kernel aufruft, wenn das Signal auftritt. Eine Sache, die man im Auge behalten sollte, ist, dass die Funktion, die der Prozess registriert, reentrant sein sollte.

Bevor wir den Grund erläutern, wollen wir zunächst verstehen, was reentrante Funktionen sind. Eine wiedereintrittsfähige Funktion ist eine Funktion, deren Ausführung aus irgendeinem Grund (z. B. aufgrund eines Interrupts oder Signals) zwischenzeitlich angehalten und dann sicher wieder eingegeben werden kann, bevor ihre vorherigen Aufrufe die Ausführung abschließen.

Um nun auf das Problem zurückzukommen, nehmen wir an, eine Funktion func() ist für einen Rückruf bei einem Signalvorkommen registriert. Nehmen Sie nun an, dass diese func() bereits ausgeführt wurde, als das Signal auftrat. Da diese Funktion für dieses Signal zurückgerufen wird, wird die aktuelle Ausführung auf diesem Signal vom Scheduler gestoppt und diese Funktion erneut aufgerufen (aufgrund des Signals).

Das Problem kann sein, wenn func() mit einigen globalen Werten oder Datenstrukturen arbeitet, die in einem inkonsistenten Zustand verbleiben, wenn die Ausführung dieser Funktion in der Mitte gestoppt wurde, dann kann der zweite Aufruf derselben Funktion (aufgrund eines Signals) zu unerwünschten Ergebnissen führen.

Wir sagen also, dass Signalerfassungsfunktionen reentrant gemacht werden sollten.

In unseren Artikeln send-signal-to-process und Linux fuser command finden Sie praktische Beispiele zum Senden von Signalen an einen Prozess.

Threads und Signale

Wir haben bereits in einem der vorherigen Abschnitte gesehen, dass die Signalverarbeitung mit ihrer eigenen Komplexität einhergeht (wie die Verwendung von reentranten Funktionen). Um die Komplexität noch zu erhöhen, haben wir normalerweise Multi-Thread-Anwendungen, bei denen die Signalverarbeitung wirklich kompliziert wird.

Jeder Thread hat seine eigene private Signalmaske (eine Maske, die definiert, welche Signale zustellbar sind), aber die Art und Weise, wie die Signale bereitgestellt werden, wird von allen Threads in der Anwendung geteilt. Dies bedeutet, dass eine Disposition für ein bestimmtes Signal, das von einem Thread gesetzt wird, leicht von einem anderen Thread außer Kraft gesetzt werden kann. In diesem Fall ändert sich der Dispositionsmechanismus für alle Threads.

Beispielsweise kann ein Thread A ein bestimmtes Signal ignorieren, aber ein Thread B im selben Prozess kann dasselbe Signal abfangen, indem er eine Callback-Funktion beim Kernel registriert. In diesem Fall wird die Anfrage von Thread A von der Anfrage von Thread B überschrieben.

Signale werden in jedem Prozess nur an einen einzelnen Thread geliefert. Abgesehen von den Hardwareausnahmen oder dem Ablauf des Timers (die an den Thread geliefert werden, der das Ereignis verursacht hat) werden alle Signale willkürlich an den Prozess weitergegeben.

Um diesem Mangel entgegenzuwirken, gibt es einige Posix-APIs wie pthread_sigmask() usw., die verwendet werden können.

Im nächsten Artikel (Teil 2) dieser Serie werden wir diskutieren, wie Signale in einem Prozess abgefangen werden können, und den praktischen Aspekt der Signalverarbeitung mit Hilfe von Codeschnipseln erläutern.


Linux
  1. Linux-Startvorgang

  2. Linux-Prozesszustände

  3. Linux-CreateProcess?

  4. Wie kann ich unter Linux feststellen, welcher Prozess meinem Prozess ein Signal gesendet hat?

  5. Wann ist setsid() nützlich oder warum müssen wir Prozesse in Linux gruppieren?

So beenden Sie einen Prozess in Linux

Pstree-Befehl unter Linux

Kill-Befehl unter Linux

Prozessüberwachung unter Linux

Linux Bash Scripting Part5 – Signale und Jobs

Was ist ein gestoppter Prozess unter Linux?