Σήμα (υπολογιστές)

Στην επιστήμη της πληροφορικής, τα σήματα (signals) είναι μία περιορισμένη μορφή διαδιεργασιακής επικοινωνίας η οποία χρησιμοποιείται στο Unix, μα και σε άλλα λειτουργικά συστήματα τα οποία υπακούν στο πρότυπο POSIX. Πρόκειται για διακοπές λογισμικού που δρουν ως σινιάλα και μπορούν να αποστέλλονται σε μία διεργασία από κάποια άλλη ή από τον πυρήνα, αναγκάζοντάς την να τα χειριστεί ασύγχρονα μόλις τα λάβει και ακολούθως να επιστρέφει στην κανονική ροή εκτέλεσης. Κάθε σήμα διακρίνεται από έναν ακέραιο με τον οποίο είναι συσχετισμένο κάποιο συμβολικό όνομα (SIGxxxx).
Το μοντέλο είναι το εξής: ένα πρόγραμμα στον κώδικά του δηλώνει ότι μία συνάρτηση είναι χειριστής ενός συγκεκριμένου σήματος (εγκατάσταση χειριστή). Όταν ληφθεί το σήμα αυτό κατά την εκτέλεση της αντίστοιχης διεργασίας, αυτομάτως η τελευταία διακόπτει ό,τι έκανε (εντολή Α) και εκτελεί τον χειριστή. Μόλις ο χειριστής επιστρέψει ο έλεγχος δίνεται ξανά στην εντολή Α. Ένας χειριστής εγκαθίσταται με την κλήση συστήματος sigaction() (αν δίνεται η ειδική τιμή SIG_IGN ως όρισμα στη sigaction() αντί για όνομα συνάρτησης, τότε το αντίστοιχο σήμα αγνοείται από τη διεργασία), ενώ με τις κλήσεις kill() ή sigqueue() το τρέχον πρόγραμμα αποστέλλει ένα συγκεκριμένο σήμα σε μία συγκεκριμένη διεργασία (η διαφορά τους είναι ότι η sigqueue(), μαζί με το σήμα, αποστέλλει και έναν επιπλέον ακέραιο με δεδομένα του προγραμματιστή). Σχεδόν για κάθε σήμα υπάρχει κάποιος προεπιλεγμένος χειριστής ο οποίος παρέχεται από το πρότυπο POSIX και δεν χρειάζεται να γραφεί ή να εγκατασταθεί χειροκίνητα (συνήθως επιφέρει τον τερματισμό της διεργασίας ή αγνοεί τελείως το σήμα)· για κάποια συγκεκριμένα σήματα μάλιστα δεν μπορεί να υποσκελιστεί από χειριστή του προγραμματιστή.

Χειρισμός και αποστολή σημάτων

Συνήθως μία διεργασία μπορεί να αποστείλει σήματα μόνο σε άλλες διεργασίες του ίδιου χρήστη, εκτός κι αν ανήκει στον λογαριασμό του διαχειριστή του συστήματος οπότε μπορεί να αποστείλει σήματα σε οποιαδήποτε άλλη διεργασία. Αν μία διεργασία δεν εκτελείται όταν ο πυρήνας τής παραδίδει κάποιο σήμα (π.χ. αν εκείνη τη στιγμή εκτελείται κάποια άλλη σε συνθήκες πολυδιεργασίας), τότε το σήμα αποθηκεύεται και της παραδίδεται μόλις λάβει τον έλεγχο του επεξεργαστή. Σε ορισμένες περιπτώσεις όμως η αποστολή σήματος προκαλεί άμεση θεματική εναλλαγή και εκτέλεση της διεργασίας-παραλήπτη, ώστε ο χειρισμός του σήματος να γίνει με πολύ μικρή καθυστέρηση. Αν μία διεργασία αρχίσει να χειρίζεται κάποιο σήμα διακόπτοντας έτσι την εκτέλεση κάποιας κλήσης συστήματος, τότε η τελευταία αποτυγχάνει και επιστρέφει τη συμβολική τιμή EINTR μετά τον τερματισμό του χειριστή. Στα περισσότερα λειτουργικά συστήματα είναι δυνατόν με χρήση της συμβολικής τιμής SA_RESTART στα ορίσματα της sigaction() να προσδιοριστεί ότι οι κλήσεις συστήματος οι οποίες διακόπτονται από τον αντίστοιχο χειριστό κατά τον χρόνο εκτέλεσης, θα επανεκκινούνται αυτομάτως αντί να αποτυγχάνουν. Σε ορισμένα συστήματα αυτή είναι η προεπιλεγμένη συμπεριφορά.
Σημαντικό ζήτημα είναι η αναστολή παράδοσης σημάτων στην τρέχουσα διεργασία ώστε να μην μπορούν να εκτελεστούν οι αντίστοιχοι χειριστές σε χρονικές περιόδους που θα μπορούσαν να προξενήσουν προβλήματα. Αυτή η αναστολή ορίζεται από μία μάσκα μπλοκαρισμένων σημάτων η οποία τροποποιείται με την κλήση sigprocmask(), ενώ με την κλήση sigpending() μία διεργασία μπορεί κάθε στιγμή να ελέγξει ποια σήματα τής έχουν αποσταλεί αλλά δεν τα έχει παραλάβει επειδή τα έχει μπλοκάρει. Συνήθως είναι απαραίτητη η αναστολή παράδοσης όλων των σημάτων όσο εκτελείται ένας χειριστής ώστε να διασφαλιστεί η ομαλή λειτουργία του προγράμματος. Προκειμένου να μην καλείται χειροκίνητα η sigprocmask() μία φορά στην αρχή του χειριστή (όπου τίθεται η νέα μάσκα που περιλαμβάνει όλα τα σήματα) και μία φορά στο τέλος του (όπου επαναφέρεται η παλαιά μάσκα), παρέχεται η δυνατότητα ενσωμάτωσης της νέας μάσκας στον ίδιο τον χειριστή, μέσω κατάλληλων ορισμάτων της sigaction(), ώστε η αλλαγή της μάσκας να συμβαίνει αυτόματα μόλις ενεργοποιείται και μόλις επιστρέφει ο χειριστής. Τη στιγμή που αναιρείται η αναστολή παράδοσης ενός σήματος Α τότε, αν όντως εστάλη τουλάχιστον ένα σήμα Α κατά το χρονικό διάστημα της αναστολής στην τρέχουσα διεργασία, αμέσως παραδίδεται στην τελευταία ένα σήμα Α.
Το πρότυπο POSIX παρέχει επίσης τις κλήσεις pause() και sigwait(), οι οποίες μπλοκάρουν την καλούσα διεργασία επ' αόριστον μέχρι αυτή να λάβει κάποιο σήμα (οποιοδήποτε με την pause() ή κάποιο συγκεκριμένο με τη sigwait()), την κλήση sleep(), η οποία μοιάζει με την pause() αλλά μπλοκάρει την καλούσα διεργασία μόνο για πεπερασμένο μέγιστο χρονικό διάστημα το οποίο της δίνεται ως όρισμα, και την οικογένεια κλήσεων sigXXXset() που επενεργούν σε δομές δεδομένων οι οποίες αναπαριστούν μάσκες σημάτων, προσθαφαιρώντας συγκεκριμένα σήματα σε αυτές ή ελέγχοντας ποια σήματα περιλαμβάνουν. Η πρότυπη βιβλιοθήκη της C παρέχει εξειδικευμένες δομές δεδομένων οι οποίες αναπαριστούν μάσκες σημάτων αλλά και κάποια από τα ορίσματα της sigaction().

