Ritorna indietro

Lezione 4.B - IPC e Segnali

Pubblicato il 1/10/22 da SeekBytes – 1865 parole

Concetti fondamentali

Un segnale è una notifica a un processo che si è verificato un evento. Essi interrompono il normale flusso di esecuzione di un programma; nella maggior parte dei casi, non è possibile prevedere esattamente quando un segnale arriverà.

Si dice che un segnale sia generato da qualche evento. Una volta generato, un segnale viene successivamente consegnato ad un processo. Tra il momento in cui viene generato e il momento in cui viene consegnato, si dice che un segnale è in sospeso. Normalmente, un segnale in sospeso viene consegnato ad un processo non appena è programmato per essere eseguito, o immediatamente se il processo è già in esecuzione.

Alla consegna di un segnale, un processo esegue una delle seguenti azioni predefinite, a seconda del segnale:

Tipi di segnali

Segnali per terminare un processo:

Segnali per stoppare e riesumare un processo:

Altri segnali:

La lista completa dei segnali disponibili in Linux può essere recuperata con il comando bash “man 7 signal”.

NomeNumeroPuò essere gestito?Azione di default
SIGTERM15Termina un processo
SIGINT2Termina un processo
SIGQUIT3Dump di un processo e termina un processo
SIGKILL9noTermina un processo
SIGSTOP17noFerma un processo
SIGCONT19Riesuma un processo che era stato fermato
SIGPIPE13Termina un processo
SIGALRM14Termina un processo
SIGUSR130Termina un processo
SIGUSR231Termina un processo

Gestione di una Signal

Un gestore di segnali (chiamato anche “catturatore” di segnali) è una funzione che viene chiamata quando un segnale specificato viene consegnato ad un processo. Ha sempre la seguente forma generale:

void sigHandler(int sig) {
	/* Code for the handler */
}

Questa funzione non restituisce nulla (void) e prende un argomento intero (sig). Quando il gestore del segnale è invocato dal kernel, sig è impostato al segnale consegnato al processo. Tipicamente, sig è usato per determinare quale segnale ha causato l’invocazione del gestore invocato quando uno stesso gestore cattura diversi tipi di segnali.

L’invocazione di un gestore di segnali può interrompere il flusso del programma principale in qualsiasi momento. Il kernel chiama il gestore del segnale, e quando il gestore ritorna, l’esecuzione del programma riprende dal punto in cui il gestore l’ha interrotta.

Signal

La chiamata di sistema signal() cambia il signal-handler predefinito per un segnale definito in un processo.

#include <signal.h>

typedef void (*sighandler_t)(int);
// Returns previous signal disposition on success, or SIG_ERR on error
sighandler_t signal(int signum, sighandler_t handler);

signum identifica il segnale di cui vogliamo cambiare la disposizione nel processo. handler può essere uno dei seguenti:

Esempio di come catturare i segnali
void sigHandler(int sig) {
	printf("The signal %s was caught!\n",
	(sig == SIGINT)? "Ctrl-C" : "signal User-1");
}
int main (int argc, char *argv[]) {
	// setting sigHandler to be executed for SIGINT or SIGUSR1
	if (signal(SIGINT, sigHandler) == SIG_ERR ||
	signal(SIGUSR1, sigHandler) == SIG_ERR) {
		errExit("change signal handler failed");
	}
	// Do something else here. During this time, if SIGINT/SIGUSR1
	// is delivered, sigHandler will be used to handle the signal.
	// Reset the default process disposition for SIGINT and SIGUSR1
	if (signal(SIGINT, SIG_DFL) == SIG_ERR ||
	signal(SIGUSR1, SIG_DFL) == SIG_ERR) {
		errExit("reset signal handler failed");
	}
	return 0;
}

Cose da tenere a mente quando si utilizzano signal handlers:

Pause

Chiamando pause si sospende l’esecuzione del processo finché la chiamata non viene interrotta da un gestore di segnale (o fino a quando un segnale non gestito termina il processo).

#include <unistd.h>
// Always return -1 with errno set to EINTR
int pause();

Sleep

La funzione sleep sospende l’esecuzione del processo chiamante per il numero di secondi specificato nell’argomento seconds o fino a quando un segnale viene catturato (interrompendo così la chiamata).

#include <unistd.h>
unsigned int sleep(unsigned int seconds); // Returns 0 on normal completion, or number of unslept seconds if prematurely terminated
Esempio di waiting di un segnale
void sigHandler(int sig) { 
	printf("Well done!\n"); 
}

