[LINUX] Das Verhalten von signal () hängt von der Kompilierungsoption ab

Einführung

Da ich süchtig nach der Implementierung von signal () war, werde ich die Umfrageergebnisse als Memorandum belassen. Die Umfrageumgebung ist

Hintergrund

Ich habe Legacy-Code überarbeitet, der in dem Jahr erstellt wurde, in dem ich geboren und modifiziert und erweitert wurde, um ihn an sich ändernde Betriebsplattformen anzupassen.

Einige von ihnen verwendeten den Systemaufruf signal (). Wenn Sie man von signal () betrachten, vermeiden Sie die Verwendung von signal (), da sich sein Verhalten je nach Plattform ändert. , Sigaction () wurde verwendet, daher habe ich es geändert, um sigaction () zu verwenden, aber es passt hier hinein.

SIGNAL(2)                                    Linux Programmer's Manual

Name
       signal -ANSI C-Signalmanipulation
Erläuterung
       signal()Das Verhalten hängt von der UNIX-Version ab.
Historisch gesehen hängt es auch von der Linux-Version ab.
Vermeiden Sie die Verwendung dieses Systemaufrufs und stattdessen die Unterzeichnung(2)benutzen.
       ………

Implementierung

Zusammenfassend lässt sich sagen, dass sich Linux signal () je nach Kompilierungsoptionen unterschiedlich verhält. Selbst wenn Sie signal () aufrufen, wird es in glibc eingeschlossen und ruft intern den Systemaufruf sigaction () auf. Beim Aufruf dieses Systemaufrufs sigaction () unterscheidet sich der Wert von sa_flags, bei dem es sich um ein Argument handelt, je nach Kompilierungsoption.

Implementierung im Linux-Kernel

Schauen wir uns zunächst die interne Implementierung des Linux-Kernels an.

/*
 * For backwards compatibility.  Functionality superseded by sigaction.
 */
SYSCALL_DEFINE2(signal, int, sig, __sighandler_t, handler)
{
	struct k_sigaction new_sa, old_sa;
	int ret;

	new_sa.sa.sa_handler = handler;
	new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;
	sigemptyset(&new_sa.sa.sa_mask);

	ret = do_sigaction(sig, &new_sa, &old_sa);

	return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
}

Es ist zu beachten, dass das in sa_flags angegebene Flag (SA_ONESHOT | SA_NOMASK) lautet. SA_ONESHOT bedeutet, dass sa_handler zu SIG_DFL zurückkehrt, wenn der Prozess ein Signal empfängt. SA_NOMASK bedeutet, dass ein Prozess, der ein Signal empfängt und einen Handler ausführt, dasselbe Signal erneut empfängt (dh es nicht maskiert). Dies ist das Verhalten des Systems V-Signals (). (Unix hat zwei Hauptsysteme, System V und BSD, und es scheint, dass die Implementierung an einigen Stellen unterschiedlich ist. Ich weiß es nicht im Detail, da ich 2012 als erster Linux (ein anderes Betriebssystem als Windows) berührt habe. )

Wenn Sie den Linux-Kernel Version 5.5.5 verwenden, denken Sie möglicherweise, dass die Spezifikation von signal () die Implementierung von System V ist. Wie oben erwähnt, ist signal () bei Verwendung von glibc im Kernel Nicht angerufen.

glibc Implementierung

Wenn Sie einen Systemaufruf aus der Sprache C aufrufen, handelt es sich im Grunde genommen um eine Form des Aufrufs einer Wrapper-Funktion von glibc. Schauen Sie sich signal.h (/usr/include/signal.h) an, das beim Aufruf von signal () enthalten ist.

/usr/include/signal.h


/* Set the handler for the signal SIG to HANDLER, returning the old
   handler, or SIG_ERR on error.
   By default `signal' has the BSD semantic.  */
#ifdef __USE_MISC
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
     __THROW;
