Da ich süchtig nach der Implementierung von signal () war, werde ich die Umfrageergebnisse als Memorandum belassen. Die Umfrageumgebung ist
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.
………
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.
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.
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.
Ich werde jeden erklären.
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.
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.
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.
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
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.
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