int main (int argc, char *argv[]) {
	if (signal(SIGINT, sigHandler) == SIG_ERR)
		errExit("change signal handler failed");
	int time = 30;
	printf("We can wait for %d seconds!\n", time);
	time = sleep(time); // the process is suspended for max. 30sec.
	printf("%s!\n", (time==0)? "out of time", "just in time");
}

Mandare un segnale (kill)

La system call kill manda un segnale ad un altro processo.

#include <signal.h>
// Returns 0 on success, or -1 on error
int kill(pid_t pid, int sig);

L’argomento pid identifica uno o più processi a cui il segnale specificato da sig deve essere inviato.

Inviare un segnale SIGKILL ad un processo figlio
int main (int argc, char *argv[]) {
	pid_t child = fork();
	switch(child) {
	case -1:
		errExit("fork");
	case 0: /* Child process */
		while(1); // <- child is stuck here!
	default: /* Parent process */
		sleep(10); // wait 10 seconds
		kill(child, SIGKILL); // kill the child process
	}
	return 0;
}

Alarm

La chiamata al sistema di allarme fa in modo che un segnale SIGALRM sia consegnato al al processo chiamante dopo un ritardo fisso.

#include <signal.h>

// Always succeeds, returning number of seconds remaining on
// any previously set timer, or 0 if no timer previously was set
unsigned int alarm(unsigned int seconds);

L’argomento seconds specifica il numero di secondi nel futuro in cui il timer deve scadere. In quel momento, un segnale SIGALRM viene consegnato al processo chiamante. L’impostazione di un timer con allarme sovrascrive qualsiasi timer precedentemente impostato.

Impostare un timer con la syscall alarm
void sigHandler(int sig) { printf("Out of time!\n"); _exit(0); }

int main (int argc, char *argv[]) {
	if (signal(SIGALRM, sigHandler) == SIG_ERR)
		errExit("change signal handler failed");

	int time = 30;
	printf("We have %d seconds to complete the job!\n", time);
	alarm(time); // setting a timer

	/* Do something else here. */
	time = alarm(0); // disabling timer
	printf("%s seconds before timer expirations!\n", time);
	return 0;
}

Impostare o bloccare un segnale

Set di segnali (sigemptyset e sigfillset)

Il tipo di dati sigset t rappresenta un insieme di segnali. Le funzioni sigemptyset e sigfillet devono essere utilizzate per inizializzare un insieme di segnali, prima di utilizzarlo in qualsiasi altro modo.

#include <signal.h>

typedef unsigned long sigset_t;
// Both return 0 on success, or -1 on error.
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

sigemptyset inizializza un insieme di segnali per non contenere alcun segnale. sigfillset inizializza un insieme per contenere tutti i segnali.

Sigaddset e Sigdelset

Dopo l’inizializzazione, i singoli segnali possono essere aggiunti a un insieme usando sigaddset e rimossi usando sigdelset.

#include <signal.h>

// Both return 0 on success, or -1 on error
int sigaddset(sigset_t *set, int sig);
int sigdelset(sigset_t *set, int sig);

Sia per sigaddset che per sigdelset, l’argomento sig è un numero di segnale. La funzione sigismember è utilizzata per verificare l’appartenenza ad un insieme.

#include <signal.h>
// Restituisce 1 se sig è un membro di set, altrimenti 0
int sigismember(const sigset_t *set, int sig);

Sigprocmask

Per ogni processo, il kernel mantiene una maschera di segnale, cioè un insieme di segnali la cui consegna al processo è attualmente bloccata. Se un segnale - che è bloccato - è inviato ad un processo, la consegna di quel segnale è ritardata fino a quando non viene sbloccato, rimuovendolo dalla maschera dei segnali del processo. La chiamata di sistema sigprocmask può essere usata in qualsiasi momento per aggiungere esplicitamente segnali alla maschera dei segnali e rimuovere segnali da essa.

#include <signal.h>
// Returns 0 on success, or -1 on error
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

L’argomento how determina i cambiamenti che sigprocmask fa alla maschera di segnale:

Bloccare un segnale a parte SIGTERM
int main (int argc, char *argv[]) {
	sigset_t mySet, prevSet;
	// inizializziamo mySet per contenere tutti i segnali
	sigfillset(&mySet);
	// rimuove SIGTERM da mySet
	sigdelset(&mySet, SIGTERM);
	// bloccare tutti i segnali tranne SIGTERM
	sigprocmask(SIG_SETMASK, &mySet, &prevSet);
	// il processo non è interrotto da segnali eccetto SIGTERM
	// resetta la maschera dei segnali del processo
	sigprocmask(SIG_SETMASK, &prevSet, NULL);
	// il processo non è interrotto da segnali in prevSet
	return 0;
}