#else
/* Make sure the used `signal' implementation is the SVID version. */
# ifdef __REDIRECT_NTH
extern __sighandler_t __REDIRECT_NTH (signal,
				      (int __sig, __sighandler_t __handler),
				      __sysv_signal);
# else
#  define signal __sysv_signal
# endif
#endif

Es unterscheidet sich in den folgenden drei Punkten.

  1. Wenn \ _ \ _USE \ _MISC definiert ist
  2. Wenn \ _ \ _USE \ _MISC nicht definiert ist und \ _ \ _ REDIRECT \ _NTH definiert ist
  3. Wenn weder \ _ \ _USE \ _MISC noch \ _ \ _REDIRECT \ _NTH definiert ist

Ich werde jeden erklären.

Wenn \ _ \ _USE_MISC definiert ist

Standardmäßig ist \ _ \ _USE \ _MISC definiert. Sie können dies unter /usr/include/features.h sehen. Die Erklärung von hier war leicht zu verstehen.

/usr/include/signal.h


extern __sighandler_t signal (int __sig, __sighandler_t __handler)
     __THROW;

signal () wird als extern deklariert und die Entität befindet sich in glibc. Die Implementierung ist wie folgt.

c:glibc-2.28/sysdeps/posix/signal.c


sigset_t _sigintr attribute_hidden;		/* Set by siginterrupt.  */

__sighandler_t
__bsd_signal (int sig, __sighandler_t handler)
{
  struct sigaction act, oact;

<Ausgelassen>

  act.sa_handler = handler;
  __sigemptyset (&act.sa_mask);
  __sigaddset (&act.sa_mask, sig);
  act.sa_flags = __sigismember (&_sigintr, sig) ? 0 : SA_RESTART;
  if (__sigaction (sig, &act, &oact) < 0)
    return SIG_ERR;

  return oact.sa_handler;
}
weak_alias (__bsd_signal, signal)

Es ist ein schwacher_Alias (\ _ \ _ bsd \ _signal, signal), und wenn signal () aufgerufen wird, wird \ _ \ _ bsd \ _signal () aufgerufen. Dies ist das BSD-Verhalten, wie Sie am Namen \ _ \ _ bsd \ _signal () sehen können. Dies steht im Einklang mit der Beschreibung "Standardmäßig hat das Signal die BSD-Semantik". Im Kommentar in der ersten Zeile von /usr/include/signal.h.

Was wir dieser Zeit beachten, ist der für act.sa_flags angegebene Wert. Ich denke, \ _ \ _ sigisempty () wird 0 zurückgeben (ich frage mich, ob es etwas anderes als 0 zurückgeben wird), also ist act.sa_flags immer SA_RESTART. In SA_RESTART wird der Handler automatisch neu registriert, nachdem der Prozess das Signal empfangen und die Handlerverarbeitung abgeschlossen hat. Wenn während der Ausführung des Systemaufrufs ein Signal empfangen wird, wird die Verarbeitung fortgesetzt, und wenn es sich im Wartezustand befand, befindet es sich wieder im Wartezustand, und EINTR gibt keinen Fehler zurück. In Ausnahmefällen wird bei Systemaufrufen der Socket-Familie und der msg-Familie beim Empfang eines Signals ein Fehler von EINTR zurückgegeben.

Wenn \ _ \ _USE \ _MISC nicht definiert ist und \ _ \ _ REDIRECT \ _NTH definiert ist

Gibt an, ob \ _ \ _USE \ _MISC definiert ist

Bevor ich die Implementierung von signal () erkläre, werde ich den Fall vorstellen, in dem \ _ \ _USE \ _MISC nicht definiert ist.

Wenn Sie beispielsweise die Option -ansi hinzufügen, wird \ _ \ _USE \ _MISC undefiniert. Sie können dies mit dem folgenden Befehl überprüfen.

$ echo "#include <signal.h>" | gcc -ansi -dM -E - | grep __USE_
#define __USE_FORTIFY_LEVEL 0
#define __bos(ptr) __builtin_object_size (ptr, __USE_FORTIFY_LEVEL > 1)

