[LINUX] Le comportement de signal () dépend de l'option de compilation

introduction

Comme j'étais accro à la mise en œuvre de signal (), je laisserai les résultats de l'enquête sous forme de mémorandum. L'environnement d'enquête est

Contexte

Je refactorisais le code hérité qui a été créé autour de l'année de ma naissance et modifié et développé pour correspondre à l'évolution des plates-formes d'exploitation.

Certains d'entre eux ont utilisé l'appel système signal (). En regardant man de signal (), évitez d'utiliser signal () car son comportement change en fonction de la plateforme. , Sigaction () a été utilisé, donc je l'ai modifié pour utiliser sigaction (), mais il tient ici.

SIGNAL(2)                                    Linux Programmer's Manual

Nom
       signal -Manipulation du signal ANSI C
La description
       signal()Le comportement dépend de la version d'UNIX.
Historiquement, cela dépend aussi de la version de Linux.
Évitez d'utiliser cet appel système et à la place sigaction(2)utiliser.
       ………

la mise en oeuvre

En conclusion, Linux signal () se comporte différemment selon les options de compilation. Même si vous appelez signal (), il est enveloppé dans la glibc, et en interne, il appelle l'appel système sigaction (). Lors de l'appel de cet appel système sigaction (), la valeur de sa_flags, qui est un argument, diffère selon l'option de compilation.

Implémentation dans le noyau Linux

Examinons d'abord l'implémentation interne du noyau Linux.

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

Il convient de noter que l'indicateur spécifié dans sa_flags est (SA_ONESHOT | SA_NOMASK). SA_ONESHOT signifie que sa_handler retourne à SIG_DFL lorsque le processus reçoit un signal. SA_NOMASK signifie que lorsqu'un processus reçoit un signal et exécute un gestionnaire, il reçoit à nouveau le même signal (c'est-à-dire qu'il ne le masque pas). C'est le comportement du signal du System V (). (Unix a deux systèmes majeurs, System V et BSD, et il semble que l'implémentation soit différente à certains endroits. Je ne sais pas en détail car j'ai été la première personne à toucher Linux (OS autre que Windows) en 2012. )

Par conséquent, si vous utilisez le noyau Linux ver.5.5.5, vous pouvez penser que la spécification de signal () est l'implémentation de System V, mais comme mentionné ci-dessus, lorsque vous utilisez la glibc, signal () dans le noyau est Pas appelé.

implémentation de la glibc

Lors de l'appel d'un appel système à partir du langage C, il s'agit essentiellement d'une forme d'appel d'une fonction wrapper de la glibc. Jetez un œil à signal.h (/usr/include/signal.h), qui est inclus lors de l'appel de signal ().

/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

Il diffère des trois manières suivantes.

  1. Si \ _ \ _USE \ _MISC est défini
  2. Si \ _ \ _USE \ _MISC n'est pas défini et que \ _ \ _ REDIRECT \ _NTH est défini
  3. Si ni \ _ \ _USE \ _MISC ni \ _ \ _REDIRECT \ _NTH n'est défini

Je vais expliquer chacun.

Si \ _ \ _USE_MISC est défini

Par défaut, \ _ \ _USE \ _MISC est défini. Vous pouvez le voir en regardant /usr/include/features.h. L'explication de ici était facile à comprendre.

/usr/include/signal.h


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

signal () est déclaré extern et l'entité est dans la glibc. La mise en œuvre est la suivante.

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;

<Omis>

  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)

Il s'agit d'une alias faible (\ _ \ _ bsd \ _signal, signal), et lorsque signal () est appelé, \ _ \ _ bsd \ _signal () est appelé. C'est un comportement BSD, comme vous pouvez le voir à partir du nom \ _ \ _ bsd \ _signal (). Ceci est cohérent avec la description "Par défaut" signal "a la sémantique BSD." Dans le commentaire sur la première ligne de /usr/include/signal.h.

Ce à quoi nous prêtons attention cette fois, c'est la valeur spécifiée pour act.sa_flags. Je pense que \ _ \ _ sigisempty () renverra 0 (je me demande s'il renverra autre chose que 0), donc act.sa_flags est toujours SA_RESTART. Dans SA_RESTART, le gestionnaire est automatiquement réenregistré une fois que le processus a reçu le signal et a terminé le traitement du gestionnaire. Si un signal est reçu pendant l'exécution de l'appel système, le traitement continuera, et s'il était dans l'état d'attente, il sera à nouveau dans l'état d'attente, et aucune erreur ne sera renvoyée par EINTR. Par exception, dans les appels système de la famille de sockets et de la famille msg, lorsqu'un signal est reçu, une erreur est renvoyée par EINTR.

Si \ _ \ _USE \ _MISC n'est pas défini et que \ _ \ _ REDIRECT \ _NTH est défini

Si \ _ \ _USE \ _MISC est défini

Avant d'expliquer l'implémentation de signal (), je présenterai le cas où \ _ \ _USE \ _MISC n'est pas défini.

Par exemple, l'ajout de l'option -ansi rend \ _ \ _USE \ _MISC indéfini. Vous pouvez le vérifier avec la commande suivante.