Παράδειγμα

Ακολουθεί ένα παράδειγμα χειρισμού σημάτων σε γλώσσα προγραμματισμού C:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <signal.h>void childcode(); /* Δήλωση της συνάρτησης του κώδικα τον οποίον εκτελεί η θυγατρική διεργασία */int main(int argc , char *argv[]){
int pid,sel;
pid=fork();/* Κατασκευή της θυγατρικής διεργασίας */
if (!pid)/* Αν τώρα εκτελείται η θυγατρική διεργασία... */
{childcode(); return;}
while (1)/* Κώδικας μητρικής διεργασίας */
{
printf("1. Send SIGUSR1 to child\n");
printf("2. Send SIGKILL to child\n");
scanf("%d",&sel);
if (sel==1)
kill(pid,SIGUSR1);
else if (sel==2)
{
kill(pid,SIGKILL);
wait(NULL);
printf("child was killed\n");
break;
}
}}/* Χειριστής του σήματος SIGUSR1 στη θυγατρική διεργασία */void sigusr1handler(int signo, siginfo_t *info, void *uc){
printf("received signal SIGUSR1 from process %ld\n", (long) info->si_pid);}void childcode(){
struct sigaction sa;
sigset_t s1,s2;
sigemptyset(&s2);
sa.sa_handler=sigusr1handler;
sa.sa_mask=s2;
sig_act.sa_flags = SA_SIGINFO; /* To SA_SIGINFO είναι μία προαιρετική δήλωση, ώστε κατά την εκτέλεσή του ο χειριστής να έχει πρόσβαση σε πληροφορίες για το σήμα που ελήφθη */
sigaction(SIGUSR1,&sa,NULL); /* Εγκατάσταση χειριστή για το σήμα SIGUSR1 */
sigemptyset(&s1);
sigaddset(&s1,SIGUSR1);/* Προσθήκη του σήματος SIGUSR1 σε μία νέα μάσκα σημάτων */

/* Με τον παρακάτω κώδικα η θυγατρική διεργασία περιμένει επαναληπτικά να λάβει το SIGUSR1 από τον γονέα της ώστε να εκτελέσει * τον χειριστή του, αλλά κατά διαστήματα μπλοκάρει τη λήψη του. Όταν τα διαστήματα αυτά λήγουν τυπώνει στην οθόνη αν της εστάλη * το SIGUSR1 όσο ήταν μπλοκαρισμένο. Αν της σταλεί το σήμα SIGKILL η διεργασία τερματίζεται αυτομάτως. */
while (1)
{
printf("SIGUSR1 is not blocked for 10s\n");
sleep(10); /* will wakeup upon signal arrival */
sigprocmask(SIG_BLOCK,&s1,NULL); /* Αναστολή παράδοσης του σήματος SIGUSR1 στην τρέχουσα διεργασία */
printf("SIGUSR1 is blocked for 10s\n");
sleep(10); /* Η διεργασία δεν θα αφυπνιστεί κατά την άφιξη ενός σήματος SIGUSR1, αφού το έχει μπλοκάρει */
sigpending(&s2);
if (sigismember(&s2,SIGUSR1))
printf("SIGUSR1 has been sent while blocked\n");
sigprocmask(SIG_UNBLOCK,&s1,NULL); /* Αναίρεση αναστολής παράδοσης του σήματος SIGUSR1 στην τρέχουσα διεργασία */
}}