Übrigens ist __USE_MISC nicht definiert, auch wenn -D_XOPEN_SOURCE als Option angegeben ist.

$ echo "#include <signal.h>" | gcc  -D_XOPEN_SOURCE -dM -E - | grep __USE_
#define __USE_FORTIFY_LEVEL 0
#define __USE_ISOC11 1
#define __USE_ISOC95 1
#define __USE_ISOC99 1
#define __USE_XOPEN 1
#define __USE_POSIX2 1
#define __USE_POSIX 1
#define __bos(ptr) __builtin_object_size (ptr, __USE_FORTIFY_LEVEL > 1)
#define __USE_POSIX_IMPLICITLY 1

Wenn andererseits -ansi oder -D_XOPEN_SOURCE nicht wie unten gezeigt als Option angegeben ist, ist standardmäßig __USE_MISC definiert.

$ echo "#include <features.h>" | gcc -dM -E - | grep __USE_
#define __USE_FORTIFY_LEVEL 0
#define __USE_ISOC11 1
#define __USE_ISOC95 1
#define __USE_ISOC99 1
#define __USE_XOPEN2K 1
#define __USE_POSIX199506 1
#define __USE_POSIX2 1
#define __USE_XOPEN2K8 1
#define __USE_MISC 1
#define __USE_POSIX 1
#define __bos(ptr) __builtin_object_size (ptr, __USE_FORTIFY_LEVEL > 1)
#define __USE_POSIX199309 1
#define __USE_POSIX_IMPLICITLY 1
#define __USE_ATFILE 1

Beachten Sie, dass \ _ \ _REDIRECT \ _NTH unabhängig vom Vorhandensein oder Fehlen von -ansi oder -D_XOPEN_SOURCE definiert ist.

Implementierung von signal ()

Die Deklaration von signal (), wenn \ _ \ _USE \ _MISC nicht definiert ist und \ _ \ _ REDIRECT \ _NTH definiert ist, lautet wie folgt.

/usr/include/signal.h


extern __sighandler_t __REDIRECT_NTH (signal,
				      (int __sig, __sighandler_t __handler),
				      __sysv_signal);

\ _ \ _REDIRECT \ _NTH ist ein Makro, das den Compiler anweist, das Symbol __sysv_signal anstelle des Symbolsignals zu verwenden Es scheint -nth-do-in-unistd-h) zu sein. Wenn Sie also signal () aufrufen, wird glibcs __sysv_signal () aufgerufen.

Die Implementierung von __sysv_signal () ist wie folgt.

sysdeps/posix/sysv_signal.c


/* Set the handler for the signal SIG to HANDLER,
   returning the old handler, or SIG_ERR on error.  */
__sighandler_t
__sysv_signal (int sig, __sighandler_t handler)
{
  struct sigaction act, oact;

<Ausgelassen>

  act.sa_handler = handler;
  __sigemptyset (&act.sa_mask);
  act.sa_flags = SA_ONESHOT | SA_NOMASK | SA_INTERRUPT;
  act.sa_flags &= ~SA_RESTART;
  if (__sigaction (sig, &act, &oact) < 0)
    return SIG_ERR;

  return oact.sa_handler;
}

weak_alias (__sysv_signal, sysv_signal)

Überprüfen Sie jedes Mal den Wert von sa_flags. SA_ONESHOT und SA_NOMASK sind identisch mit signal () im Kernel, werden jedoch auch als SA_INTERRUPT gekennzeichnet. Dieses SA_INTERRUPT wird im Sigaction-Mann nicht erklärt, und wenn ich glibc betrachte, heißt es / * Historical no-op. * /, Kann ich es also ignorieren? Ich besiege höflich das SA_RESTART-Bit, bin mir aber nicht sicher. Gibt es eine Umgebung mit denselben Bits wie SA_INTERRUPT? Erstens ist SA_RESTART ein Flag, das nur für die BSD-Erweiterung gilt. Ich frage mich daher, ob es sorgfältig besiegt wird, da es in der Implementierung von System v nicht vorhanden ist.

