Συγχρονισμός & σηματοφόροι Προγραμματισμός II 1 lalis@inf.uth.gr
Ταυτόχρονη εκτέλεση Οι διεργασίες εκτελούνται ταυτόχρονα μεταξύ τους Ο προγραμματιστής δεν ελέγχει την εναλλαγή Τι γίνεται αν δύο ή περισσότερες διεργασίες πρέπει να συγχρονιστούν μεταξύ τους; Ο επιθυμητός συγχρονισμός μπορεί να επιτευχθεί μέσω των διαθέσιμων μηχανισμών επικοινωνίας Όμως, σε κάποιες περιπτώσεις οι λύσεις είναι είτε άκομψες είτε ιδιαίτερα πολύπλοκες Προγραμματισμός II 2 lalis@inf.uth.gr
Κλασικά προβλήματα συγχρονισμού Αναμονή σε συνθήκη: μια διεργασία πρέπει να περιμένει να ικανοποιηθεί (από μια άλλη διεργασία) μια συγκεκριμένη συνθήκη ώστε να συνεχίσει την εκτέλεση της π.χ., ραντεβού (συμμετρικό ή ασύμμετρο) Αμοιβαίος αποκλεισμός: οι διεργασίες επιθυμούν να προσπελάσουν έναν ή περισσότερους κοινούς πόρους, χωρίς όμως η μια διεργασία «να μπλέκεται στα πόδια» της άλλης π.χ., αρχεία ή κοινές μεταβλητές (κοινόχρηστη μνήμη) Προγραμματισμός II 3 lalis@inf.uth.gr
Σηματοφόρος / Semaphore Ειδικός τύπος ακέραιου, με ειδικές λειτουργίες init(sem *s, int val) αρχικοποιεί τον s με την τιμή val >=0 void down(sem *s) επιχειρεί να μειώσει την τιμή του s κατά 1 αν s είναι 0, η διεργασία μπλοκάρεται void up(sem *s) αν υπάρχει μπλοκαρισμένη διεργασία, αυτή αφυπνίζεται διαφορετικά, αυξάνει την τιμή του s κατά 1 Οι down/up είναι ασφαλείς υπό ταυτόχρονη εκτέλεση Προγραμματισμός II 4 lalis@inf.uth.gr
Τύποι σηματοφόρων Δυαδικός σηματοφόρος (binary semaphore) μπορεί να λάβει τιμές 0 ή 1 Γενικός σηματοφόρος ή σηματοφόρος μετρητής (general semaphore / counting semaphore) μπορεί να λάβει τιμές >= 0 Οι δυαδικοί σηματοφόροι μπορεί να υλοποιηθούν με πιο απλό τρόπο (και δεν τίθεται θέμα overflow) Οι γενικοί σηματοφόροι μπορεί να χρησιμοποιηθούν για να λύσουν κάποια προβλήματα συγχρονισμού με πιο απλό τρόπο Προγραμματισμός II 5 lalis@inf.uth.gr
Συμβολική(!) υλοποίηση typedef struct { int val; /* τιμή σηματοφόρου */ QUEUE q; /* ουρά αναμονής */ } gsem; void init(gsem *s, int val) { s->val = val; Q_INIT(&s->q); } void down(gsem *s) { ATOMIC_BEG if (s->val >= 1) { s->val--; } else { Q_SUSPEND(&s->q); } ATOMIC_END } void up(gsem *s) { ATOMIC_BEG if (Q_LEN(&s->q) == 0) { s->val++; } else { Q_WAKEUP(&s->q); } ATOMIC_END } Προγραμματισμός II 6 lalis@inf.uth.gr
Ραντεβού με σηματοφόρους Έστω 2 διεργασίες, P1 και P2, όπου η P2 πρέπει να περιμένει στο σημείο Β του κώδικα της μέχρι να φτάσει η P1 στο σημείο Α του κώδικα της (αν δεν έχει ήδη περάσει από αυτό το σημείο) Οι διεργασίες μπορεί να χρησιμοποιήσουν έναν κοινό δυαδικό σηματοφόρο Πριν αρχίσει η «ανταγωνιστική» εκτέλεση των P1 και P2, o σηματοφόρος αρχικοποιείται σε 0 Όταν η P2 φτάσει στο Β, μειώνει τον σηματοφόρο Όταν η P1 φτάσει στο Α, αυξάνει τον σηματοφόρο Προγραμματισμός II 7 lalis@inf.uth.gr
sem s; init(&s,0); P1 (Α) up(&s); P2 (Β) down(&s); Προγραμματισμός II 8 lalis@inf.uth.gr
Αμοιβαίος αποκλεισμός με σηματοφόρους Έστω δύο διεργασίες, P1 και P2, με «κρίσιμα τμήματα» κώδικα, CS1 και CS2, που πρέπει να εκτελεστούν υπό αμοιβαίο αποκλεισμό Οι διεργασίες μπορεί να χρησιμοποιήσουν έναν κοινό δυαδικό σηματοφόρο Πριν αρχίσει η «ανταγωνιστική» εκτέλεση των P1 και P2, o σηματοφόρος αρχικοποιείται σε 1 Κάθε διεργασία, προτού αρχίσει την εκτέλεση του κρίσιμου τμήματος, μειώνει τον σηματοφόρο Κάθε διεργασία, αφού ολοκληρώσει την εκτέλεση του κρίσιμου τμήματος, αυξάνει τον σηματοφόρο Προγραμματισμός II 9 lalis@inf.uth.gr
sem s; init(&s,1); P1 down(&s); CS1 up(&s); P2 down(&s); CS2 up(&s); Προγραμματισμός II 10 lalis@inf.uth.gr
Σηματοφόροι στο System V Προγραμματισμός II 11 lalis@inf.uth.gr
Διαφορές με το «θεωρητικό μοντέλο» Η βασική ιδέα («θεωρητικό» μοντέλο) είναι η ίδια Υποστηρίζονται γενικοί σηματοφόροι Παρέχεται επιπλέον λειτουργικότητα! Οι λειτουργίες πρόσβασης αφορούν μια ομάδα σηματοφόρων (όχι μόνο έναν σηματοφόρο) Μπορεί να αυξομειωθούν πολλοί σηματοφόροι μέσα από μια μοναδική κλήση συστήματος, «ατομικά», χωρίς να παρεμβληθούν εναλλαγές Προγραμματισμός II 12 lalis@inf.uth.gr
Δημιουργία/ανεύρεση ομάδας σηματοφόρων int semget(key_t key, int n, int flags); Δημιουργεί/βρίσκει μια ομάδα n σηματοφόρων με κλειδί key, και επιστρέφει το αναγνωριστικό της Βασικές επιλογές flags IPC_CREAT: δημιουργεί την ομάδα σηματοφόρων IPC_EXCL: αποτυχία αν η ομάδα σηματοφόρων υπάρχει άδειες πρόσβασης/χρήσης δίνονται όπως στην open Αν key είναι ίσο με IPC_PRIVATE, δημιουργείται μια «ανώνυμη», ιδιωτική ομάδα σηματοφόρων δεν μπορεί να βρεθεί μέσω semget Δεν αρχικοποιούνται (απαραίτητα) οι τιμές των σηματοφόρων αυτό πρέπει να γίνει με την semctl Προγραμματισμός II 13 lalis@inf.uth.gr
Έλεγχος ομάδας σηματοφόρων int semctl(int semid, int semnr, int cmd,); H cmd προσδιορίζει την λειτουργία, από την οποία εξαρτάται αν απαιτείται 4η παράμετρος (επόμενο slide) SETVAL: ανάθεση τιμής στον σηματοφόρο semnr SETALL: ανάθεση τιμών σε όλους τους σηματοφόρους GETVAL: λήψη τιμής του σηματοφόρου semnr GETALL: λήψη τιμών όλων των σηματοφόρων GETCNT: λήψη πλήθους διεργασιών που έχουν μπλοκαριστεί στον σηματοφόρο semnr GETZCNT: πλήθους διεργασιών που έχουν μπλοκαριστεί περιμένοντας ο σηματοφόρος semnr να γίνει 0 IPC_RMID: καταστροφή ομάδας σηματοφόρων Προγραμματισμός II 14 lalis@inf.uth.gr
Δομή union semum union semun { int val; struct semid_ds *info; unsigned short *vals; }; To val χρησιμοποιείται στην SETVAL Το vals χρησιμοποιείται στην SETALL/GETALL To info χρησιμοποιείται για την λήψη επιπλέον δεδομένων (με IPCSTAT), π.χ., πλήθος σηματοφόρων της ομάδας, άδειες χρήσης, Η παραπάνω δομή δεν ορίζεται τυπικά μπορεί να περαστούν απευθείας τιμές/μεταβλητές που είναι συμβατές με τα επιμέρους πεδία της δομής Προγραμματισμός II 15 lalis@inf.uth.gr
Πράξεις σε ομάδα σηματοφόρων semop(int semid, struct sembuf *sops, size_t nsops); Εκτελεί τις πράξεις που βρίσκονται στον πίνακα sops το μήκος του οποίου καθορίζεται από την nsops Κάθε στοιχείο του πίνακα sops καθορίζει μια πράξη αύξησης/μείωσης ενός συγκεκριμένου σηματοφόρου ή αναμονή μέχρι αυτός να μηδενιστεί Οι πράξεις εκτελούνται ατομικά: η semop μπλοκάρει μέχρι να εκτελεστούν όλες οι αιτούμενες πράξεις μαζί Προγραμματισμός II 16 lalis@inf.uth.gr
Η δομή struct sembuf struct sembuf { unsigned short sem_num; short sem_op; short sem_flg; }; Το πεδίο sem_num προσδιορίζει τον σηματοφόρο Το πεδίο sem_op προσδιορίζει την πράξη >0: η τιμή προστίθεται στον σηματοφόρο (up) <0: η απόλυτη τιμή αφαιρείται από τον σηματοφόρο (down) 0 : η διεργασία μπλοκάρεται μέχρι ο σηματοφόρος να γίνει 0 Το πεδίο sem_flg προσδιορίζει χαρακτηριστικά IPC_NOWAIT: σε περίπτωση που η κλήση θα μπλόκαρε την διεργασία, άμεση επιστροφή, χωρίς εκτέλεση καμίας πράξης SEM_UNDO: αναίρεση της πράξης όταν τερματιστεί η διεργασία Προγραμματισμός II 17 lalis@inf.uth.gr
int main(int argc, char *argv[]) { int semid; struct sembuf op; semid = semget(ipc_private,1,s_irwxu); semctl(semid,0,setval,0); // init sem to 0 if (!fork()) { printf("this should print first\n"); op.sem_num = 0; op.sem_op = 1; op.sem_flg = 0; semop(semid,&op,1); // wakeup other party } return(0); op.sem_num = 0; op.sem_op = -1; op.sem_flg = 0; semop(semid,&op,1); // wait for other party printf("this should print second\n"); } semctl(semid,0,ipc_rmid); return(0); Προγραμματισμός ΙΙΙ 18 lalis@inf.uth.gr
int main(int argc, char *argv[]) { int semid,i,n,pid; struct sembuf op; n = atoi(argv[1]); semid = semget(ipc_private,1,s_irwxu); semctl(semid,0,setval,1); // init to 1 pid=fork(); for (i=0; i<n; i++) { op.sem_num = 0; op.sem_op = -1; op.sem_flg = 0; semop(semid,&op,1); printf("%d: got mutual exclusion %d\n",getpid(),i); } op.sem_num = 0; op.sem_op = 1; op.sem_flg = 0; semop(semid,&op,1); if (pid!= 0) { waitpid(pid,null,0); semctl(semid,0,ipc_rmid); } } return(0); Προγραμματισμός ΙΙΙ 19 lalis@inf.uth.gr
int main(int argc, char *argv[]) { int semid; struct sembuf op[2]; semid = semget(ipc_private,2,s_irwxu); semctl(semid,0,setval,0); semctl(semid,1,setval,0); if (!fork()) { op[0].sem_num = 0; op[0].sem_op = 1; op[0].sem_flg = 0; semop(semid,op,1); // inc first semaphore sleep(3); semop(semid,op,1); // inc first semaphore sleep(3); op[0].sem_num = 1; op[0].sem_op = 1; op[0].sem_flg = 0; semop(semid,op,1); // inc second semaphore return(0); } } op[0].sem_num = 0; op[0].sem_op = -2; op[0].sem_flg = 0; op[1].sem_num = 1; op[1].sem_op = -1; op[1].sem_flg = 0; semop(semid,op,2); // dec both semaphores 2 and 1 time printf ("parent: done\n"); semctl(semid,0,ipc_rmid); return(0); Προγραμματισμός ΙΙΙ 20 lalis@inf.uth.gr