POSIX
Les signaux
Recherche…
Syntaxe
- alarme non signée (secondes non signées);
- int kill (pid_t pid, int sig);
Paramètres
Fonction, paramètre (s), valeur de retour | La description |
---|---|
alarm() | nom de la fonction |
unsigned seconds | Secondes pour déclencher une alarme ou 0 pour annuler toute alarme en attente |
> = 0 | 0 si aucune autre alarme n'était en attente, sinon le nombre de secondes pendant lesquelles l'alarme en attente était encore ouverte. Cette fonction n'échouera pas. |
- | - |
kill() | nom de la fonction |
pid_t pid | . |
int sig | 0 ou ID du signal |
0, -1 | En cas de succès, 0 est renvoyé, -1 en cas d'échec avec la définition d' errno sur EINVAL , EPERM ou ESRCH . |
Relever SIGALARM avec l'action par défaut
En utilisant l' alarm
, l'utilisateur peut programmer le signal SIGALARM
pour qu'il soit levé après l'intervalle spécifié. Si l'utilisateur n'a pas bloqué, ignoré ou spécifié un gestionnaire de signal explicite pour ce signal, l'action par défaut pour ce signal sera effectuée à l'arrivée. L'action par défaut par spécification pour SIGALARM
consiste à terminer le processus:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char** argv)
{
printf("Hello!\n");
// Set the alarm for five second
alarm(5); // Per POSIX, this cannot fail
// Now sleep for 15 seconds
for (int i = 1; i <= 15; i++)
{
printf("%d\n", i);
sleep(1);
}
// And print the message before successful exit
printf("Goodbye!\n");
return EXIT_SUCCESS;
}
Cela produit:
Hello!
1
2
3
4
5
[2] 35086 alarm ./a.out
Réglage du gestionnaire de signaux à l'aide de sigaction et de signaux de relance à l'aide de raise
Pour qu'un programme réagisse à un certain signal, autre qu'une action par défaut, un gestionnaire de signal personnalisé peut être installé à l'aide de sigaction
. sigaction
reçoit trois arguments - signal sur lequel agir, pointeur sur la structure sigaction_t
qui, sinon NULL
, décrit le nouveau comportement et le pointeur sur sigaction_t
qui, si NULL
ne sera pas rempli avec l'ancien comportement (on peut donc le restaurer). Augmenter les signaux dans le même processus peut être fait avec la méthode raise
. Si plus de contrôle est nécessaire (pour envoyer le signal à un autre processus, kill
ou pthread_kill
peut être utilisé, qui accepte l'identifiant du processus de destination ou l'identifiant du thread).
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Signals are numbered from 1, signal 0 doesn't exist
volatile sig_atomic_t last_received_signal = 0;
// Signal handler, will set the global variable
// to indicate what is the last signal received.
// There should be as less work as possible inside
// signal handler routine, and one must take care only
// to call reentrant functions (in case of signal arriving
// while program is already executing same function)
void signal_catcher(int signo, siginfo_t *info, void *context)
{
last_received_signal = info->si_signo;
}
int main (int argc, char** argv)
{
// Setup a signal handler for SIGUSR1 and SIGUSR2
struct sigaction act;
memset(&act, 0, sizeof act);
// sigact structure holding old configuration
// (will be filled by sigaction):
struct sigaction old_1;
memset(&old_1, 0, sizeof old_1);
struct sigaction old_2;
memset(&old_2, 0, sizeof old_2);
act.sa_sigaction = signal_catcher;
// When passing sa_sigaction, SA_SIGINFO flag
// must be specified. Otherwise, function pointed
// by act.sa_handler will be invoked
act.sa_flags = SA_SIGINFO;
if (0 != sigaction(SIGUSR1, &act, &old_1))
{
perror("sigaction () failed installing SIGUSR1 handler");
return EXIT_FAILURE;
}
if (0 != sigaction(SIGUSR2, &act, &old_2))
{
perror("sigaction() failed installing SIGUSR2 handler");
return EXIT_FAILURE;
}
// Main body of "work" during which two signals
// will be raised, after 5 and 10 seconds, and which
// will print last received signal
for (int i = 1; i <= 15; i++)
{
if (i == 5)
{
if (0 != raise(SIGUSR1))
{
perror("Can't raise SIGUSR1");
return EXIT_FAILURE;
}
}
if (i == 10)
{
if (0 != raise(SIGUSR2))
{
perror("Can't raise SIGUSR2");
return EXIT_FAILURE;
}
}
printf("Tick #%d, last caught signal: %d\n",
i, last_received_signal);
sleep(1);
}
// Restore old signal handlers
if (0 != sigaction(SIGUSR1, &old_1, NULL))
{
perror("sigaction() failed restoring SIGUSR1 handler");
return EXIT_FAILURE;
}
if (0 != sigaction(SIGUSR2, &old_2, NULL))
{
perror("sigaction() failed restoring SIGUSR2 handler");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Cela produit:
Tick #1, last caught signal: 0
Tick #2, last caught signal: 0
Tick #3, last caught signal: 0
Tick #4, last caught signal: 0
Tick #5, last caught signal: 30
Tick #6, last caught signal: 30
Tick #7, last caught signal: 30
Tick #8, last caught signal: 30
Tick #9, last caught signal: 30
Tick #10, last caught signal: 31
Tick #11, last caught signal: 31
Tick #12, last caught signal: 31
Tick #13, last caught signal: 31
Tick #14, last caught signal: 31
Tick #15, last caught signal: 31
Un processus qui se suicide en utilisant kill ()
Un processus peut (essayer de) envoyer un signal à tout autre processus en utilisant la fonction kill()
.
Pour ce faire, le processus d'envoi doit connaître le PID du processus de réception. Comme, sans introduire de race, un processus ne peut être sûr que de son propre PID (et des PID de ses enfants), l'exemple le plus simple pour démontrer l'utilisation de kill()
consiste à envoyer un signal à lui-même.
Ci-dessous, un exemple de processus initiant sa propre terminaison en s'envoyant un kill-signal ( SIGKILL
):
#define _POSIX_C_SOURCE 1
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
int main (void)
{
pid_t pid = getpid(); /* Get my iown process ID. */
kill(pid, SIGKILL); /* Send myself a KILL signal. */
puts("Signal delivery initiated."); /* Although not guaranteed,
practically the program never gets here. */
pause(); /* Wait to die. */
puts("This never gets printed.");
}
Sortie:
Killed
(... ou similaire, selon l'implémentation)
Gérer SIGPIPE généré par write () d'une manière thread-safe
Lorsque write()
est appelé pour un socket ou un socket de flux nommé ou non nommé dont la fin de lecture est fermée, deux choses se produisent:
-
SIGPIPE
signalSIGPIPE
est envoyé au processus appeléwrite()
-
SIGPIPE
signalSIGPIPE
est envoyé au thread qui a appeléwrite()
-
EPIPE
erreurEPIPE
est renvoyée parwrite()
Il y a plusieurs façons de traiter avec SIGPIPE
:
- Pour les sockets,
SIGPIPE
peut être désactivé en définissant des options spécifiques à la plate-forme telles queMSG_NOSIGNAL
sous Linux etSO_NOSIGPIPE
dans BSD (fonctionne uniquement pour l'send
, mais pas pour l'write
). Ce n'est pas portable.
- Pour les FIFO (canaux nommés),
SIGPIPE
ne sera pas généré si writer utiliseO_RDWR
au lieu deO_WRONLY
, afin que la fin de la lecture soit toujours ouverte. Cependant, cela désactiveEPIPE
aussi.
- Nous pouvons ignorer
SIGPIPE
ou définir le gestionnaire global. C'est une bonne solution, mais ce n'est pas acceptable si vous ne contrôlez pas toute l'application (par exemple, vous écrivez une bibliothèque).
- Avec les versions récentes de POSIX, nous pouvons utiliser le fait que
SIGPIPE
est envoyé au thread qui a appeléwrite()
et le gère en utilisant la technique de traitement du signal synchrone.
Le code ci-dessous illustre la gestion SIGPIPE
sans risque pour POSIX.1-2004 et les versions ultérieures.
C'est inspiré par ce post :
- Tout d'abord, ajoutez
SIGPIPE
au signal de masque du thread en cours en utilisantpthread_sigmask()
. - Vérifiez si
SIGPIPE
est déjà en attente à l'aide desigpending()
. - Appelez
write()
. Si la fin de la lecture est fermée,SIGPIPE
sera ajouté au masque de signaux en attente etEPIPE
sera renvoyé. - Si
write()
renvoyéEPIPE
et queSIGPIPE
n'était pas déjà en attente avantwrite()
, supprimez-le du masque de signaux en attente à l'aide desigtimedwait()
. - Restaurer le masque de signal original en utilisant
pthread_sigmask()
.
Code source:
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <sys/signal.h>
ssize_t safe_write(int fd, const void* buf, size_t bufsz)
{
sigset_t sig_block, sig_restore, sig_pending;
sigemptyset(&sig_block);
sigaddset(&sig_block, SIGPIPE);
/* Block SIGPIPE for this thread.
*
* This works since kernel sends SIGPIPE to the thread that called write(),
* not to the whole process.
*/
if (pthread_sigmask(SIG_BLOCK, &sig_block, &sig_restore) != 0) {
return -1;
}
/* Check if SIGPIPE is already pending.
*/
int sigpipe_pending = -1;
if (sigpending(&sig_pending) != -1) {
sigpipe_pending = sigismember(&sig_pending, SIGPIPE);
}
if (sigpipe_pending == -1) {
pthread_sigmask(SIG_SETMASK, &sig_restore, NULL);
return -1;
}
ssize_t ret;
while ((ret = write(fd, buf, bufsz)) == -1) {
if (errno != EINTR)
break;
}
/* Fetch generated SIGPIPE if write() failed with EPIPE.
*
* However, if SIGPIPE was already pending before calling write(), it was
* also generated and blocked by caller, and caller may expect that it can
* fetch it later. Since signals are not queued, we don't fetch it in this
* case.
*/
if (ret == -1 && errno == EPIPE && sigpipe_pending == 0) {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 0;
int sig;
while ((sig = sigtimedwait(&sig_block, 0, &ts)) == -1) {
if (errno != EINTR)
break;
}
}
pthread_sigmask(SIG_SETMASK, &sig_restore, NULL);
return ret;
}