Wenn weder \ _ \ _USE \ _MISC noch \ _ \ _REDIRECT \ _NTH definiert ist

Da es in /usr/include/signal.h wie folgt deklariert ist, ist ersichtlich, dass __sysv_signal () aufgerufen wird, wenn signal () aufgerufen wird, unabhängig von der Anwesenheit oder Abwesenheit von \ _ \ _REDIRECT \ _NTH. Hat die Verwendung von \ _ \ _REDIRECT \ _NTH Auswirkungen auf irgendetwas? Ich war mir nicht sicher.

/usr/include/signal.h


#  define signal __sysv_signal

Beiseite

Als ich den Quellcode von glibc untersuchte, wurde die gleichnamige Funktion an mehreren Stellen implementiert, und ich war mir nicht sicher, welche aufgerufen wurde. Vorläufig habe ich mir den folgenden Quellcode unter Berücksichtigung meiner eigenen Umgebung angesehen.

Zusammenfassung

Da sich das Verhalten von signal () abhängig von der Kompilierungsoption ändert, hat sich das Verhalten geändert, als alle Änderungen an signal () -> sigaction () auf dieselbe Weise mit dem Hirntod geändert wurden.

Wir hoffen, dass dieser Artikel denjenigen hilft, die Legacy-Code pflegen und umgestalten (und natürlich auch anderen).

Recommended Posts

Das Verhalten von signal () hängt von der Kompilierungsoption ab
Hinweis zum Standardverhalten von collate_fn in PyTorch
Über das Verhalten von Yield_per von SqlAlchemy
Überprüfen Sie das Verhalten des Zerstörers in Python
Ich habe die Optionen von copyMakeBorder von OpenCV überprüft
Untersuchen Sie die Auswirkung von Ausreißern auf die Korrelation
Informationen zum Verhalten von enable_backprop von Chainer v2
Veröffentlichen Sie das Thema Google Mail auf Twitter
Zeigen Sie das Diagramm von tensorBoard auf jupyter an
Ändern Sie die Reihenfolge von PostgreSQL in Heroku
Ändern Sie in Python das Verhalten der Methode je nach Aufruf
Über das Verhalten von copy, deepcopy und numpy.copy
[2020Juli] Überprüfen Sie die UDID des iPad unter Linux
Verwenden Sie die neueste Version von PyCharm unter Ubuntu
Berechnen Sie die Wahrscheinlichkeit von Ausreißern auf den Box-Whiskern
Zum Zeitpunkt des Python-Updates mit Ubuntu
Visualisieren Sie das Verhalten des Sortieralgorithmus mit matplotlib
Ändern Sie die Auflösung von Ubuntu, das auf VirtualBox ausgeführt wird
Informationen zum Verhalten der Warteschlange während der Parallelverarbeitung
[AWS S3] Bestätigung des Vorhandenseins von Ordnern in S3
Verhalten von multiprocessing.pool.Pool.map
rsync Das Verhalten ändert sich abhängig vom Vorhandensein oder Fehlen des Schrägstrichs der Kopierquelle
Installieren Sie die neueste Version von CMake unter Ubuntu 18.04.4 LTS
Vielleicht habe ich die Auswirkungen von Shell Shock auf CGI überschätzt
Versuchen Sie, die Anzahl der Likes auf Twitter zu schätzen
Zeichnen auf Jupyter mit der Plot-Funktion von Pandas
Tweet die dreifache Vorhersage des Bootsrennens auf Twitter
Unterschied in den Ergebnissen abhängig vom Argument von multiprocess.Process
Scrollen Sie mit Japanisch zur LED von RaspberryPi Sense HAT
Eine Überlegung zur Visualisierung des Anwendungsbereichs des Vorhersagemodells
Ankündigung der Verfügbarkeit von Java 11 LTS unter Amazon Linux 2