Forma molto semplice di comunicazione tra processi.
Tecnicamente sono interruzioni software causate da svariati eventi:
ctrl-c
(SIGINT
);SIGALARM
.Cosa possiamo fare quando arriva un segnale?
Segnali POSIX (Portable Operating System Interface) da man 7 signal
Signal Value Action Comment
-----------------------------------------------------------------------
SIGHUP 1 Term Hangup detected on controlling terminal
or death of controlling process
SIGINT 2 Term Interrupt from keyboard <== ctrl-C
SIGQUIT 3 Core Quit from keyboard <== ctrl-\
SIGILL 4 Core Illegal Instruction
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating point exception
SIGKILL 9 Term Kill signal <== kill -9 (da shell)
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no readers
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal <== kill (da shell)
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated <== gestito da wait()
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at tty
SIGTTIN 21,21,26 Stop tty input for background process
SIGTTOU 22,22,27 Stop tty output for background process
The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
The entries in the "Action" column of the tables below specify the
default disposition for each signal, as follows:
Term Default action is to terminate the process.
Ign Default action is to ignore the signal.
Core Default action is to terminate the process and dump core (see core(5)).
Stop Default action is to stop the process.
Cont Default action is to continue the process if it is currently stopped.
alarm
alarm
manda un SIGALRM
dopo n secondi (serve per dare un timeout). Il default handler termina il programma.
La shell analizza lo stato di uscita del programma e stampa il motivo della terminazione (“alarm clock”).
#include <unistd.h>
int main()
{
alarm(3);
while(1){}
}
Il programma “punta una sveglia” dopo 3 secondi e attende in un ciclo infinito.
Tramite la system call signal
è possibile cambiare il gestore dei segnali. La system call prende come parametri un segnale e una funzione che da quel momento diventerà il nuovo gestore del segnale.
void alarmHandler()
{
printf("questo me lo gestisco io!\n");
alarm(3); // ri-setta il timer a 3 secondi
}
int main() {
signal(SIGALRM, alarmHandler);
alarm(3);
while(1){}
}
È possibile passare alla signal
le costanti SIG_IGN
o SIG_DFL
al posto della funzione handler per indicare, rispettivamente:
Il valore di ritorno di signal
è
SIG_ERR
in caso di errore
Se modifichiamo il gestore del segnale SIGINT
possiamo evitare che un programma venga interrotto tramite ctrl-c
int main() {
void (*old)(int);
old = signal(SIGINT,SIG_IGN);
printf("Sono protetto!\n");
sleep(3);
signal(SIGINT,old);
printf("Non sono più protetto!\n");
sleep(3);
}
Notare l’uso del valore di ritorno della signal per reimpostare il gestore originale.
kill
La chiamata a sistema kill
manda un segnale a un processo.
int main(){
pid_t pid1,pid2;
pid1 = fork();
if ( pid1 < 0 ) {
perror("errore fork"); exit(EXIT_FAILURE);
} else if (pid1 == 0)
while(1) { // primo figlio
printf("%d è vivo !\n",getpid());
sleep(1); }
pid2 = fork();
if ( pid2 < 0 ) {
perror("errore fork"); exit(EXIT_FAILURE);
} else if (pid2 == 0)
while(1) { // secondo figlio
printf("%d è vivo !\n",getpid());
sleep(1); }
// processo genitore
sleep(2);
kill(pid1,SIGSTOP); // sospende il primo figlio
sleep(5);
kill(pid1,SIGCONT); // risveglia il primo figlio
sleep(2);
kill(pid1,SIGINT); // termina il primo figlio
kill(pid2,SIGINT); // termina il secondo figlio
}
A volte risulta utile bloccare temporaneamente la ricezione dei segnali per poi riattivarli. Tali segnali non sono ignorati ma solamente ‘posticipati’.
sigprocmask(int action, sigset_t *newmask, sigset_t *oldmask)
, dove action
vale:
action==SIG_BLOCK
: l’insieme dei segnali newmask
viene unito all’insieme dei segnali attualmente bloccati, che sono restituiti in oldmask
;
action==SIG_UNBLOCK
: l’insieme dei segnali newmask
viene sottratto dai segnali attualmente bloccati, sempre restituiti in oldmask
;
action==SIG_SETMASK
: l’insieme dei segnali newmask
sostituisce quello dei segnali attualmente bloccati (oldmask
).
Per gestire gli insiemi di segnali (di tipo sigset_t
) si utilizzano:
sigemptyset(sigset_t *set)
che inizializza l’insieme set
all’insieme vuotosigaddset(sigset_t *set, int signum)
che aggiunge il segnale signum
all’insieme setNOTA: POSIX non specifica se più occorrenze dello stesso segnale debbano essere memorizzate (accodate) oppure no. Tipicamente se più segnali uguali vengono generati, solamente uno verrà “recapitato” quando il blocco viene tolto.
SIGINT
(ctrl-c)
int main() {
sigset_t newmask,oldmask;
sigemptyset(&newmask); // insieme vuoto
sigaddset(&newmask, SIGINT); // aggiunge SIGINT alla "maschera"
// setta la nuova maschera e memorizza la vecchia
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
perror("errore settaggio maschera"); exit(1);
}
printf("Sono protetto!\n");
sleep(3);
// reimposta la vecchia maschera
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
perror("errore settaggio maschera"); exit(1);
}
printf("Non sono piu' protetto!\n");
sleep(3);
}
Se digitiamo ctrl-c
mentre il segnale è mascherato esso viene sospeso. Appena la maschera viene rimossa, il segnale è ricevuto dal processo che viene immediatamente interrotto.
NOTA: Non stiamo modificando il gestore del segnale.
L’uso della printf
nell’handler è rischioso perchè usa dati globali: Se anche il programma interrotto stava facendo I/O i due potrebbero interferire!
Facendo man signal-safety
(in sistemi Linux recenti), troviamo la lista di funzioni safe:
Function Notes abort(3) Added in POSIX.1-2003 accept(2) access(2) aio_error(3) aio_return(3) aio_suspend(3) See notes below alarm(2) bind(2) ... (molte altre) ...
Usare solo funzioni safe nel handler e assicurarsi che non modifichi variabili globali del programma
oppure
Mascherare i segnali ogni volta che si usa una funzione unsafe nel programma (infattibile in generale)
printf
void alarmHandler(); // gestore
static int i=0; // contatore globale `volatile'
int main() {
signal(SIGALRM, alarmHandler);
alarm(1);
while(1){
printf("prova\n");
}
}
void alarmHandler()
{
printf("questo me lo gestisco io %d!\n",i++);
alarm(1); // ri-setta il timer a 1 secondo
}
NOTA: eseguire il comando con in coda | grep io
per evitare di osservare tutte le stampe “prova”.
Esercizio 1
Provare a mascherare il segnale nel main
prima e dopo la printf
.
Elimina le interferenze ma estremamente poco pratico perché richiede di proteggere tutte le occorrenze di printf
nel main
Esercizio 2
Utilizzare la write
(safe) per fare la stampa dentro il gestore e verificare che non ci sono interferenze. Questa soluzione non richiede nessuna modifica del main
, ma limita le funzioni utilizzabili nei gestori dei segnali solamente a quelle safe
NOTA: è necessario usare uno degli approcci sopra per evitare interferenze, usualmente il secondo perché più pratico!