$ 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)

À propos, __USE_MISC n'est pas défini même si -D_XOPEN_SOURCE est spécifié comme option.

$ 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

En revanche, si -ansi ou -D_XOPEN_SOURCE n'est pas spécifié comme option comme indiqué ci-dessous, c'est-à-dire que par défaut, __USE_MISC est défini.

$ 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

Notez que \ _ \ _REDIRECT \ _NTH est défini indépendamment de la présence ou de l'absence de -ansi ou -D_XOPEN_SOURCE.

Implémentation du signal ()

La déclaration de signal () lorsque \ _ \ _USE \ _MISC n'est pas défini et que \ _ \ _ REDIRECT \ _NTH est défini est la suivante.

/usr/include/signal.h


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

\ _ \ _REDIRECT \ _NTH est une macro qui indique au compilateur d'utiliser le symbole __sysv_signal au lieu du signal de symbole Cela semble être -nth-do-in-unistd-h), donc lorsque vous appelez signal (), __sysv_signal () de la glibc est appelée.

L'implémentation de __sysv_signal () est la suivante.

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;

<Omis>

  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)

Regardez la valeur de sa_flags à chaque fois. SA_ONESHOT et SA_NOMASK sont les mêmes que signal () à l'intérieur du noyau, mais sont également marqués comme SA_INTERRUPT. Ce SA_INTERRUPT n'est pas expliqué dans l'homme de sigaction, et quand je regarde la glibc, il dit / * No-op historique. * /, Alors puis-je l'ignorer? Je bat poliment le bit SA_RESTART, mais je ne suis pas sûr. Existe-t-il un environnement qui a les mêmes bits que SA_INTERRUPT? En premier lieu, SA_RESTART est un drapeau unique à l'extension BSD, je me demande donc s'il est soigneusement vaincu car il n'existe pas dans l'implémentation du système v.

Si ni \ _ \ _USE \ _MISC ni \ _ \ _REDIRECT \ _NTH n'est défini

Puisqu'il est déclaré dans /usr/include/signal.h comme suit, on peut voir que __sysv_signal () est appelé lorsque signal () est appelé indépendamment de la présence ou de l'absence de \ _ \ _REDIRECT \ _NTH. L'utilisation de \ _ \ _REDIRECT \ _NTH affecte-t-elle quelque chose? Je n'étais pas sûr.

/usr/include/signal.h


#  define signal __sysv_signal

De côté

Lorsque j'examinais le code source de la glibc, la fonction portant le même nom était implémentée à plusieurs endroits, et je n'étais pas sûr de celle qui était appelée. Pour le moment, je regardais le code source suivant en tenant compte de mon propre environnement.

Résumé

Puisque le comportement de signal () change en fonction de l'option de compilation, le comportement a changé lorsque toutes les modifications de signal () -> sigaction () ont été modifiées de la même manière avec la mort cérébrale.

Nous espérons que cet article aidera ceux qui maintiennent et refactorisent le code hérité (et bien sûr d'autres).

Recommended Posts

Le comportement de signal () dépend de l'option de compilation
Remarque sur le comportement par défaut de collate_fn dans PyTorch
À propos du comportement de yield_per de SqlAlchemy
Vérifiez le comportement du destroyer en Python
J'ai vérifié les options de copyMakeBorder d'OpenCV
Étudiez l'effet des valeurs aberrantes sur la corrélation
A propos du comportement de enable_backprop de Chainer v2
Publier le sujet de Gmail sur Twitter
Afficher le graphique de tensorBoard sur Jupyter
Changer l'ordre de PostgreSQL dans Heroku
En Python, changez le comportement de la méthode en fonction de la façon dont elle est appelée
À propos du comportement de copy, deepcopy et numpy.copy
[2020July] Vérifiez l'UDID de l'iPad sous Linux
Utilisez la dernière version de PyCharm sur Ubuntu
Calculer la probabilité de valeurs aberrantes sur les moustaches de la boîte
Au moment de la mise à jour de python avec ubuntu
Visualisez le comportement de l'algorithme de tri avec matplotlib
Changer la résolution d'Ubuntu s'exécutant sur VirtualBox
À propos du comportement de la file d'attente pendant le traitement parallèle
[AWS S3] Confirmation de l'existence de dossiers sur S3
Comportement de multiprocessing.pool.Pool.map
rsync Le comportement change en fonction de la présence ou de l'absence de la barre oblique de la source de copie
Installez la dernière version de CMake sur Ubuntu 18.04.4 LTS
Peut-être ai-je surestimé l'impact de Shell Shock sur CGI
Essayez d'estimer le nombre de likes sur Twitter
Dessinez sur Jupyter en utilisant la fonction de tracé des pandas
Tweetez le triple pronostic de la course de bateaux sur Twitter
Différence de résultats en fonction de l'argument du multiprocessus.
Faites défiler le japonais jusqu'à la LED du RaspberryPi Sense HAT
Une réflexion sur la visualisation du champ d'application du modèle de prédiction
Annonce de la disponibilité de Java 11 LTS sur Amazon Linux 2