You are on page 1of 145

ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ

Παναγιώτα Φατούρου
Τµήµα Πληροφορικής, Πανεπιστήµιο Ιωαννίνων

Παύλος Σπυράκης
Τµήµα Μηχανικών Η/Υ και Πληροφορικής, Πανεπιστήµιο Πατρών
&
Ινστιτούτο Τεχνολογίας Υπολογιστών

Σεπτέµβριος 2007
ΑΚΟΥΓΕ ΟΤΑΝ ΜΙΛΑΣ
Μη λες πολύ συχνά ότι έχεις δίκιο δάσκαλε!
Άσε να το δουν κι οι µαθητές!
Μην πιέζεις πολύ την αλήθεια,
∆εν το αντέχει.
Άκουγε όταν µιλάς!

Μπέρτολτ Μπρεχτ
από τη συλλογή «76 Ποιήµατα»
Πίνακας Περιεχοµένων
1 ΕΙΣΑΓΩΓΗ..................................................................................................................................... 9
1.1 ΣΎΝΤΟΜΗ ΑΝΑΣΚΌΠΗΣΗ ΥΛΙΚΟΎ ΥΠΟΛΟΓΙΣΤΏΝ .................................................................... 10
1.2 ΣΚΟΠΌΣ & ΥΠΗΡΕΣΊΕΣ ΕΝΌΣ ΛΣ................................................................................................ 13
1.3 ΠΟΛΥΠΡΟΓΡΑΜΜΑΤΙΣΜΌΣ ΚΑΙ ∆ΙΑΜΟΙΡΑΣΜΌΣ ΧΡΌΝΟΥ .......................................................... 14
1.4 ΚΑΤΑΣΤΆΣΕΙΣ ΛΕΙΤΟΥΡΓΊΑΣ ΣΥΣΤΗΜΆΤΩΝ ............................................................................... 16
1.5 ΤΑ ΚΎΡΙΑ ΜΈΡΗ ΕΝΌΣ ΛΕΙΤΟΥΡΓΙΚΟΎ ΣΥΣΤΉΜΑΤΟΣ ............................................................... 17
1.6 ΚΑΤΑΝΕΜΗΜΈΝΑ ΣΥΣΤΉΜΑΤΑ & ΣΥΣΤΉΜΑΤΑ ΠΡΑΓΜΑΤΙΚΟΎ ΧΡΌΝΟΥ ................................. 19
1.7 ΓΙΑ ΠΕΡΙΣΣΌΤΕΡΗ ΜΕΛΈΤΗ ........................................................................................................ 20
1.8 ΒΙΒΛΙΟΓΡΑΦΙΚΈΣ ΑΝΑΦΟΡΈΣ ..................................................................................................... 22
2 ∆ΙΕΡΓΑΣΙΕΣ – ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΣΗ ∆ΙΕΡΓΑΣΙΩΝ.................................................... 23
2.1 ∆ΙΕΡΓΑΣΊΕΣ ................................................................................................................................ 24
2.2 ΚΑΤΑΣΤΆΣΕΙΣ ∆ΙΕΡΓΑΣΙΏΝ ......................................................................................................... 24
2.3 ΤΟ ΜΠΛΟΚ ΕΛΈΓΧΟΥ ∆ΙΕΡΓΑΣΙΏΝ ............................................................................................ 25
2.4 ΛΕΙΤΟΥΡΓΊΕΣ ΕΠΊ ∆ΙΕΡΓΑΣΙΏΝ.................................................................................................... 26
2.5 ∆ΙΑΚΟΠΈΣ .................................................................................................................................. 27
2.6 ΧΡΟΝΟ∆ΡΟΜΟΛΌΓΗΣΗ ............................................................................................................... 32
2.7 ΑΛΓΌΡΙΘΜΟΙ ΧΡΟΝΟ∆ΡΟΜΟΛΌΓΗΣΗΣ ........................................................................................ 36
2.7.1 Πρώτη Εισερχόµενη – Πρώτη Εξυπηρετούµενη (First Come – First Served, FCFS)............ 36
2.7.2 Εκ Περιτροπής (Round Robin, RR) ....................................................................................... 37
2.7.3 Προτεραιοτήτων (Priority).................................................................................................... 38
2.7.4 Θεωρητική Μελέτη Απόδοσης Χρονοδροµολογητών ............................................................. 39
2.8 ΒΙΒΛΙΟΓΡΑΦΙΚΈΣ ΑΝΑΦΟΡΈΣ ..................................................................................................... 50
3 ∆ΙΑ∆ΙΕΡΓΑΣΙΑΚΗ ΕΠΙΚΟΙΝΩΝΙΑ & ΣΥΓΧΡΟΝΙΣΜΟΣ.................................................. 51
3.1 ΤΑΥΤΌΧΡΟΝΗ ΕΚΤΈΛΕΣΗ ∆ΙΕΡΓΑΣΙΏΝ ....................................................................................... 52
3.2 Η ΑΝΆΓΚΗ ΣΥΓΧΡΟΝΙΣΜΟΎ ....................................................................................................... 62
3.3 ΤΟ ΠΡΌΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΊΟΥ ΑΠΟΚΛΕΙΣΜΟΎ...................................................................... 66
3.4 ΛΎΣΕΙΣ ΜΕ ΤΗ ΒΟΉΘΕΙΑ ΤΟΥ ΥΛΙΚΟΎ ....................................................................................... 69
3.5 ΛΎΣΕΙΣ ΜΕ ΧΡΉΣΗ ΛΟΓΙΣΜΙΚΟΎ ΜΌΝΟ ..................................................................................... 76
3.5.1 Πρώτες Προσπάθειες προς την λύση ..................................................................................... 76
3.5.2 Λύση του Peterson................................................................................................................. 83
3.6 ΣΗΜΑΦΌΡΟΙ ............................................................................................................................... 92
3.7 ΠΡΟΒΛΉΜΑΤΑ ∆ΙΑ∆ΙΕΡΓΑΣΙΑΚΉΣ ΕΠΙΚΟΙΝΩΝΊΑΣ ..................................................................... 98
3.7.1 Για Προθέρµανση................................................................................................................. 98
3.7.2 Το Πρόβληµα του Ζωολογικού Κήπου (Θέµα 6, Α’ Τελική Εξέταση, Ιούνιος 2003)............ 106
3.7.3 Το Πρόβληµα του Κουρέα (Θέµα 3Β, Α’ Τελική Εξέταση, Μάιος 2001) ............................. 110
3.7.4 Το Πρόβληµα των Αναγνωστών-Εγγραφέων....................................................................... 112
3.7.5 Για περισσότερη εξάσκηση .................................................................................................. 114
3.7.6 Γενικές Παρατηρήσεις......................................................................................................... 118
3.8 ΚΡΊΣΙΜΕΣ ΠΕΡΙΟΧΈΣ ΚΑΙ ΚΡΊΣΙΜΕΣ ΠΕΡΙΟΧΈΣ ΥΠΌ ΣΥΝΘΉΚΗ ............................................... 118
3.9 ΛΊΓΟ ΠΙΟ ∆ΎΣΚΟΛΑ ................................................................................................................. 126
3.9.1 Λύση του Lamport (Bakery Algorithm) ............................................................................... 126
3.9.2 Λύση του Dekker ................................................................................................................. 128
3.9.3 ∆ικαιοσύνη.......................................................................................................................... 129
3.9.4 Προσοµοιώσεις ................................................................................................................... 132
3.10 ΒΙΒΛΙΟΓΡΑΦΙΚΈΣ ΑΝΑΦΟΡΈΣ ................................................................................................... 136
ΠΑΡΑΡΤΗΜΑ Ι ΜΟΡΦΗ ΨΕΥ∆ΟΚΩ∆ΙΚΑ ΠΕΡΙΓΡΑΦΗΣ ΠΡΟΓΡΑΜΜΑΤΟΣ ∆ΙΕΡΓΑΣΙΑΣ137
3.10.1 Πρόταση ∆ήλωσης Μεταβλητών..................................................................................... 137
3.10.2 Πρόταση Καταχώρησης .................................................................................................. 137
3.10.3 Βρόγχοι........................................................................................................................... 138
3.10.4 Προτάσεις Ελέγχου......................................................................................................... 140
3.10.5 Πρόταση goto ................................................................................................................. 141
3.10.6 Πίνακες και ∆οµές.......................................................................................................... 141
3.10.7 Typedef........................................................................................................................... 142
3.10.8 Συναρτήσεις.................................................................................................................... 142
3.10.9 Άλλα Θέµατα .................................................................................................................. 142
ΒΙΒΛΙΟΓΡΑΦΙΑ ...................................................................................................................................... 143
Πίνακας Σχηµάτων
ΣΧΗΜΑ 1: ΒΑΣΙΚΗ ΟΡΓΑΝΩΣΗ ΕΝΟΣ ΥΠΟΛΟΓΙΣΤΗ......................................................................................... 10
ΣΧΗΜΑ 2: ΣΧΕΣΗ ΤΟΥ ΛΣ ΜΕ ΤΟ ΥΛΙΚΟ ΤΟΥ ΥΠΟΛΟΓΙΣΤΗ ΚΑΙ ΤΟΥΣ ΧΡΗΣΤΕΣ............................................. 14
ΣΧΗΜΑ 3: ΓΡΑΦΗΜΑ ΚΑΤΑΣΤΑΣΕΩΝ ∆ΙΕΡΓΑΣΙΑΣ. ......................................................................................... 25
ΣΧΗΜΑ 4: ΤΟ PCB ΜΙΑΣ ∆ΙΕΡΓΑΣΙΑΣ............................................................................................................. 26
ΣΧΗΜΑ 5: Η ΟΥΡΑ ΕΤΟΙΜΩΝ ∆ΙΕΡΓΑΣΙΩΝ. ..................................................................................................... 33
ΣΧΗΜΑ 6: ΣΧΗΜΑΤΙΚΗ ΠΕΡΙΓΡΑΦΗ ΤΗΣ ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΣΗΣ ∆ΙΕΡΓΑΣΙΩΝ. .............................................. 34
ΣΧΗΜΑ 7: ΕΙ∆ΟΣ ∆ΡΟΜΟΛΟΓΗΣΗΣ ΠΟΥ ΠΑΡΕΧΕΤΑΙ ΑΠΟ ΤΟΥΣ ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΤΕΣ ΚΜΕ ΚΑΙ ΜΝΗΜΗΣ-
∆ΙΣΚΟΥ. ............................................................................................................................................... 35
ΣΧΗΜΑ 8: ΠΙΝΑΚΑΣ ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΣΗΣ ΓΙΑ ΤΟΝ ΑΛΓΟΡΙΘΜΟ RR......................................................... 45
ΣΧΗΜΑ 9: ∆ΙΑΓΡΑΜΜΑΤΑ GANNT ΓΙΑ ΤΟΥΣ ΑΛΓΟΡΙΘΜΟΥΣ ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΣΗΣ ΤΟΥ ΠΑΡΑ∆ΕΙΓΜΑΤΟΣ 6.
............................................................................................................................................................ 47
ΣΧΗΜΑ 10: 1Ο ΠΑΡΑ∆ΕΙΓΜΑ ∆ΥΝΑΤΗΣ ΤΑΥΤΟΧΡΟΝΗΣ ΕΚΤΕΛΕΣΗΣ ............................................................. 54
ΣΧΗΜΑ 11: 1Ο ΠΑΡΑ∆ΕΙΓΜΑ ∆ΥΝΑΤΗΣ ΤΑΥΤΟΧΡΟΝΗΣ ΕΚΤΕΛΕΣΗΣ: ΠΙΟ ΕΥΑΝΑΓΝΩΣΤΗ ΠΑΡΟΥΣΙΑΣΗ ...... 55
ΣΧΗΜΑ 12: 2Ο ΠΑΡΑ∆ΕΙΓΜΑ ∆ΥΝΑΤΗΣ ΤΑΥΤΟΧΡΟΝΗΣ ΕΚΤΕΛΕΣΗΣ ............................................................. 56
ΣΧΗΜΑ 13: ΚΩ∆ΙΚΕΣ ΠΟΥ ΕΚΤΕΛΟΥΝ ΟΙ ∆ΙΕΡΓΑΣΙΕΣ Α ΚΑΙ Β....................................................................... 57
ΣΧΗΜΑ 14: ΣΕΝΑΡΙΟ ΠΟΥ Ο∆ΗΓΕΙ ΣΤΗΝ ΜΙΚΡΟΤΕΡΗ ΤΙΜΗ ΓΙΑ ΤΗ ∆ΙΑΜΟΙΡΑΖΟΜΕΝΗ ΜΕΤΑΒΛΗΤΗ TALLY. .. 60
ΣΧΗΜΑ 15: ΟΥΡΑ ΣΤΗΝ ΟΠΟΙΑ ΚΑΤΑΓΡΑΦΟΝΤΑΙ ΠΛΗΡΟΦΟΡΙΕΣ ΓΙΑ ΤΑ ΠΡΟΣ ΕΚΤΥΠΩΣΗ ΑΡΧΕΙΑ................. 63
ΣΧΗΜΑ 16: ΣΕΝΑΡΙΟ ΠΟΥ Ο∆ΗΓΕΙ ΣΕ ΑΝΑΛΗΨΗ 1000€ ΑΠΟ ΕΝΑ ΛΟΓΑΡΙΑΣΜΟ ΠΟΥ ΠΕΡΙΕΧΕΙ ΜΟΝΟ 500€... 65
ΣΧΗΜΑ 17: ΕΝΑΛΛΑΚΤΙΚΕΣ ΙΣΟ∆ΥΝΑΜΕΣ ΓΕΝΙΚΕΣ ΜΟΡΦΕΣ ΚΩ∆ΙΚΑ ∆ΙΕΡΓΑΣΙΩΝ. ....................................... 68
ΣΧΗΜΑ 18: ∆ΙΑΚΟΠΕΣ ΕΝΟΣΩ ΜΙΑ ∆ΙΕΡΓΑΣΙΑ ΕΚΤΕΛΕΙ ΤΟ ΚΡΙΣΙΜΟ ΤΜΗΜΑ ΤΗΣ. ......................................... 68
ΣΧΗΜΑ 19: Η ΕΝΤΟΛΗ TEST&SET().............................................................................................................. 69
ΣΧΗΜΑ 20: ΛΥΣΗ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΜΕ ΧΡΗΣΗ ΤΗΣ ΕΝΤΟΛΗΣ TEST&SET().. 69
ΣΧΗΜΑ 21: ΚΩ∆ΙΚΑΣ ΠΟΥ ΕΚΤΕΛΕΙΤΑΙ ΣΤΗΝ ΠΡΑΞΗ ΑΠΟ ∆ΥΟ ∆ΙΕΡΓΑΣΙΕΣ 0 ΚΑΙ 1 ΟΤΑΝ Η TEST&SET()
ΧΡΗΣΙΜΟΠΟΙΕΙΤΑΙ ΓΙΑ ΕΠΙΤΕΥΞΗ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ.............................................................. 70
ΣΧΗΜΑ 22: ΕΚΤΕΛΕΣΗ ΠΟΥ ΠΑΡΑΒΙΑΖΕΙ ΤΗ ΣΥΝΘΗΚΗ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΑΝ Η TEST&SET() ∆ΕΝ
ΕΚΤΕΛΕΙΤΑΙ ΑΤΟΜΙΚΑ.......................................................................................................................... 71
ΣΧΗΜΑ 23: ΑΤΟΜΙΚΗ ΕΚ∆ΟΣΗ ΤΗΣ DEPOSIT()............................................................................................... 72
ΣΧΗΜΑ 24: ΑΤΟΜΙΚΗ ΕΚ∆ΟΣΗ ΤΗΣ WITHDRAW().......................................................................................... 72
ΣΧΗΜΑ 25: ΕΚΤΕΛΕΣΗ ΠΟΥ Ο∆ΗΓΕΙ ΣΕ ΠΑΡΑΤΕΤΑΜΕΝΗ ΣΤΕΡΗΣΗ ΓΙΑ ΤΗ ∆ΙΕΡΓΑΣΙΑ 1 ΣΤΗ ΛΥΣΗ ΠΟΥ
ΧΡΗΣΙΜΟΠΟΙΕΙ ΤΗΝ TEST&SET(). ........................................................................................................ 73
ΣΧΗΜΑ 26: Η ΕΝΤΟΛΗ FETCH&ADD(). ......................................................................................................... 74
ΣΧΗΜΑ 27: Η ΕΝΤΟΛΗ SWAP()...................................................................................................................... 75
ΣΧΗΜΑ 28: Η ΕΝΤΟΛΗ RMW(). .................................................................................................................... 75
ΣΧΗΜΑ 29: ΠΡΩΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ
ΑΠΟΚΛΕΙΣΜΟΥ..................................................................................................................................... 76
ΣΧΗΜΑ 30: ΣΕΝΑΡΙΟ ΠΟΥ ΚΑΤΑΣΤΡΑΤΗΓΕΙ ΤΗ ΣΥΝΘΗΚΗ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ΤΗΝ 1Η
ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ.......................................................................................................................... 77
ΣΧΗΜΑ 31: ∆ΕΥΤΕΡΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ
ΑΠΟΚΛΕΙΣΜΟΥ..................................................................................................................................... 78
ΣΧΗΜΑ 32: ΣΕΝΑΡΙΟ ΠΟΥ ΚΑΤΑΣΤΡΑΤΗΓΕΙ ΤΗ ΣΥΝΘΗΚΗ ΤΗΣ ΠΡΟΟ∆ΟΥ ΓΙΑ ΤΗ 2Η ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ. ... 78
ΣΧΗΜΑ 33: ΤΡΙΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ (ΛΥΣΗ ΤΗΣ ΑΥΣΤΗΡΗΣ ΕΝΑΛΛΑΓΗΣ) ΣΤΟ
ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ. ...................................................................................... 79
ΣΧΗΜΑ 34: ΣΕΝΑΡΙΟ ΠΟΥ ΚΑΤΑΣΤΡΑΤΗΓΕΙ ΤΗ ΣΥΝΘΗΚΗ ΤΗΣ ΠΡΟΟ∆ΟΥ ΓΙΑ ΤΗΝ 3Η ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ.. 80
ΣΧΗΜΑ 35: ΤΕΤΑΡΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ
ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ∆ΥΟ ∆ΙΕΡΓΑΣΙΕΣ..................................................................................................... 80
ΣΧΗΜΑ 36: Η ΛΥΣΗ ΤΟΥ PETERSON. ............................................................................................................. 83
ΣΧΗΜΑ 37: ΠΕΜΠΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ
ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ∆ΥΟ ∆ΙΕΡΓΑΣΙΕΣ..................................................................................................... 85
ΣΧΗΜΑ 38: ΈΚΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ
ΑΠΟΚΛΕΙΣΜΟΥ..................................................................................................................................... 86
ΣΧΗΜΑ 39: ΣΕΝΑΡΙΟ ΠΟΥ ΚΑΤΑΣΤΡΑΤΗΓΕΙ ΤΗ ΣΥΝΘΗΚΗ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ΤΗΝ 5Η
ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ.......................................................................................................................... 88
ΣΧΗΜΑ 40: ΕΛΑΦΡΩΣ ΤΡΟΠΟΠΟΙΗΜΕΝΗ ΕΚ∆ΟΣΗ ΤΗΣ ΛΥΣΗΣ ΤΟΥ PETERSON............................................... 89
ΣΧΗΜΑ 41: ΈΝΑ ΣΕΝΑΡΙΟ ΠΟΥ Ο∆ΗΓΕΙ ΣΕ ΠΑΡΑΤΕΤΑΜΕΝΗ ΣΤΕΡΗΣΗ ΤΗΝ ΕΛΑΦΡΩΣ ΤΡΟΠΟΠΟΙΗΜΕΝΗ
ΕΚ∆ΟΣΗ ΤΗΣ ΛΥΣΗΣ ΤΟΥ PETERSON..................................................................................................... 89
ΣΧΗΜΑ 42: ΈΒ∆ΟΜΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ
ΑΠΟΚΛΕΙΣΜΟΥ..................................................................................................................................... 90
ΣΧΗΜΑ 43: ΌΓ∆ΟΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ
ΑΠΟΚΛΕΙΣΜΟΥ..................................................................................................................................... 91
ΣΧΗΜΑ 44: ΠΙΟ ΑΝΑΛΥΤΙΚΗ ΠΕΡΙΓΡΑΦΗ ΤΟΥ ΚΩ∆ΙΚΑ ΤΩΝ ∆ΙΕΡΓΑΣΙΩΝ ΤΗΣ ΟΓ∆ΟΗΣ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗΣ.
............................................................................................................................................................ 91
ΣΧΗΜΑ 45: ΈΝΑΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ∆ΥΟ
∆ΙΕΡΓΑΣΙΕΣ........................................................................................................................................... 92
ΣΧΗΜΑ 46: ΥΛΟΠΟΙΗΣΗ ΛΕΙΤΟΥΡΓΙΩΝ UP() ΚΑΙ DOWN() ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ......................................... 93
ΣΧΗΜΑ 47: ΛΥΣΗ ΠΡΟΒΛΗΜΑΤΟΣ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΜΕ ΣΗΜΑΦΟΡΟΥΣ. ..................................... 93
ΣΧΗΜΑ 48: ΈΝΑ ΚΟΙΝΟ ΛΑΘΟΣ ΣΤΗ ΧΡΗΣΗ ΣΗΜΑΦΟΡΩΝ.............................................................................. 94
ΣΧΗΜΑ 49: ΈΝΑ ΑΚΟΜΗ ΚΟΙΝΟ ΛΑΘΟΣ ΣΤΗ ΧΡΗΣΗ ΣΗΜΑΦΟΡΩΝ. ................................................................ 95
ΣΧΗΜΑ 50: ΥΛΟΠΟΙΗΣΗ ΣΗΜΑΦΟΡΩΝ ΧΡΗΣΙΜΟΠΟΙΩΝΤΑΣ ΑΚΕΡΑΙΕΣ ∆ΙΑΜΟΙΡΑΖΟΜΕΝΕΣ ΜΕΤΑΒΛΗΤΕΣ ΚΑΙ
ΤΗΝ ΑΤΟΜΙΚΗ ΛΕΙΤΟΥΡΓΙΑ TEST&SET() ΠΟΥ ΠΑΡΕΧΕΤΑΙ ΑΠΟ ΤΟ ΥΛΙΚΟ............................................ 97
ΣΧΗΜΑ 51: ΠΕΡΙΓΡΑΦΗ ΕΝΕΡΓΕΙΩΝ ΠΟΥ ΕΚΤΕΛΟΥΝ ΟΙ ∆ΙΕΡΓΑΣΙΕΣ ΠΕΛΑΤΩΝ ΚΑΙ ΕΞΥΠΗΡΕΤΗ. ................. 100
ΣΧΗΜΑ 52: ΜΙΑ ΠΡΩΤΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΠΕΛΑΤΩΝ-ΕΞΥΠΗΡΕΤΗ. ...................................................... 102
ΣΧΗΜΑ 53: ΜΙΑ ΒΕΛΤΙΣΤΟΠΟΙΗΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΠΕΛΑΤΩΝ-ΕΞΥΠΗΡΕΤΗ. ................................ 103
ΣΧΗΜΑ 54: ΜΙΑ ΛΑΘΟΣ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΠΕΛΑΤΩΝ-ΕΞΥΠΗΡΕΤΗ....................................................... 103
ΣΧΗΜΑ 55: ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΩΝ ΚΑΠΝΙΣΤΩΝ-ΠΩΛΗΤΗ. ................................................................... 105
ΣΧΉΜΑ 56: PING-PONG............................................................................................................................... 106
ΣΧΗΜΑ 57: ΠΕΡΙΓΡΑΦΗ ΑΠΛΩΝ ΕΝΕΡΓΕΙΩΝ ΠΟΥ ΕΚΤΕΛΟΥΝ ΟΙ ∆ΙΕΡΓΑΣΙΕΣ ΕΠΙΒΑΤΩΝ ΚΑΙ ΑΥΤΟΚΙΝΗΤΩΝ. 107
ΣΧΗΜΑ 58: ΑΠΛΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ........................................................... 107
ΣΧΗΜΑ 59: ΠΡΩΤΗ ΒΕΛΤΙΩΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ. ................................. 108
ΣΧΗΜΑ 60: ∆ΕΥΤΕΡΗ ΒΕΛΤΙΩΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ. ............................... 109
ΣΧΗΜΑ 61: ΛΥΣΗ ΣΤΗΝ ΑΠΛΗ ΕΚ∆ΟΣΗ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΤΟΥ ΚΟΥΡΕΑ.................................................. 110
ΣΧΗΜΑ 62: ΜΙΑ ΑΚΟΜΗ ΣΩΣΤΗ ΛΥΣΗ ΣΤΗΝ ΑΠΛΗ ΕΚ∆ΟΣΗ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΤΟΥ ΚΟΥΡΕΑ................... 111
ΣΧΗΜΑ 63: ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΚΟΥΡΕΑ. ........................................................................................ 112
ΣΧΗΜΑ 64: ΠΡΩΤΗ ΠΡΟΣΠΑΘΕΙΑ ΕΠΙΛΥΣΗΣ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ. .................... 112
ΣΧΗΜΑ 65: ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ. .................................................................. 113
ΣΧΗΜΑ 66: ΣΚΕΛΕΤΟΣ ΚΩ∆ΙΚΑ ∆ΙΕΡΓΑΣΙΑΣ ΑΥΤΟΚΙΝΗΤΟΥ. ....................................................................... 114
ΣΧΗΜΑ 67: ΠΕΡΙΓΡΑΦΗ ΤΩΝ ΕΝΕΡΓΕΙΩΝ ΤΩΝ ∆ΙΕΡΓΑΣΙΩΝ ΤΗΣ ΆΣΚΗΣΗΣ ΑΥΤΟΑΞΙΟΛΟΓΗΣΗΣ 44. ............. 116
ΣΧΗΜΑ 68: ΣΚΕΛΕΤΟΣ ΚΩ∆ΙΚΑ ∆ΙΕΡΓΑΣΙΩΝ ΓΙΑ ΤΟ ΠΡΟΒΛΗΜΑ ΠΟΥ ΠΕΡΙΓΡΑΦΕΤΑΙ ΣΤΗΝ ΆΣΚΗΣΗ
ΑΥΤΟΑΞΙΟΛΟΓΟΓΗΣΗΣ 45.................................................................................................................. 118
ΣΧΗΜΑ 69: ΓΛΩΣΣΙΚΗ ΈΚΦΡΑΣΗ ΤΟΥ HANSEN. .......................................................................................... 119
ΣΧΗΜΑ 70: ΑΤΟΜΙΚΗ ΕΚ∆ΟΣΗ ΤΩΝ DEPOSIT() ΚΑΙ WITHDRAW() ΧΡΗΣΙΜΟΠΟΙΩΝΤΑΣ ΤΗ ΓΛΩΣΣΙΚΗ ΕΚΦΡΑΣΗ
ΤΟΥ HANSEN.
□ 120
ΣΧΗΜΑ 71: ΓΛΩΣΣΙΚΗ ΈΚΦΡΑΣΗ ΚΡΙΣΙΜΗΣ ΠΕΡΙΟΧΗΣ ΥΠΟ ΣΥΝΘΗΚΗ. ..................................................... 120
ΣΧΗΜΑ 72: ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ ΜΕ ΧΡΗΣΗ ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ
(ΑΝΤΙΣΤΟΙΧΗ ΕΚΕΙΝΗΣ ΤΟΥ ΣΧΗΜΑΤΟΣ 59)....................................................................................... 122
ΣΧΗΜΑ 73: ΠΡΩΤΗ ΒΕΛΤΙΩΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ ΜΕ ΧΡΗΣΗ ΚΡΙΣΙΜΩΝ
ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ (ΑΝΤΙΣΤΟΙΧΗ ΕΚΕΙΝΗΣ ΤΟΥ ΣΧΗΜΑΤΟΣ 60).............................................. 122
ΣΧΗΜΑ 74: ∆ΕΥΤΕΡΗ ΒΕΛΤΙΩΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ ΜΕ ΧΡΗΣΗ ΚΡΙΣΙΜΩΝ
ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ (ΑΝΤΙΣΤΟΙΧΗ ΕΚΕΙΝΗΣ ΤΟΥ ΣΧΗΜΑΤΟΣ 61).............................................. 123
ΣΧΗΜΑ 75: ΛΥΣΗ ΑΝΤΙΣΤΟΙΧΗ ΕΚΕΙΝΗΣ ΤΟΥ ΣΧΗΜΑΤΟΣ 75 ΑΛΛΑ ΠΟΥ ΕΠΙΤΥΓΧΑΝΕΙ ΜΕΓΑΛΥΤΕΡΟ
ΠΑΡΑΛΛΗΛΙΣΜΟ. □ ............................................................................................................................ 124
ΣΧΗΜΑ 76: ΠΡΩΤΗ ΠΡΟΣΠΑΘΕΙΑ ΕΠΙΛΥΣΗΣ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ ΜΕ ΧΡΗΣΗ
ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ.................................................................................................. 124
ΣΧΗΜΑ 77: ΣΩΣΤΗ ΛΥΣΗ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ ΜΕ ΧΡΗΣΗ ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ
ΥΠΟ ΣΥΝΘΗΚΗ. .................................................................................................................................. 125
ΣΧΗΜΑ 78: Ο ΑΛΓΟΡΙΘΜΟΣ ΤΟΥ ΑΡΤΟΠΩΛΕΙΟΥ. ........................................................................................ 127
ΣΧΗΜΑ 79: Η ΛΥΣΗ ΤΟΥ DEKKER. .............................................................................................................. 128
ΣΧΗΜΑ 80: ∆ΙΚΑΙΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΜΕ ΧΡΗΣΗ TEST&SET().......... 130
ΣΧΗΜΑ 81: ∆ΙΚΑΙΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΩΝ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ. ............................................... 132
ΣΧΗΜΑ 82: ΕΝΕΡΓΕΙΕΣ ΠΟΥ ΕΠΙΤΕΛΟΥΝ ΟΙ ΛΕΙΤΟΥΡΓΙΕΣ SEM_DOWN() ΚΑΙ GSEM_UP().............................. 134
ΣΧΗΜΑ 83: ΥΛΟΠΟΙΗΣΗ ΓΕΝΙΚΟΥ ΣΗΜΑΦΟΡΟΥ ΑΠΟ ∆ΥΑ∆ΙΚΟ ΣΗΜΑΦΟΡΟ. ............................................... 134
ΣΧΗΜΑ 84: ΣΚΕΛΕΤΟΣ ΚΩ∆ΙΚΑ ΓΙΑ ΤΗΝ ΥΛΟΠΟΙΗΣΗ ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ ΧΡΗΣΙΜΟΠΟΙΩΝΤΑΣ
ΣΗΜΑΦΟΡΟΥΣ. ................................................................................................................................... 136
Πρόλογος
Το εκπαιδευτικό υλικό αυτό αποτελεί βοηθητικό διδακτικό υλικό για το µάθηµα των
Λειτουργικών Συστηµάτων και έχει γραφτεί για τις ανάγκες των φοιτητών της θεµατικής
ενότητας ΠΛΗ-11 του ΕΑΠ, της οποίας τα µαθήµατα διδάσκονται στο 1ο έτος σπουδών
του Τµήµατος Πληροφορικής, δηλαδή σε φοιτητές που δεν έχουν πολύ µεγάλη
εξοικείωση µε θέµατα προγραµµατισµού και αρχιτεκτονικής υπολογιστών. Έτσι, το
εκπαιδευτικό υλικό αυτό παρουσιάζει βασικά µόνο θέµατα των ΛΣ και αυτά µε
συνοπτικό και όσο το δυνατό πιο απλό τρόπο.
Πολλά από τα παραδείγµατα και τις ασκήσεις αυτοαξιολόγησης που περιγράφονται στο
εκπαιδευτικό υλικό, έχουν προταθεί από διάφορα µέλη ΣΕΠ της θεµατικής ενότητας και
έχουν αποτελέσει µέρος των εργασιών των φοιτητών της ΠΛΗ-11 στο παρελθόν. Οι
συγγραφείς έχουν βάλει τη προσωπική τους σφραγίδα στα περισσότερα µέρη του
εκπαιδευτικού υλικού, πολλές φορές αποκλίνοντας από τις προτεινόµενες λύσεις της
θεµατικής ενότητας, προκειµένου να καλύψουν διάφορα θέµατα που θα ήθελαν να
συζητηθούν µε µεγαλύτερη λεπτοµέρεια. Ωστόσο, δεν παύουν να πιστεύουν πως το
εκπαιδευτικό υλικό αυτό είναι αποτέλεσµα και της συλλογικής προσπάθειας των µελών
ΣΕΠ της θεµατικής ενότητας.
Σε επόµενες εκδόσεις του εκπαιδευτικού υλικού θα προστεθεί υλικό που θα αφορά τη
διαχείριση µνήµης (και θα ενσωµατωθούν οποιεσδήποτε αλλαγές και βελτιώσεις
θεωρηθούν απαραίτητες στα υπάρχοντα κεφάλαια).
1ο Κεφάλαιο
Εισαγωγή
1ο Κεφάλαιο Εισαγωγή

Ένα Λειτουργικό Σύστηµα (ΛΣ) είναι ένα πρόγραµµα που αποσκοπεί στο να διευκολύνει
τους χρήστες στη χρήση του υλικού (hardware) ενός υπολογιστή και των υπολοίπων
πόρων του συστήµατος. Ονοµάζουµε πόρο οτιδήποτε µπορεί να χρησιµοποιηθεί κάθε
χρονική στιγµή από ένα µόνο πρόγραµµα που εκτελείται. Οι πόροι µπορεί να είναι είτε
συσκευές υλικού (π.χ., η κεντρική µονάδα επεξεργασίας, η µνήµη, ο δίσκος, ο
εκτυπωτής, ο σχεδιογράφος, άλλες συσκευές εισόδου/εξόδου, κλπ.) είτε δεδοµένα ή
λογικές οντότητες (π.χ., ένα αρχείο, µια εγγραφή σε βάση δεδοµένων, µια δοµή
δεδοµένων στην οποία το ΛΣ αποθηκεύει πληροφορίες, κλπ.). Προφανώς, είναι δυνατόν
να υπάρχουν δύο ή περισσότεροι πόροι του ίδιου είδους (π.χ., δύο εκτυπωτές). Το ΛΣ
ελέγχει όλους τους πόρους του συστήµατος, παρέχοντας ένα εύχρηστο περιβάλλον για
την ανάπτυξη των εφαρµογών των χρηστών.

1.1 Σύντοµη Ανασκόπηση Υλικού Υπολογιστών


Τα βασικά µέρη του υλικού ενός υπολογιστή παρουσιάζονται στο Σχήµα 1. Ένα από τα
κυριότερα συστατικά του είναι η κεντρική µονάδα επεξεργασίας (ΚΜΕ). Η ΚΜΕ ελέγχει
τη λειτουργία του υπολογιστή και πραγµατοποιεί την επεξεργασία των δεδοµένων κατά
την εκτέλεση ενός προγράµµατος. Η ΚΜΕ εκτελεί επαναληπτικά τα εξής. ∆ιαβάζει την
επόµενη προς εκτέλεση εντολή, την αποκωδικοποιεί και την εκτελεί. Η εκτέλεση των
λειτουργιών αυτών είναι γνωστή ως κύκλος λειτουργίας ΚΜΕ. Η ΚΜΕ ξεκινά µε την
πρώτη εντολή ενός προγράµµατος (1ος κύκλος λειτουργίας) και συνεχίζει µε όσους
κύκλους είναι απαραίτητοι για να εκτελεστούν όλες οι εντολές του προγράµµατος. Κάθε
χρονική στιγµή µπορεί να εκτελείται µόνο ένα πρόγραµµα στην ΚΜΕ και ένα
πρόγραµµα µπορεί να εκτελεστεί µόνο αν υπάρχει διαθέσιµη ΚΜΕ που θα το εκτελέσει.

Καταχωρητές

∆ίσκος
Συσκευή Λήψης
Εφεδρικών
Αντιγράφων
Κύρια Μνήµη
∆ίσκος
Κρυφή
Μνήµη
ΚΜΕ

∆ίσκος

Σχήµα 1: Βασική οργάνωση ενός υπολογιστή.


Κάθε υπολογιστής έχει ένα ρολόι συστήµατος (system clock) που χρησιµοποιείται από
την ΚΜΕ για να συγχρονίζει τις λειτουργίες επεξεργασίας. Ένας κύκλος λειτουργίας
συνήθως απαιτεί έναν µικρό σταθερό αριθµό από χτύπους του ρολογιού συστήµατος.

2η έκδοση 10
1ο Κεφάλαιο Εισαγωγή

Η ΚΜΕ χρησιµοποιεί ένα µικρό αριθµό γρήγορων στοιχείων αποθήκευσης που


ονοµάζονται καταχωρητές. Ένας καταχωρητής συνήθως αποθηκεύει δεδοµένα των 32 ή
των 64 bit (ωστόσο, σήµερα έχουν κάνει την εµφάνισή τους και υπολογιστικά
συστήµατα των οποίων οι καταχωρητές έχουν µέγεθος 128 bit). Μερικοί καταχωρητές
χρησιµοποιούνται για την εκτέλεση πολύ συγκεκριµένων λειτουργιών. Ένας τέτοιος
καταχωρητής είναι ο µετρητής προγράµµατος (Program Counter, PC). Ο καταχωρητής
αυτός περιέχει την πληροφορία του ποια είναι η επόµενη προς εκτέλεση εντολή του
εκτελούµενου προγράµµατος. Η εντολή αυτή µεταφέρεται από τη µνήµη σε έναν άλλο
καταχωρητή, που ονοµάζεται καταχωρητής εντολών (Instruction Register, IR),
προκειµένου να την προσπελάσει και να την επεξεργαστεί η ΚΜΕ.
Οι καταχωρητές είναι ενσωµατωµένοι στο chip του επεξεργαστή και έτσι µπορούν να
προσπελασθούν πάρα πολύ γρήγορα (µε την ίδια ταχύτητα όπως εκείνη της λειτουργίας
της ΚΜΕ), αλλά είναι εξαιρετικά ακριβοί. Έτσι, οι σηµερινοί υπολογιστές περιέχουν
έναν µικρό µόνο αριθµό καταχωρητών. Είναι εποµένως απαραίτητη η ύπαρξη πολύ
µεγαλύτερου χώρου αποθήκευσης προκειµένου να αποθηκεύονται εκεί τα εκτελούµενα
προγράµµατα, καθώς και τα δεδοµένα που χρησιµοποιούνται από αυτά. Για το λόγο
αυτό, το δεύτερο βασικό συστατικό κάθε υπολογιστή είναι η κύρια µνήµη. Η κύρια
µνήµη χρησιµοποιείται ως χώρος αποθήκευσης και επεξεργασίας από τα προγράµµατα
και περιέχει προσωρινά δεδοµένα (που χάνονται όταν πάψει να λειτουργεί ο
υπολογιστής). Η ΚΜΕ επικοινωνεί µε τη µνήµη (και τις υπόλοιπες µονάδες του
υπολογιστή) διαµέσου του διαύλου του συστήµατος (system bus). Ο δίαυλος αποτελείται
από έναν αριθµό καναλιών µέσω των οποίων γίνεται η ανταλλαγή των πληροφοριών
µεταξύ των διαφορετικών µονάδων του συστήµατος (π.χ., µεταξύ µνήµης και
καταχωρητών).
Ένα πρόγραµµα δεν µπορεί να εκτελεστεί αν δεν βρίσκεται στη µνήµη του υπολογιστή.
Καθώς το πρόγραµµα εκτελείται, τα δεδοµένα που απαιτούνται θα πρέπει να
τοποθετηθούν στους καταχωρητές του συστήµατος. Η ΚΜΕ λειτουργεί σε υψηλότερες
ταχύτητες από την κύρια µνήµη. Έτσι, πολύς χρόνος καταναλώνεται για τη µετακίνηση
δεδοµένων µεταξύ ΚΜΕ και κύριας µνήµης. Για τη µείωση του χρόνου αυτού, κάθε
υπολογιστικό σύστηµα χρησιµοποιεί µία ή περισσότερες κρυφές µνήµες. Η κρυφή µνήµη
βρίσκεται µεταξύ της κύριας µνήµης και των καταχωρητών. Οι κρυφές µνήµες
κατασκευάζονται από ειδικά κυκλώµατα ώστε να είναι πολύ γρήγορες, αλλά έχουν
µεγάλο κόστος και εποµένως το µέγεθος τους είναι περιορισµένο (αρκετά µικρότερο από
εκείνο της κύριας µνήµης).
Όταν η ΚΜΕ χρειάζεται να διαβάσει δεδοµένα (ή εντολές) από τη µνήµη, τα δεδοµένα
αναζητούνται πρώτα στην κρυφή µνήµη. Αν βρίσκονται εκεί, η µεταφορά τους στους
καταχωρητές θα γίνει πολύ γρήγορα (απευθείας από την κρυφή µνήµη). ∆ιαφορετικά, τα
δεδοµένα θα πρέπει να αναζητηθούν στην κύρια µνήµη και να φορτωθούν στους
καταχωρητές από εκεί. Ταυτόχρονα τα δεδοµένα τοποθετούνται και στην κρυφή µνήµη,
ώστε να είναι διαθέσιµα εκεί στο µέλλον (αν εν τω µεταξύ δεν αντικατασταθούν από
άλλα πιο πρόσφατα δεδοµένα).
Τα δύο βασικότερα χαρακτηριστικά µιας µονάδας αποθήκευσης (όπως η µνήµη) είναι η
ταχύτητα προσπέλασής της και το κόστος της. ∆υστυχώς, τα δύο αυτά χαρακτηριστικά
αντιπαλεύονται το ένα το άλλο. Γρήγορες µνήµες είναι εξαιρετικά ακριβές για να
µπορούν να παρέχονται σε µεγάλες ποσότητες. Επιπρόσθετα, τόσο η κύρια όσο και οι

2η έκδοση 11
1ο Κεφάλαιο Εισαγωγή

κρυφές µνήµες δεν διατηρούν τα δεδοµένα τους, όταν ο υπολογιστής δεν είναι σε
λειτουργία. Έτσι, οι υπολογιστές χρησιµοποιούν και άλλες µονάδες αποθήκευσης που το
κόστος τους είναι αρκετά χαµηλό ώστε να παρέχονται σε µεγάλες ποσότητες. Οι µονάδες
αυτές είναι αρκετά αργές συγκριτικά µε την κύρια µνήµη. Οι µονάδες εξωτερικής
αποθήκευσης, όπως π.χ., µονάδες ταινίας, κλπ., έχουν το µικρότερο κόστος αλλά το
µεγαλύτερο χρόνο προσπέλασης. Μια άλλη µονάδα αποθήκευσης είναι οι (ηλεκτρονικοί)
δίσκοι που είναι σχετικά φθηνοί αλλά και σχετικά αργοί (παρότι σαφώς πιο γρήγοροι από
τις µονάδες εξωτερικής αποθήκευσης).
Όπως είναι αναµενόµενο, οι µονάδες κύριας µνήµης είναι αρκετά ακριβότερες από τις
µονάδες βοηθητικής αποθήκευσης (δίσκοι, µονάδες εξωτερικής αποθήκευσης). Το
µέγεθος της κύριας µνήµης επηρεάζει σηµαντικά την ισχύ ενός υπολογιστή.
Περισσότερη µνήµη σηµαίνει πως ο υπολογιστής µπορεί να εκτελεί περισσότερο
απαιτητικά σε µνήµη προγράµµατα και µάλιστα περισσότερα του ενός από αυτά
ταυτόχρονα.
Κάθε υπολογιστικό σύστηµα περιλαµβάνει ένα σύνολο συσκευών Εισόδου/Εξόδου
(Ε/Ε), όπως η οθόνη, το πληκτρολόγιο, το ποντίκι, ο εκτυπωτής, το modem, το
τερµατικό, κλπ. Τα προγράµµατα των χρηστών αποτελούνται από αλληλουχίες
εκτέλεσης υπολογισµών και λειτουργιών Ε/Ε. Ένα πρόγραµµα επιτελεί λειτουργίες Ε/Ε
αν δεν µπορεί να χρησιµοποιήσει την ΚΜΕ επειδή περιµένει κάποια εξωτερική συσκευή
να ολοκληρώσει την εργασία της. Μια συσκευή Ε/Ε µπορεί να λειτουργεί ταυτόχρονα µε
την ΚΜΕ. Αυτό είναι πολύ επιθυµητό για λόγους απόδοσης, αφού οι συσκευές Ε/Ε είναι
πολύ πιο αργές στη λειτουργία τους από την ΚΜΕ.
Σε όλους τους υπολογιστές το υλικό παρέχει ένα µηχανισµό για τη διακοπή της
κανονικής λειτουργίας της ΚΜΕ. Μια διακοπή είναι ένα σήµα που στέλνεται στην ΚΜΕ
π.χ., από κάποια συσκευή Ε/Ε (µια διακοπή µπορεί να προκληθεί και για άλλους λόγους
µερικούς από τους οποίους θα µελετήσουµε στο Κεφάλαιο 2). Η συσκευή Ε/Ε µπορεί,
για παράδειγµα, να ενηµερώσει µε αυτό τον τρόπο την ΚΜΕ για το πότε ολοκληρώνεται
η εκτέλεση της τρέχουσας λειτουργίας της. Όταν πραγµατοποιείται µια διακοπή, η ΚΜΕ
σταµατά την εκτέλεση του τρέχοντος προγράµµατος και ξεκινά την εκτέλεση ενός
κατάλληλου προγράµµατος χειρισµού της διακοπής ανάλογα µε το είδος της συσκευής
Ε/Ε από όπου προήλθε η διακοπή. Όταν πραγµατοποιηθούν οι κατάλληλες ενέργειες
χειρισµού διακοπής, η ΚΜΕ µπορεί να συνεχίσει µε την εκτέλεση του προγράµµατος που
εκτελούσε πριν προκληθεί η διακοπή.
Προκειµένου η ΚΜΕ και οι συσκευές Ε/Ε να δουλεύουν ταυτόχρονα κάποιες χρονικές
περιόδους κατά την εκτέλεση ενός προγράµµατος, συχνά χρησιµοποιούνται από τις
συσκευές Ε/Ε µικροί χώροι προσωρινής αποθήκευσης (buffers). Η βασική ιδέα είναι η
εξής. Αφού διαβαστούν τα δεδοµένα από τη συσκευή Ε/Ε, τοποθετούνται σε έναν µικρό
χώρο προσωρινής αποθήκευσης και στη συνέχεια αντιγράφονται στη µνήµη,
προκειµένου να τα επεξεργαστεί η ΚΜΕ. Καθώς γίνεται η επεξεργασία αυτή, η συσκευή
Ε/Ε διαβάζει νέα δεδοµένα και τα τοποθετεί στον ενδιάµεσο χώρο προσωρινής
αποθήκευσης. Κάτι αντίστοιχο µπορεί να συµβεί και κατά την έξοδο δεδοµένων. Η
ΚΜΕ τοποθετεί τα δεδοµένα εξόδου σε ένα χώρο προσωρινής αποθήκευσης και
συνεχίζει την εκτέλεση του προγράµµατος. Η συσκευή Ε/Ε διαβάζει τα δεδοµένα από το
χώρο αυτό. Ένας προσωρινός χώρος αποθήκευσης συνήθως δεν µπορεί να αποθηκεύσει
περισσότερα από µερικές εκατοντάδες bytes.

2η έκδοση 12
1ο Κεφάλαιο Εισαγωγή

Μια δεύτερη τεχνική που επιτρέπει την ταυτόχρονη λειτουργία ΚΜΕ και Ε/Ε, είναι η
τεχνική της παροχέτευσης (Spooling – Simultaneous Peripheral Operation On Line). Η
βασική ιδέα είναι να χρησιµοποιείται µεγάλο µέρος του δίσκου σαν ένας τεράστιος
προσωρινός χώρος αποθήκευσης. Όταν ένα πρόγραµµα εκτελείται διαβάζει τα δεδοµένα
εισόδου του από το χώρο παροχέτευσης του δίσκου και αντίστοιχα γράφει τα δεδοµένα
εξόδου του στο χώρο αυτό, αντί να διαβάζει κα να γράφει απευθείας στις συσκευές Ε/Ε.
Παρόµοια, µια συσκευή Ε/Ε διαβάζει τα δεδοµένα από το χώρο παροχέτευσης του δίσκο
και γράφει τα δεδοµένα εισόδου επίσης στο χώρο αυτό. Με τον τρόπο αυτό, οι
λειτουργίες Ε/Ε ενός προγράµµατος µπορούν να εκτελεστούν ακόµη και µετά τον
τερµατισµό του (offline) προγράµµατος. Είναι αξιοσηµείωτο ότι η τεχνική της
παροχέτευσης επιτρέπει σε πολλά προγράµµατα να διαβαστούν και να αποθηκευτούν στο
δίσκο. ∆ίνεται έτσι η δυνατότητα, οποιοδήποτε από αυτά να είναι το επόµενο που θα
τοποθετηθεί στη µνήµη προκειµένου να µπορέσει να εκτελεστεί.

1.2 Σκοπός & Υπηρεσίες ενός ΛΣ


Η κυρίαρχη φροντίδα ενός ΛΣ είναι η κατάλληλη διαχείριση όλων των πόρων του
υπολογιστή. Αυτό κατανέµει τους πόρους στα προγράµµατα των χρηστών και µεσολαβεί
όταν υπάρχουν αντικρουόµενες αιτήσεις. Για παράδειγµα, φανταστείτε τι θα συµβεί αν
δύο διαφορετικά προγράµµατα χρησιµοποιήσουν ταυτόχρονα τον εκτυπωτή. Το
πιθανότερο είναι το αποτέλεσµα να είναι ένα µπερδεµένο σύνολο σελίδων. Το
λειτουργικό εξασφαλίζει πως κάτι τέτοιο δεν θα συµβεί. Μέριµνα του ΛΣ είναι όχι µόνο
η ελεγχόµενη αλλά και η συστηµατική κατανοµή των πόρων ώστε να επιτυγχάνεται ως
επί το πλείστον η αποδοτική λειτουργία του συστήµατος.
Ένας δεύτερος βασικός στόχος ενός λειτουργικού συστήµατος είναι η διευκόλυνση του
χρήστη στην εκτέλεση προγραµµάτων. Κάθε µέρος του υλικού ενός συστήµατος έχει τις
δικές του ιδιαιτερότητες. Το λειτουργικό σύστηµα αναλαµβάνει να αποκρύψει από τον
µέσο προγραµµατιστή τις ιδιαιτερότητες αυτές, παρέχοντάς του µια απλή και υψηλού
επιπέδου αφαιρετική εικόνα.
Τέλος, ένας ακόµη σηµαντικός στόχος ενός λειτουργικού συστήµατος είναι η παροχή
προστασίας στους χρήστες. Το ΛΣ είναι υπεύθυνο για τον έλεγχο των συσκευών και των
προγραµµάτων, προκειµένου να αποφεύγονται σφάλµατα και να γίνεται δόκιµη χρήση
του υπολογιστή.
Με λίγα λόγια, το λειτουργικό σύστηµα αποσκοπεί στην αποτελεσµατική εκτέλεση
προγραµµάτων και την εύκολη και γρήγορη επίλυση των προβληµάτων επικοινωνίας του
χρήστη µε τον υπολογιστή.
Η τοποθέτηση του ΛΣ σε ένα υπολογιστικό σύστηµα φαίνεται στο
Σχήµα 2 (ένα αντίστοιχο σχήµα παρουσιάζεται στην Ενότητα 1.1 του Τόµου Γ της
θεµατικής ενότητας). Η άποψη που έχει ο χρήστης για το σύστηµα είναι η άποψη ενός
συνόλου εφαρµογών (όπως µεταγλωττιστών, συστηµάτων βάσεων δεδοµένων,
επεξεργαστών κειµένου, διορθωτών, κλπ.) Οι λεπτοµέρειες του υλικού αποκρύπτονται µε
τη βοήθεια του ΛΣ.

2η έκδοση 13
1ο Κεφάλαιο Εισαγωγή

Χρήστης 1
Χρήστης 2
γ ρ ά µ µ
ο α
ρ τ
Π
α
Λειτουργικό Σύστηµα

Υλικό

Χρήστης 3

Ε ν
φ ώ
α ο γ
ρ µ

Χρήστης 5 Χρήστης 4

Σχήµα 2: Σχέση του ΛΣ µε το υλικό του υπολογιστή και τους χρήστες.


Όταν βάζουµε τον υπολογιστή σε λειτουργία, εκκινείται αυτόµατα η εκτέλεση του
λειτουργικού συστήµατος πριν αρχίσει η εκτέλεση οποιουδήποτε άλλου προγράµµατος
στο σύστηµα.

1.3 Πολυπρογραµµατισµός και ∆ιαµοιρασµός Χρόνου


Όταν σε έναν υπολογιστή εκτελείται ένα µόνο πρόγραµµα, η ΚΜΕ παραµένει για αρκετό
χρόνο ανενεργή. Αυτό συµβαίνει γιατί, όπως έχει ήδη αναφερθεί, τα περισσότερα
προγράµµατα απαιτούν λειτουργίες Ε/Ε των οποίων η εκτέλεση είναι εξαιρετικά αργή.
Προκειµένου να αυξηθεί η απόδοση των συστηµάτων, τα σύγχρονα λειτουργικά
συστήµατα χρησιµοποιούν µια τεχνική που λέγεται πολυ-προγραµµατισµός. Η βασική
ιδέα είναι να υπάρχουν αρκετά έτοιµα προς εκτέλεση προγράµµατα στη µνήµη. Κάθε
φορά που η εκτέλεση ενός από αυτά φθάνει σε σηµείο που απαιτείται η εκτέλεση
κάποιας λειτουργία Ε/Ε, ένα άλλο πρόγραµµα επιλέγεται και εκτελείται στην ΚΜΕ. Η
τεχνική αυτή εφαρµόζεται επαναληπτικά (όσο υπάρχουν έτοιµα προς εκτέλεση
προγράµµατα στο σύστηµα). Έτσι, σε µια τυχαία χρονική στιγµή µπορεί να υπάρχουν
πολλά προγράµµατα που έχουν µερικώς εκτελεστεί. Κάποια από αυτά µπορεί να
εκτελούν κάποια λειτουργία Ε/Ε την τρέχουσα χρονική στιγµή, ενώ άλλα ίσως
βρίσκονται στη µνήµη και είναι έτοιµα να εκτελεστούν, όταν η ΚΜΕ γίνει διαθέσιµη.
Το ΛΣ είναι και αυτό ένα πρόγραµµα που χρησιµοποιεί περιοδικά την ΚΜΕ προκειµένου
να εκτελέσει τα δικά του καθήκοντα. Το ΛΣ είναι το πρόγραµµα µε την υψηλότερη
προτεραιότητα στη χρήση της ΚΜΕ. Το ΛΣ και πιο συγκεκριµένα ένα µέρος του, που
ονοµάζεται χρονο-δροµολογητής (ή χρονοπρογραµµατιστής), είναι αυτό που θα
αποφασίσει ποιο πρόγραµµα θα απασχολεί κάθε χρονική στιγµή την ΚΜΕ. Ο

2η έκδοση 14
1ο Κεφάλαιο Εισαγωγή

πολυπρογραµµατισµός επιφέρει εποµένως την ανάγκη σχεδιασµού καλών αλγορίθµων


χρονοδροµολόγησης. Επιπρόσθετα, εφόσον περισσότερα του ενός προγράµµατα πρέπει
να βρίσκονται στη µνήµη ταυτόχρονα, ο πολυπρογραµµατισµός οδηγεί στην
αναγκαιότητα ύπαρξης µηχανισµών διαχείρισης της µνήµης , δηλαδή αλγορίθµων που θα
αποφασίζουν πώς η µνήµη κατανέµεται στα διάφορα προγράµµατα. Το πρόβληµα της
διαχείρισης της µνήµης είναι ένα ακόµη σηµαντικό πρόβληµα που θα µελετηθεί σε
κάποια από τα επόµενα κεφάλαια.
Πολλά προγράµµατα απαιτούν κάποιου είδους αλληλεπίδραση µε το χρήστη. Για
παράδειγµα, ο χρήστης µπορεί να πρέπει να καθορίσει διάφορες παραµέτρους καθώς το
πρόγραµµα εκτελείται ή µπορεί να χρειαστεί να παρέχει δεδοµένα εισόδου, κλπ.
Προγράµµατα τα οποία απαιτούν αλληλεπίδραση µε το χρήστη ονοµάζονται
αλληλεπιδραστικά (interactive) και τα συστήµατα που παρέχουν αυτή τη δυνατότητα
ονοµάζονται αλληλεπιδραστικά συστήµατα.
Ένα λειτουργικό σύστηµα διαµοιρασµού χρόνου επιτρέπει σε πολλούς χρήστες
ταυτόχρονα να χρησιµοποιούν το σύστηµα, παρέχοντας τους ωστόσο τη ψευδαίσθηση
ότι ο κάθε ένας από αυτούς είναι ο µοναδικός χρήστης του συστήµατος. Αυτό
επιτυγχάνεται µε τη γρήγορη εναλλαγή (switching) των προγραµµάτων των διαφόρων
χρηστών που είναι έτοιµα προς εκτέλεση από και προς την ΚΜΕ. Πιο συγκεκριµένα,
κάθε πρόγραµµα εκτελείται για λίγο και στη συνέχεια εναλλάσσεται µε κάποιο άλλο, το
οποίο είναι επίσης έτοιµο για εκτέλεση και διεκδικεί την ΚΜΕ. Με τον τρόπο αυτό, κάθε
χρήστης χρησιµοποιεί ένα µέρος της υπολογιστικής ισχύος του συστήµατος, έχοντας
ωστόσο τη ψευδαίσθηση ότι είναι ο µοναδικός χρήστης που απασχολεί το σύστηµα.
Όπως έχει ήδη αναφερθεί, η απόφαση του ποιο πρόγραµµα θα εκτελείται κάθε χρονική
στιγµή λαµβάνεται από τον χρονοδροµολογητή. Είναι σηµαντικό να γίνει κατανοητό,
πως ο χρονοδροµολογητής µπορεί να διακόπτει την εκτέλεση ενός προγράµµατος
οποιαδήποτε χρονική στιγµή και να αποδίδει την ΚΜΕ σε κάποιο άλλο πρόγραµµα.
Έτσι, ακόµη και σε συστήµατα µε µια µόνο ΚΜΕ, τα διάφορα προγράµµατα εκτελούνται
κατά µία έννοια (ψευδο-)παράλληλα.
Η τεχνική του διαµοιρασµού χρόνου βασίζεται στις τεχνικές του πολυπρογραµµατισµού
και της χρνοδροµολόγησης και είναι εφικτή για τους ακόλουθους λόγους:
• Τα προγράµµατα των χρηστών είναι συνήθως µικρά και απαιτούν πολύ Ε/Ε.
• Η ταχύτητα µε την οποία ο χρήστης παρέχει δεδοµένα στο σύστηµα (π.χ., η ταχύτητα
πληκτρολόγησης) είναι εξαιρετικά µικρή σε σχέση µε εκείνη της ΚΜΕ.
• Η εναλλαγή των προγραµµάτων γίνεται πολύ γρήγορα, ώστε να µην είναι αντιληπτή.
Αξίζει να σηµειωθεί πως κάποια από τα πρώτα υπολογιστικά συστήµατα ήταν συστήµατα
οµαδικής επεξεργασίας (ή συστήµατα δέσµης, batch systems) και δεν υποστήριζαν καµία
αλληλεπίδραση µε το χρήστη. Το ΛΣ ενός τέτοιου συστήµατος δέχεται µια οµάδα
εργασιών (προγραµµάτων) προς εκτέλεση και έχει σαν στόχο να κάνει καλή διανοµή των
πόρων και να εφαρµόσει καλές πολιτικές χρονοδροµολόγησης, ώστε να ελαχιστοποιηθεί
ο χρόνος περάτωσης της εκτέλεσης των εργασιών αυτών.
Με την τεχνική του διαµοιρασµού χρόνου µπορούν πολλοί χρήστες να χρησιµοποιούν
ταυτόχρονα τον υπολογιστή. Έτσι, προγράµµατα διαφορετικών χρηστών (που

2η έκδοση 15
1ο Κεφάλαιο Εισαγωγή

ενδεχόµενα έχουν διαφορετικούς και αντικρουόµενους στόχους) συναγωνίζονται για


τους πόρους του υπολογιστή. Το λειτουργικό σύστηµα θα πρέπει να λάβει µέτρα για την
δίκαιη χρήση των πόρων αυτών. Προκειµένου να αποφευχθεί, για παράδειγµα, η επ’
άπειρο χρήση της ΚΜΕ από ένα µόνο πρόγραµµα, πολλά συστήµατα χρησιµοποιούν
έναν µετρητή, που ονοµάζεται χρονοµετρητής. Η τιµή του χρονοµετρητή ρυθµίζεται κάθε
φορά που ένα πρόγραµµα αποκτά την ΚΜΕ. Με κάθε παλµό του ρολογιού του
συστήµατος, η τιµή του χρονοµετρητή µειώνεται και όταν µηδενιστεί προκαλείται
διακοπή. Τότε αναλαµβάνει δράση το ΛΣ, το οποίο ελέγχει το χρόνο που το τρέχον
πρόγραµµα έχει χρησιµοποιήσει την ΚΜΕ και αποφασίζει αν αυτό θα συνεχίσει να
κατέχει την ΚΜΕ για µια ακόµη περίοδο χρόνου (ή κβάντο χρόνου όπως ονοµάζεται), ή
αν θα ανασταλεί παραχωρώντας την ΚΜΕ σε κάποιο άλλο ενεργό πρόγραµµα. Έτσι, το
ΛΣ µπορεί περιοδικά να ελέγχει τη λειτουργία της ΚΜΕ και να αποφασίζει ποιο
πρόγραµµα πρέπει να εκτελείται εκεί.

1.4 Καταστάσεις Λειτουργίας Συστηµάτων


Ένα υπολογιστικό σύστηµα µπορεί να λειτουργεί µε δύο τρόπους, σε κατάσταση επόπτη
(supervisor mode), στην οποία εκτελείται µόνο το ΛΣ και σε κατάσταση χρήστη (user
mode), στην οποία εκτελούνται όλα τα προγράµµατα των χρηστών. Πολλές λειτουργίες
πρέπει να εκτελούνται σε κατάσταση επόπτη για λόγους προστασίας (π.χ., για να
διασφαλιστεί πως δεν θα εκτελέσουν παράνοµες ενέργειες και δεν θα χρησιµοποιήσουν
τους πόρους παραπάνω από όσο χρόνο τους αναλογεί). Οι λειτουργίες αυτές ονοµάζονται
προνοµιακές εντολές (privileged instructions). Οι λειτουργίες Ε/Ε επιτρέπεται να
εκτελεστούν µόνο σε κατάσταση επόπτη, αφού αν ένα πρόγραµµα χρήστη εκτελέσει
λειτουργίες Ε/Ε χωρίς την παρεµβολή του ΛΣ, τίποτα δεν µπορεί να το αποτρέψει από το
να προσπελάσει και να τροποποιήσει δεδοµένα που ανήκουν σε άλλους χρήστες, το
οποίο δεν είναι επιθυµητό.
Προκειµένου να εκτελεστούν οι λειτουργίες Ε/Ε που απαιτούνται από τα προγράµµατα
χρήστη, τα προγράµµατα αυτά θα πρέπει να ζητήσουν από το ΛΣ να τα εξυπηρετήσει
εκτελώντας τις λειτουργίες αυτές, εφ’ όσον δεν έχουν τη δικαιοδοσία να τις εκτελέσουν
από µόνα τους. Όλα τα ΛΣ παρέχουν µια συλλογή από ρουτίνες, κάθε µια από τις οποίες
αντιστοιχεί σε µια προνοµιακή εντολή που µπορεί να εκτελείται από τα προγράµµατα
των χρηστών. Οι ρουτίνες αυτές ονοµάζονται κλήσεις συστήµατος. Κάθε φορά που ένα
πρόγραµµα χρήστη καλεί µια κλήση συστήµατος, γίνεται διακοπή προς το ΛΣ και το
σύστηµα µεταπίπτει από κατάσταση χρήστη σε κατάσταση επόπτη. Το ΛΣ αρχίζει να
εκτελείται, αποκρυπτογραφεί το είδος της διακοπής και αναλαµβάνει να διεκπεραιώσει
την προνοµιακή εντολή που του ζητήθηκε, αφού φυσικά πρώτα ελέγξει ότι αυτή είναι
έγκυρη και ότι δεν παρουσιάζεται κανένα πρόβληµα προστασίας. Εν τω µεταξύ, το ΛΣ
µπορεί να αποφασίσει πως θα πρέπει να ανασταλεί η εκτέλεση του προγράµµατος που
περιµένει τη διεκπεραίωση της προνοµιακής εντολής και να αρχίσει να εκτελείται κάποιο
άλλο πρόγραµµα, προκειµένου η ΚΜΕ να µην παραµείνει ανενεργή. Επίσης, ίσως έχει
ζητηθεί από κάποια από τις συσκευές Ε/Ε να εκτελέσει τις απαραίτητες λειτουργίες για
τη διεκπεραίωση της προνοµιακής εντολής. Όταν η συσκευή Ε/Ε τελειώσει την εκτέλεση
της λειτουργίας που της ζητήθηκε, διακόπτει και πάλι την ΚΜΕ, το σύστηµα εισέρχεται
σε κατάσταση επόπτη και το ΛΣ λαµβάνει τις κατάλληλες ενέργειες εξυπηρέτησης της
διακοπής. Αν η εκτέλεση της προνοµιακής εντολής έχει τελειώσει, το ΛΣ έχει τη

2η έκδοση 16
1ο Κεφάλαιο Εισαγωγή

δυνατότητα να ξανα-αποδώσει την ΚΜΕ στο πρόγραµµα που ζήτησε την προνοµιακή
εντολή για να συνεχιστεί η εκτέλεσή του (προφανώς µε µετάπτωση σε κατάσταση
χρήστη). Εναλλακτικά, το ΛΣ µπορεί να αποφασίσει ότι είναι προτιµότερο να συνεχιστεί
η εκτέλεση του προγράµµατος που απασχολούσε την ΚΜΕ όταν έγινε η διακοπή, ή µε
οποιοδήποτε άλλο ενεργό πρόγραµµα.
Είναι αξιοσηµείωτο πως κλήσεις συστήµατος υπάρχουν όχι µόνο για την εκτέλεση
λειτουργιών Ε/Ε αλλά και για τη δηµιουργία διεργασιών, τη διαχείριση µνήµης, κ.α.

1.5 Τα Κύρια Μέρη ενός Λειτουργικού Συστήµατος


Μια διεργασία είναι ένα πρόγραµµα που εκτελείται. (Περισσότερα για τις διεργασίες θα
συζητηθούν στο Κεφάλαιο 2.) Τα περισσότερα ΛΣ υποστηρίζουν τη δηµιουργία και τον
τερµατισµό διεργασιών, παρέχουν µηχανισµούς για τη µεταξύ τους επικοινωνία και το
συγχρονισµό τους, ενώ επιπρόσθετα επιτρέπουν την αναστολή εκτέλεσης µιας
διεργασίας, καθώς και την επαναπόδοση της ΚΜΕ σε αυτήν. Όλες αυτές οι λειτουργίες
αποτελούν βασικά καθήκοντα ενός από τα σηµαντικότερα µέρη του ΛΣ, που ονοµάζεται
διαχειριστής διεργασιών.
Ένα σύνολο διεργασιών βρίσκεται σε αδιέξοδο αν κάθε διεργασία του συνόλου περιµένει
να συµβεί κάποιο γεγονός που µόνο µια άλλη διεργασία του συνόλου µπορεί να
προκαλέσει. Για παράδειγµα, έστω ότι µία διεργασία Α έχει υπό την κατοχή της και
χρησιµοποιεί έναν πόρο Π, αλλά για να τελειώσει την εκτέλεσή της και να τερµατίσει,
ελευθερώνοντας τον πόρο Π, χρειάζεται να χρησιµοποιήσει και τον πόρο Π’. Έστω τώρα
ότι µια άλλη διεργασία Β, που έχει υπό την κατοχή της και χρησιµοποιεί τον Π’,
χρειάζεται, για να τερµατίσει και να ελευθερώσει τον Π’, να χρησιµοποιήσει τον Π.
Προφανώς, από κάποιο σηµείο και έπειτα, η εκτέλεση και των δύο διεργασιών θα
σταµατήσει και για καµία από τις δύο δεν θα είναι εφικτό να συνεχίσει, αφού κάθε µια
περιµένει µια ενέργεια που θα πρέπει να προκαλέσει η άλλη. Όταν οι δύο διεργασίες δεν
µπορούν πλέον να εκτελεστούν, βρίσκονται σε αδιέξοδο. Είναι ευθύνη του διαχειριστή
διεργασιών κάθε ΛΣ να χειρίζεται καταστάσεις αδιεξόδου (είτε επιλύοντας αδιέξοδα που
προκύπτουν, είτε προλαµβάνοντας ή αποφεύγοντάς τα ώστε να µην προκύψουν, ή
λαµβάνοντας κάποιου άλλου είδους ενέργειες προκειµένου να τα διαχειριστεί).
Μέρος του διαχειριστή διεργασιών είναι και ο χρονοδροµολογητής διεργασιών, που
αποφασίζει ποια διεργασία θα απασχολεί την ΚΜΕ κάθε χρονική στιγµή. Πολλά θέµατα
που άπτονται του διαχειριστή διεργασιών θα συζητηθούν στα Κεφάλαια 2 και 3.
Ένα δεύτερο σηµαντικό συστατικό ενός ΛΣ είναι ο διαχειριστής µνήµης. Μερικές από τις
λειτουργίες για τις οποίες είναι υπεύθυνος ο διαχειριστής µνήµης είναι (1) η επιλογή των
διεργασιών που βρίσκονται κάθε χρονική στιγµή στη µνήµη, (2) η χορήγηση µνήµης στις
διεργασίες όταν τη χρειάζονται και διαχείριση του ελεύθερου χώρου που προκύπτει στη
µνήµη όταν αυτές ολοκληρώνουν την εκτέλεσή τους, (3) η εναλλαγή πληροφοριών
µεταξύ κύριας µνήµης και δίσκου όταν η κύρια µνήµη δεν επαρκεί για τις ανάγκες όλων
των ενεργών διεργασιών, και (4) η διατήρηση πληροφοριών για τα µέρη της µνήµης που
χρησιµοποιούνται, καθώς και για εκείνα που είναι αχρησιµοποίητα κάθε χρονική στιγµή.
∆ιαφορετικές τεχνικές διαχείρισης µνήµης θα µελετηθούν αναλυτικά στο κεφάλαιο 4.

2η έκδοση 17
1ο Κεφάλαιο Εισαγωγή

Το εκπαιδευτικό υλικό αυτό, ως εισαγωγικό στο χώρο των ΛΣ, επικεντρώνεται µόνο στα
παραπάνω δύο σηµαντικά συστατικά των ΛΣ. Ωστόσο, ένα ΛΣ απαρτίζεται από πολύ
περισσότερα συστατικά τα οποία αναφέρονται περιληπτικά στη συνέχεια.
Κάθε διεργασία µπορεί να αποθηκεύει τα δεδοµένα της στην κύρια µνήµη. Ωστόσο, ο
χώρος αυτός είναι περιορισµένος και για τις περισσότερες εφαρµογές δεν είναι αρκετός.
Επιπρόσθετα όταν µια διεργασία τερµατίζει όλες οι πληροφορίες που γράφτηκαν από
αυτήν στην κύρια µνήµη χάνονται. Για τους λόγους αυτούς, τα δεδοµένα χρειάζεται να
αποθηκεύονται και σε δίσκους ή σε άλλα µέσα δευτερεύουσας αποθήκευσης, όπως τα
CD, τα DVD, κλπ. Η λογική µονάδα αποθήκευσης στα µέσα αυτά ονοµάζεται αρχείο.
Ένα αρχείο αποτελείται είτε από µια ακολουθία χαρακτήρων ή έχει κάποιου είδους
µορφοποίηση (π.χ., είναι αρχείο εγγραφών). Τα αρχεία λειτουργούν αφαιρετικά ως προς
τις ιδιαιτερότητες των συσκευών στις οποίες αποθηκεύονται. Το µέρος του ΛΣ που
ονοµάζεται διαχειριστής αρχείων είναι υπεύθυνο για τη δηµιουργία και τη διαγραφή
αρχείων, τον καθορισµό του είδους προσπέλασης που επιτρέπεται σε ένα αρχείο και την
αποφυγή άλλου είδους παράνοµων προσπελάσεων σε αυτό, την οργάνωση των αρχείων
σε καταλόγους, την παροχή κλήσεων συστήµατος για τη διαχείριση αρχείων, την
αποθήκευση αρχείων στα διάφορα µέσα αποθήκευσης που παρέχονται στο σύστηµα, κ.α.
Ένα άλλο σηµαντικό συστατικό ενός ΛΣ είναι ο διαχειριστής του συστήµατος Ε/Ε. Κάθε
συσκευή Ε/Ε χρειάζεται, προκειµένου να λειτουργήσει, ειδικό λογισµικό που ονοµάζεται
οδηγός της συσκευής (device driver). Το ΛΣ παρέχει µια σειρά από οδηγούς για τις
συσκευές Ε/Ε του υπολογιστικού συστήµατος στο οποίο εκτελείται. Θα πρέπει να
τονιστεί ωστόσο, πως πολλές φορές οι οδηγοί συσκευών δεν θεωρούνται µέρος του ΛΣ,
εκτελούνται σε κατάσταση χρήστη και µπορούν να αντικατασταθούν από άλλους της
αρεσκείας του χρήστη.
Ο διαχειριστής δίσκου είναι ένα ακόµη συστατικό κάθε λειτουργικού συστήµατος. Ρόλος
του είναι να διαχειρίζεται αποδοτικά και δίκαια το χώρο του δίσκου, ο οποίος είναι το
σηµαντικότερο µέσο δευτερεύουσας αποθήκευσης. Τα περισσότερα ΛΣ παρέχουν επίσης
µηχανισµούς διαχείρισης δικτύου που επιτρέπουν σε δύο ή περισσότερες µηχανές να
επικοινωνήσουν µεταξύ τους. Τέλος, ένα ακόµη σηµαντικό µέρος ενός ΛΣ είναι και το
σύστηµα προστασίας και ασφάλειας που παρέχει.
Ένα µικρό (αλλά πολύ βασικό) µέρος του ΛΣ ονοµάζεται πυρήνας. Ο πυρήνας χτίζεται
κατευθείαν πάνω στο υλικό του Η/Υ και είναι το περισσότερο εξαρτώµενο από τη
µηχανή µέρος του ΛΣ. Στον πυρήνα συνήθως ανήκουν τα µέρη εκείνα του ΛΣ που
υλοποιούν τις παρακάτω λειτουργίες:
• Χειρισµός διακοπών.
• ∆ηµιουργία και τερµατισµός διεργασιών.
• Μέρος του χρονοδροµολογητή.
• Συντονισµός διεργασιών.
• Αναστολή και αφύπνιση διεργασιών.
• Υποστήριξη δραστηριοτήτων Ε/Ε.
• Υποστήριξη δέσµευσης και αποδέσµευσης µνήµης, κ.α.

2η έκδοση 18
1ο Κεφάλαιο Εισαγωγή

Μέρος του πυρήνα είναι συχνά γραµµένο σε γλώσσα µηχανής (assembly). Τα τελευταία
χρόνια παρουσιάζεται η τάση κάποια µέρη των ΛΣ να γράφονται σε µικροκώδικα
(κώδικα στοιχειωδών εντολών, ενσωµατωµένο στο υλικό).
Κάθε ΛΣ ακολουθείται από λογισµικό που είναι γνωστό ως διερµηνευτής εντολών ή
φλοιός. Παρότι ο φλοιός δεν αποτελεί µέρος του ΛΣ, είναι ίσως το σηµαντικότερο
πρόγραµµα που εκτελείται πάνω από αυτό, αφού παρέχει τη διεπιφάνεια χρήσης µεταξύ
του χρήστη και του ΛΣ. Ο φλοιός δέχεται εντολές του χρήστη και ζητά από το ΛΣ να τις
εκτελέσει. Ο φλοιός των Windows είναι το παραθυρικό περιβάλλον χρήσης που
εµφανίζεται όταν εκτελούνται τα Windows. Σε άλλα ΛΣ, ο φλοιός δεν είναι τόσο
εύχρηστος. Ο φλοιός εκτελείται σε κατάσταση χρήστη και µπορεί να αντικατασταθεί από
οποιονδήποτε φλοιό της αρεσκείας του χρήστη.

1.6 Κατανεµηµένα Συστήµατα & Συστήµατα Πραγµατικού


Χρόνου
Ένα κατανεµηµένο σύστηµα αποτελείται από πολλούς επεξεργαστές που µπορούν να
επικοινωνούν µεταξύ τους. Σε ένα κατανεµηµένο σύστηµα, ένας υπολογισµός µπορεί να
εκτελεστεί µε συµµετοχή περισσότερων του ενός επεξεργαστών. Επίσης, προγράµµατα
που εκτελούνται σε διαφορετικούς επεξεργαστές µπορούν να µοιράζονται δεδοµένα και
άλλους πόρους. Μερικοί από τους λόγους που οδήγησαν στην δηµιουργία
κατανεµηµένων συστηµάτων είναι ο διαµοιρασµός πόρων, η αύξηση της ταχύτητας
εκτέλεσης µεγάλων υπολογισµών (αυτό γίνεται µε το διαχωρισµό ενός υπολογισµού σε
µικρότερες εργασίες και τη χρήση περισσότερων του ενός επεξεργαστών για την
εκτέλεσή τους), η αξιοπιστία (αν κάποιος επεξεργαστής αποτύχει, οι υπόλοιποι µπορούν
να αναλάβουν τις εργασίες που εκτελούσε, ώστε ο χρήστης να µην καταλάβει ότι υπήρξε
κάποιο πρόβληµα µε τον επεξεργαστή που ανέλαβε να διεκπεραιώσει τη διεργασία του),
η επικοινωνία, κ.α.
Οι στόχοι ενός κατανεµηµένου συστήµατος είναι αρκετά διαφορετικοί από εκείνους των
συµβατικών συστηµάτων. Τα ΛΣ κατανεµηµένων συστηµάτων είναι ειδικά σχεδιασµένα
για να ανταποκρίνονται στις υψηλές απαιτήσεις τους. Η µελέτη κατανεµηµένων ΛΣ δεν
θα µας απασχολήσει στο εκπαιδευτικό υλικό αυτό. Αντίθετα, θα µελετήσουµε
συστήµατα µε µία µόνο ΚΜΕ. Ωστόσο, ο πολυπρογραµµατισµός επιτρέπει την
ταυτόχρονη εκτέλεση πολλών διεργασιών και επιφέρει προβλήµατα συγχρονισµού (όπως
το πρόβληµα του αµοιβαίου αποκλεισµού που θα µελετηθεί στο Κεφάλαιο 3) που
θεωρούνται θεµελιώδη στον κατανεµηµένο υπολογισµό.
Σε ένα σύστηµα πραγµατικού χρόνου υπάρχουν καλά προκαθορισµένες χρονικές
προθεσµίες για την εκτέλεση κάθε µιας από τις διεργασίες. Ο βασικότερος στόχος του
ΛΣ είναι να καταφέρει να χρονοδροµολογήσει όλες τις διεργασίες µε τέτοιο τρόπο ώστε
η εκτέλεση κάθε µιας να περατωθεί πριν τη λήξη της προθεσµίας της. Τα συστήµατα
πραγµατικού χρόνου χρησιµοποιούνται όλο και συχνότερα τον τελευταίο καιρό, π.χ.,
στην ιατρική (για εγχειρίσεις µέσω υπολογιστών), στις µεταφορές (αυτόµατη πλοήγηση
αεροσκάφους), κ.α.

2η έκδοση 19
1ο Κεφάλαιο Εισαγωγή

1.7 Για Περισσότερη Μελέτη


Άσκηση Αυτοαξιολόγησης 1 (Μέρος Θέµατος 1, 4η Εργασία Ακ. Έτους 2001-2002)
1. Περιγράψτε τις βασικές ιδιότητες των παρακάτω τύπων λειτουργικών συστηµάτων:
α) οµαδικής επεξεργασίας
β) διαµοιρασµού χρόνου
γ) πραγµατικού χρόνου
2. Για κάθε ένα από τα παρακάτω συστήµατα επιλέξτε τον τύπο λειτουργικού
συστήµατος (i, ii ή iii) που κρίνετε ως πιο κατάλληλο. Σχολιάστε την επιλογή σας.
α) Σύστηµα που χρησιµοποιείται από µια τράπεζα για επεξεργασία επιταγών και που
απαιτεί ελάχιστη ανθρώπινη παρέµβαση.
β) Σύστηµα που χρησιµοποιείται για την ιατρική παρακολούθηση ασθενών στη
µονάδα εντατικής θεραπείας.
γ) Σύστηµα που χρησιµοποιείται για παιχνίδια.
δ) Σύστηµα που χρησιµοποιείται για ανάπτυξη εφαρµογών.
3. Ποιες από τις παρακάτω θα πρέπει να είναι προνοµιακές εντολές ενός λειτουργικού
συστήµατος και γιατί;
α) Αλλαγή της τιµής του ρολογιού του συστήµατος.
β) Ανάγνωση της τιµής του ρολογιού του συστήµατος.
γ) Καθαρισµός µνήµης
δ) Απενεργοποίηση διακοπών
ε) Αλλαγή από κατάσταση χρήστη σε κατάσταση επόπτη
στ) Λειτουργίες Εισόδου/Εξόδου.

Λύση
1. Βασικές ιδιότητες:
α) ΛΣ Οµαδικής Επεξεργασίας.
• Οι εργασίες υποβάλλονται στον Η/Υ κατά οµάδες.
• ∆εν είναι δυνατή η αλληλεπίδραση ανάµεσα στον χρήστη και στο πρόγραµµα
κατά τη διάρκεια της επεξεργασίας.
• Ο χρόνος απόκρισης για κάθε χρήστη είναι το χρονικό διάστηµα από την
υποβολή ως την παραλαβή της εργασίας.
β) ΛΣ ∆ιαµοιρασµού Χρόνου.
• Επιτρέπει την παράλληλη εξυπηρέτηση πολλών χρηστών.
• Επιτρέπει την αλληλεπίδραση των χρηστών µε τα προγράµµατά τους.

2η έκδοση 20
1ο Κεφάλαιο Εισαγωγή

• ∆ιαµοιράζει τον χρόνο της ΚΜΕ µεταξύ των χρηστών, δηµιουργώντας την
εντύπωση της παράλληλης επεξεργασίας.
γ) ΛΣ Πραγµατικού Χρόνου.
• Εξυπηρετεί εργασίες που απαιτούν αυστηρά καθορισµένο όριο χρόνου
απόκρισης.
2. Είναι φανερό ότι τα συστήµατα οµαδικής επεξεργασίας είναι τα απλούστερα, ενώ τα
συστήµατα πραγµατικού χρόνου είναι τα συνθετότερα. Μια απλή εφαρµογή που
µπορεί να εξυπηρετηθεί από ένα σύστηµα οµαδικής επεξεργασίας, είναι επόµενο να
µπορεί επίσης να εξυπηρετηθεί και από ένα σύστηµα διαµοιρασµού χρόνου και από
ένα σύστηµα πραγµατικού χρόνου. Έτσι, το ερώτηµα ουσιαστικά ζητάει τον
απλούστερο τύπο ΛΣ που µπορεί να εξυπηρετήσει ικανοποιητικά την κάθε
εφαρµογή. Λέµε «ικανοποιητικά» γιατί µια εξίσου σηµαντική παράµετρος για την
επιλογή µας είναι η αποδοτικότητα. Για παράδειγµα, ένα σύστηµα που χρησιµοποιεί
εξεζητηµένους αλγόριθµους χρονοδροµολόγησης, µπορεί πράγµατι να εξυπηρετήσει
µια απλή εφαρµογή, όµως ενδεχοµένως να σπαταλά πολύ χρόνο για τη
χρονοδροµολόγηση, χωρίς να χρειάζεται (επειδή η εφαρµογή είναι απλή). Σύµφωνα
µε τα παραπάνω, οι καταλληλότερες επιλογές έχουν ως εξής:
• Το σύστηµα επεξεργασίας επιταγών µπορεί να εξυπηρετηθεί από ένα ΛΣ
οµαδικής επεξεργασίας. ∆εν απαιτείται αλληλεπίδραση µεταξύ του χρήστη και
του συστήµατος κατά την επεξεργασία µιας επιταγής, ούτε η παράλληλη
επεξεργασία των επιταγών.
• Το σύστηµα παρακολούθησης ασθενών αποτελεί κλασική εφαρµογή για ΛΣ
πραγµατικού χρόνου. Μόλις γίνει αντιληπτή µια ανωµαλία στην κατάσταση
κάποιου ασθενούς, το σύστηµα θα πρέπει αµέσως να διακόψει κάθε µικρότερης
προτεραιότητας εργασία και να ανταποκριθεί µε αυστηρά καθορισµένα χρονικά
περιθώρια στην κατάσταση.
• Οι παιχνιδοµηχανές είναι στην πραγµατικότητα πολύ απαιτητικά συστήµατα.
Πρέπει να έχουν τη δυνατότητα να εξυπηρετούν παράλληλα πολλούς παίκτες, οι
αντιδράσεις των οποίων θα πρέπει να επιδρούν στο παιχνίδι ακαριαία. Ένα ΛΣ
πραγµατικού χρόνου απαιτείται για την υποστήριξη µιας ποιοτικής
παιχνιδοµηχανής. Μια λιγότερο απαιτητική παιχνιδοµηχανή ενδεχοµένως να
µπορούσε να υποστηριχθεί από ένα γρήγορο σύστηµα διαµοιρασµού χρόνου.
• Στο σύστηµα για ανάπτυξη εφαρµογών το κατάλληλο ΛΣ είναι αυτό του
διαµοιρασµού χρόνου. Επιτρέπει την φαινοµενικά παράλληλη εκτέλεση πολλών
εργασιών (π.χ., editor, help browser, παράθυρο εκτέλεσης προγράµµατος, κλπ),
χωρίς να είναι κρίσιµος ο συγχρονισµός τους.
3. Η έννοια της προνοµιακής εντολής αφορά λειτουργίες οι οποίες για λόγους
προστασίας δεν επιτρέπεται να εκτελούνται άµεσα από τις διεργασίες των χρηστών.
α) Η αλλαγή της τιµής του ρολογιού του συστήµατος θα πρέπει να είναι προνοµιακή
εντολή γιατί ο χρονισµός του συστήµατος είναι κρίσιµος παράγοντας για τη
σωστή λειτουργία του (ας θυµηθούµε τη σχετική συζήτηση στην ενότητα
«Προστασία ΚΜΕ»).

2η έκδοση 21
1ο Κεφάλαιο Εισαγωγή

β) Η ανάγνωση του ρολογιού του συστήµατος δεν χρειάζεται να είναι προνοµιακή


εντολή γιατί η ανάγνωση του ρολογιού του συστήµατος δεν είναι κρίσιµος
παράγοντας για τη λειτουργία του συστήµατος.
γ) Ο καθαρισµός µνήµης είναι λειτουργία Ε/Ε και, εποµένως, πρέπει να είναι
προνοµιακή εντολή.
δ) Η απενεργοποίηση διακοπών σαφώς και πρέπει να είναι προνοµιακή εντολή για να
µην µπορεί µια διαδικασία να µονοπωλήσει τους πόρους του συστήµατος µε
απενεργοποίηση των διακοπών. Ουσιαστικά, η παροχή στις διεργασίες χρήστη
του δικαιώµατος απενεργοποίησης των διακοπών θα επέτρεπε σε αυτές να
καταργούν το ΛΣ.
ε) Η αλλαγή από κατάσταση χρήστη σε κατάσταση επόπτη θα πρέπει να είναι
προνοµιακή εντολή, για τους λόγους προστασίας που έχουν ήδη συζητηθεί.
∆ιαφορετικά µια διαδικασία χρήστη θα µπορούσε να περάσει από κατάσταση
χρήστη σε κατάσταση επόπτη και στη συνέχεια να αποκτήσει πλήρη έλεγχο στο
σύστηµα.
στ) Οι λειτουργίες Εισόδου/Εξόδου πρέπει να είναι προνοµιακές εντολές για τους
λόγους που συζητήθηκαν στην ενότητα «Προστασία Ε/Ε». □

1.8 Βιβλιογραφικές Αναφορές


Στο κεφάλαιο αυτό παρουσιάστηκε µια συνοπτική µόνο περιγραφή των βασικότερων
εννοιών ενός ΛΣ. Πολλά κλασικά βιβλία των λειτουργικών συστηµάτων [BH73, D90,
N00, SGG05, St03, TanΙ] παρέχουν αναλυτικότερες και πιο πλήρεις περιγραφές των
εννοιών που παρουσιάστηκαν εδώ καθώς και άλλων που δεν κρίθηκε σκόπιµο να
αναφερθούν.
Οι πρώτες προσπάθειες κατασκευής ΛΣ που χρησιµοποιούσαν τις τεχνικές του
πολυπρογραµµατισµού και του διαµοιρασµού χρόνου έγιναν στη δεκαετία του 1960-’70.
Η τεχνική του πολυπρογραµµατισµού εφαρµόστηκε αρχικά σε συστήµατα δέσµης. Τα
συστήµατα διαµοιρασµού χρόνου προτάθηκαν στην εργασία [S59], αλλά πατέρας των
συστηµάτων αυτών συχνά θεωρείται ο Fernando Corbató [CDD62] που ανέπτυξε το
πρώτο τέτοιο σύστηµα και µελέτησε τη λειτουργία του.
Όλα τα εισαγωγικά βιβλία της αρχιτεκτονικής υπολογιστών [St00, Tan90], αλλά και τα
περισσότερα βιβλία της εισαγωγής στους υπολογιστές [GL96, N00] καλύπτουν την ύλη
της Ενότητας 1.
Βασικά θέµατα των κατανεµηµένων λειτουργικών συστηµάτων καλύπτονται στα βιβλία
[G00, TanΙΙ]. Κλασικά βιβλία των κατανεµηµένων συστηµάτων θεωρούνται τα [CDK01,
M93, TanS06], ενώ του κατανεµηµένου υπολογισµού τα [AW97, HS07, Lyn96, M93,
T94].

2η έκδοση 22
2ο Κεφάλαιο
∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

2.1 ∆ιεργασίες
Ο επικρατέστερος ορισµός της έννοιας της διεργασίας είναι πως είναι ένα πρόγραµµα
που εκτελείται. Στον Τόµο Γ της θεµατικής ενότητας παρουσιάζονται πολλοί ακόµη
“ισοδύναµοι” ορισµοί για την έννοια αυτή (εκεί χρησιµοποιείται ο όρος διαδικασία αντί
του όρου διεργασία).
Η κύρια διαφορά µιας διεργασίας από ένα πρόγραµµα είναι πως η διεργασία είναι µια
ενεργή οντότητα, ενώ το πρόγραµµα παθητική. Για να γίνει πιο κατανοητή η διαφορά
µεταξύ διεργασίας και προγράµµατος, ας φανταστούµε έναν πληροφορικάριο ο οποίος
κάποια χρονική στιγµή συναρµολογεί έναν υπολογιστή. Έχει µπροστά του το εγχειρίδιο
συναρµολόγησης και τα κοµµάτια του υπολογιστή και εκτελεί την συναρµολόγηση βάσει
του εγχειριδίου. Στο παράδειγµά µας, ο πληροφορικάριος είναι ο επεξεργαστής (δηλαδή η
ΚΜΕ), το εγχειρίδιο είναι το πρόγραµµα, και τα κοµµάτια του υπολογιστή είναι η
είσοδος στο πρόγραµµα. Η έξοδος του προγράµµατος θα είναι ο συναρµολογηµένος
υπολογιστής. ∆ιεργασία είναι όλη η διαδικασία συναρµολόγησης του νέου υπολογιστή.
Περιλαµβάνει το πρόγραµµα, την είσοδο, την έξοδο, τον αριθµό της εντολής του
εγχειριδίου που εκτελείται κάθε χρονική στιγµή, κλπ. Αντίθετα, το πρόγραµµα, στο
παράδειγµα µας, αποτελείται απλώς από το εγχειρίδιο (το οποίο µπορεί να βρίσκεται στη
βιβλιοθήκη του πληροφορικάριου για µήνες, χωρίς να διαδραµατίζει κανένα ενεργό
ρόλο, ή µπορεί να βρίσκεται αποθηκευµένο σε ένα αρχείο). Ένα πρόγραµµα µπορεί και
να µην εκτελεστεί ποτέ.
Έστω τώρα πως τη στιγµή της συναρµολόγησης, ο διευθυντής της εταιρίας στην οποία
δουλεύει ο πληροφορικάριος του ζητά βοήθεια για να εγκαταστήσει ένα νέο εκτυπωτή
στο γραφείο του. Στο σηµείο αυτό, ο πληροφορικάριος είναι σαν να δέχεται µια διακοπή.
∆εδοµένου ότι η εγκατάσταση του εκτυπωτή του διευθυντή είναι διεργασία µεγαλύτερης
προτεραιότητας, ο πληροφορικάριος αναστέλλει προσωρινά τη συναρµολόγηση του
υπολογιστή, φροντίζοντας να θυµάται το σηµείο από όπου θα συνεχίσει όταν επιστρέψει
και ξεκινά να εκτελεί τη νέα διεργασία που προέκυψε.

2.2 Καταστάσεις ∆ιεργασιών


Όπως έχει ήδη αναφερθεί, πολλές διεργασίες µπορεί να είναι έτοιµες να εκτελεστούν,
αλλά µόνο µία είναι αυτή που απασχολεί κάθε χρονική στιγµή την ΚΜΕ. Η διεργασία
αυτή λέγεται εκτελούµενη ή τρέχουσα. Οι διεργασίες που είναι έτοιµες να εκτελεστούν
όταν η ΚΜΕ γίνει διαθέσιµη, λέγονται έτοιµες ή εκτελέσιµες, ενώ οι υπόλοιπες που
περιµένουν κάποιο άλλο συµβάν (π.χ., την ολοκλήρωση µιας λειτουργίας Ε/Ε) λέγονται
υπό αναστολή ή µπλοκαρισµένες. Εποµένως, υπάρχουν τρεις βασικές καταστάσεις στις
οποίες µπορεί να βρίσκεται µια διεργασία: εκτελούµενη, έτοιµη ή υπό αναστολή.
Το Σχήµα 3 αναπαριστά το γράφηµα καταστάσεων µιας διεργασίας. Κάθε διεργασία
αρχικά είναι σε κατάσταση έτοιµη. Όταν της αποδοθεί η ΚΜΕ, γίνεται εκτελούµενη.
Ενόσω εκτελείται, µπορεί να θελήσει να εκτελέσει κάποια λειτουργία Ε/Ε, οπότε
µεταπίπτει σε κατάσταση υπό αναστολή (και να είχε την ΚΜΕ δεν θα µπορούσε να την
χρησιµοποιήσει πριν την περάτωση της λειτουργίας Ε/Ε). Κάποια στιγµή αργότερα, που
η λειτουργία Ε/Ε θα ολοκληρωθεί, η διεργασία θα ξαναγίνει έτοιµη και θα

2η έκδοση 24
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

ξαναεκτελεστεί αργότερα στην ΚΜΕ. Εναλλακτικά, ενόσω εκτελείται, η διεργασία


µπορεί να διακοπεί από τον χρονοδροµολογητή, ο οποίος αποφασίζει πως η διεργασία
έχει ήδη εκτελεστεί για αρκετά µεγάλο χρονικό διάστηµα και θα πρέπει να παραχωρήσει
την ΚΜΕ σε κάποια άλλη διεργασία. Στην περίπτωση αυτή, η κατάσταση της διεργασίας
αλλάζει από εκτελούµενη σε έτοιµη. Η κατάσταση µιας διεργασίας είναι σύνηθες να
αλλάζει ένα µεγάλο αριθµό φορών από την εισαγωγή της στο σύστηµα µέχρι τον
τερµατισµό της. Το Σχήµα 3 περιγράφει τις καταστάσεις µιας διεργασίας καθώς και τις
δυνατές µεταβάσεις από µια κατάσταση σε κάποια άλλη.
αν η εκτελούµενη διεργασία
τερµατίσει εξέρχεται του
συστήµατος

εκτελούµενη

ο δροµολογητής µπορεί
αν η εκτελούµενη διεργασία ζητήσει
να εναλάσσει διεργασίες
την εκτέλεση Ε/Ε ή αναµένει κάποιο
από και προς την ΚΜΕ
γεγονός, θα ανασταλεί

έτοιµη ή µπλοκαρισµένη ή
εκτελέσιµη υπό αναστολή
όταν η Ε/Ε (ή το γεγονός)
πραγµατοποιηθεί θα προκληθεί
διακοπή και η διεργασία θα
ξαναγίνει εκτελέσιµη

κάθε νέα διεργασία που


εισέρχεται στο σύστηµα
είναι εκτελέσιµη

Σχήµα 3: Γράφηµα καταστάσεων διεργασίας.

2.3 Το Μπλοκ Ελέγχου ∆ιεργασιών


Η εκτελούµενη διεργασία χρησιµοποιεί τους καταχωρητές του συστήµατος, ενώ
ενδεχόµενα έχει υπό την κατοχή της και άλλους πόρους. Ο µετρητής προγράµµατος
περιέχει τη διεύθυνση της επόµενης εντολής που πρέπει να εκτελεστεί από τη διεργασία
(έστω ότι η διεργασία ονοµάζεται Α). Προφανώς, αν µια νέα διεργασία (έστω Β) την
αντικαταστήσει στην ΚΜΕ, θα χρησιµοποιήσει και αυτή µε αντίστοιχο τρόπο τους
καταχωρητές και τους υπόλοιπους πόρους του συστήµατος, καταστρέφοντας τις
πληροφορίες που είχαν αποθηκευθεί εκεί από την Α. Το ΛΣ θα πρέπει εποµένως να
διατηρήσει αρκετές πληροφορίες για την Α, πριν την αντικαταστήσει στην ΚΜΕ, ώστε
όταν αποδοθεί η ΚΜΕ στη Β, να εξακολουθήσει να είναι εφικτή η επανεκτέλεση της Α
κάποια χρονική στιγµή στο µέλλον από το σηµείο ακριβώς που διακόπηκε. (Ας
θυµηθούµε το παράδειγµα µε τον πληροφορικάριο, ο οποίος θα πρέπει να θυµάται το
σηµείο στο οποίο διακόπηκε η συναρµολόγηση του υπολογιστή, προκειµένου να τη

2η έκδοση 25
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

συνεχίσει αργότερα. Έτσι, και το ΛΣ θα πρέπει να «θυµάται» το «σηµείο» στο οποίο


διακόπηκε η Α για να µπορέσει να συνεχίσει την εκτέλεσή της αργότερα).
Το λειτουργικό σύστηµα διατηρεί για κάθε διεργασία µια δοµή δεδοµένων που
ονοµάζεται Μπλοκ Ελέγχου ∆ιεργασίας (Process Control Block, PCB). Κάθε διεργασία
έχει το δικό της ξεχωριστό PCB. Το PCB µιας διεργασίας συνήθως περιέχει τις
ακόλουθες πληροφορίες:
• Περιεχόµενα µετρητή προγράµµατος και άλλων καταχωρητών.
• Κατάσταση διεργασίας.
• ∆ιάφορες παραµέτρους χρονοδροµολόγησης.
• Ταυτότητα διεργασίας και άλλα στοιχεία ταυτοποίησης της διεργασίας.
• Χρόνος εκκίνησης της διεργασίας, χρόνος χρήσης της ΚΜΕ και άλλα λογιστικά
στοιχεία.
• ∆είκτες που καταγράφουν τις διευθύνσεις µνήµης στις οποίες βρίσκεται ο κώδικας,
τα δεδοµένα και η στοίβα της διεργασίας.
• ∆είκτες που καταγράφουν τους πόρους που έχει στην κατοχή της η διεργασία (π.χ.,
ανοιχτά αρχεία, συσκευές Ε/Ε, κλπ).
Το PCB µιας διεργασίας φαίνεται στο Σχήµα 4.

Κατάσταση διεργασίας
Στοιχεία ταυτοποίησης
Περιεχόµενα µετρητή προγράµµατος και άλλων
καταχωρητών
Παράµετροι χρονοδροµολόγησης
Χρήσιµες διευθύνσεις και όρια µνήµης
Λίστα ανοιχτών αρχείων
Χρησιµοποιούµενοι πόροι
Λογιστικά στοιχεία και άλλες χρήσιµες
πληροφορίες

Σχήµα 4: Το PCB µιας διεργασίας.

2.4 Λειτουργίες επί ∆ιεργασιών


Οι πιο συνηθισµένες λειτουργίες που µπορούν να εκτελεστούν επί των διεργασιών είναι:
• ∆ηµιουργία διεργασίας. Με την εκκίνηση του ΛΣ δηµιουργούνται αυτόµατα µερικές
διεργασίες. Κάποιες από αυτές εκτελούνται στο προσκήνιο (foreground) και
ενδεχόµενα αλληλεπιδρούν µε τους χρήστες, ενώ κάποιες άλλες εκτελούνται στο

2η έκδοση 26
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

παρασκήνιο (background). Κάποιες από αυτές που εκτελούνται στο παρασκήνιο


ονοµάζονται δαίµονες (daemons).
Επίσης, διεργασίες δηµιουργούνται κατά την εκκίνηση των προγραµµάτων του
χρήστη. Τα περισσότερα ΛΣ παρέχουν µια ειδική κλήση συστήµατος που επιτρέπει
σε µια διεργασία να δηµιουργήσει άλλες διεργασίες. Οι διεργασίες αυτές
ονοµάζονται θυγατρικές, ενώ αυτή που τις δηµιούργησε ονοµάζεται γονική
διεργασία. Κάθε µια από τις θυγατρικές διεργασίες µπορεί να δηµιουργήσει άλλες
θυγατρικές διεργασίες και έτσι δηµιουργείται µια ιεραρχική δενδροειδής δοµή
διεργασιών.
• Τερµατισµός διεργασίας. Εκτός από τον κανονικό τερµατισµό µιας διεργασίας που
συµβαίνει όταν εκτελεστεί ο κώδικάς της, το ΛΣ µπορεί να αποφασίσει να τερµατίσει
µια διεργασία για άλλους λόγους, π.χ., για λόγους προστασίας. Όταν µια διεργασία
τερµατίζει, το PCB της καταστρέφεται και όλοι οι πόροι που κατείχε
ελευθερώνονται.
• Αναστολή-Επανενεργοποίηση διεργασίας. Μια διεργασία µπορεί να ανασταλεί επειδή
π.χ., περιµένει την εκτέλεση κάποιας λειτουργίας Ε/Ε. Μια διεργασία συνήθως
αναστέλλεται κάθε φορά που καλεί µια κλήση συστήµατος. Η επανενεργοποίηση της
διεργασίας είναι αποκλειστική ευθύνη του ΛΣ και γίνεται µόνο µετά από ενέργειες
που πραγµατοποιούνται από αυτό.
• ∆ροµολόγηση διεργασίας. Όπως έχει ήδη αναφερθεί, κάθε ΛΣ παρέχει ένα µηχανισµό
δροµολόγησης διεργασιών. Η δροµολόγηση των διεργασιών επηρεάζει σηµαντικά
την απόδοση ενός συστήµατος. Εποµένως, η σχεδίαση καλών αλγορίθµων
δροµολόγησης αποτελεί ένα σηµαντικό πρόβληµα των ΛΣ.

2.5 ∆ιακοπές
Μια διακοπή µπορεί να προκληθεί για πολλούς λόγους. Οι πιο σηµαντικοί από αυτούς
παρατίθενται στη συνέχεια:
• ∆ιακοπές κλήσεις επόπτη. Πραγµατοποιούνται κάθε φορά που µια διεργασία καλεί
µια κλήση συστήµατος.
• ∆ιακοπές ελέγχου προγράµµατος. Πραγµατοποιούνται κάθε φορά που η εκτέλεση ενός
προγράµµατος οδηγεί σε λάθη, όπως π.χ., στην περίπτωση διαίρεσης µε το µηδέν.
• ∆ιακοπές που προκαλούνται από το ρολόι του συστήµατος, διακοπές ελέγχου του
υλικού του συστήµατος, κ.α.
• ∆ιακοπές Ε/Ε. Είναι διακοπές που προκαλούνται από τις συσκευές Ε/Ε, όταν µια
λειτουργία Ε/Ε περατωθεί, όταν συµβεί ένα λάθος, όταν ένα περιφερειακό είναι
έτοιµο για λειτουργία, κλπ.
• ∆ιακοπές που προκαλούνται από τον χρήστη µε την πίεση κάποιων πλήκτρων, όπως
π.χ., το restart και το INT.
∆ιακοπές µπορεί να προκληθούν για πολλούς ακόµη λόγους (οι παραπάνω κατηγορίες
δεν είναι εξαντλητικές). ∆εδοµένου του κώδικα που εκτελεί µια διεργασία, είναι πολύ

2η έκδοση 27
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

σηµαντικό να γίνει σαφές ποια είναι τα δυνατά σηµεία διακοπής της εκτέλεσης της
διεργασίας. Στο παρακάτω παράδειγµα γίνεται προσπάθεια να αποσαφηνιστεί το
ερώτηµα αυτό.

Παράδειγµα 1
Θεωρείστε µια διεργασία που εκτελεί τον παρακάτω απλό κώδικα.
int b = 3, c = 4, p = 5;
int tmp, flag = 0;
tmp = 1;
while (p > 0)
begin
if (flag == 0)
tmp = tmp * b;
else
tmp = tmp * c;
flag = 1 – flag;
p = p - 1;
end
print tmp;
Ο παραπάνω κώδικας δεν κάνει κάτι ιδιαίτερα έξυπνο. Απλά υπολογίζει και τυπώνει (µε
λίγο πιο πολύπλοκο τρόπο από ότι ίσως θα έπρεπε) το b*c*b*c*b. Ωστόσο το σηµαντικό
σε αυτό το παράδειγµα δεν είναι να χρησιµοποιήσουµε έναν κώδικα που κάνει κάτι
εξαιρετικά χρήσιµο, αλλά να γίνει κατανοητό ποια είναι τα δυνατά σηµεία διακοπής στην
εκτέλεση του παραπάνω προγράµµατος. Αν το πρόγραµµα εκτελεστεί χωρίς διακοπές, θα
πραγµατοποιηθούν οι 28 λειτουργίες που περιγράφονται στη συνέχεια (µε τη σειρά που
παρουσιάζονται).
1. tmp = 1
2. Έλεγχος της συνθήκης της while (είναι TRUE)
3. Έλεγχος της συνθήκης της if (είναι TRUE)
4. tmp = 1 * 3 = 3
5. flag = 1
6. p = 4
7. Έλεγχος της συνθήκης της while (είναι TRUE)
8. Έλεγχος της συνθήκης της if (είναι FALSE)
9. tmp = 3 * 4 = 12
10. flag = 0
11. p = 3
12. Έλεγχος της συνθήκης της while (είναι TRUE)
13. Έλεγχος της συνθήκης της if (είναι TRUE)
14. tmp = 12 * 3 = 36
15. flag = 1
16. p = 2

2η έκδοση 28
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

17. Έλεγχος της συνθήκης της while (είναι TRUE)


18. Έλεγχος της συνθήκης της if (είναι FALSE)
19. tmp = 36 * 4 = 144
20. flag = 0
21. p = 1
22. Έλεγχος της συνθήκης της while (είναι TRUE)
23. Έλεγχος της συνθήκης της if (είναι TRUE)
24. tmp = 144 * 3 = 432
25. flag = 1
26. p = 0
27. Έλεγχος της συνθήκης της while (είναι FALSE)
28. print 432
Εάν πραγµατοποιηθούν µία ή περισσότερες διακοπές, η διεργασία θα πρέπει και πάλι να
εκτελέσει ακριβώς τις ίδιες λειτουργίες µε αυτές που περιγράφτηκαν πιο πάνω. Το
γεγονός ότι µπορεί να συµβούν διακοπές, κατά τη διάρκεια εκτέλεσης µιας διεργασίας,
δεν πρέπει να επιφέρει την επανάληψη εκτέλεσης κάποιων λειτουργιών της διεργασίας,
ούτε την παράλειψη κάποιων από αυτές.
∆ιακοπή µπορεί να συµβεί πριν η µετά από κάθε µια από τις παραπάνω λειτουργίες. Η
διεργασία µπορεί να διακοπεί πριν καν προλάβει να εκτελέσει την πρώτη λειτουργία της.
Ας συζητήσουµε λίγο πιο αναλυτικά την εκτέλεση των δοµηµένων προτάσεων, όπως
π.χ., της if else και της while. Η εκτέλεση της while ξεκινά µε την εκτέλεση της
λειτουργίας 2 και τελειώνει µε την εκτέλεση της λειτουργίας 27. Υπάρχουν 25
διαφορετικά σηµεία στα οποία η διεργασία µπορεί να διακοπεί κατά τη διάρκεια
εκτέλεσης της while. Το πρώτο από αυτά είναι µετά την εκτέλεση της λειτουργίας 2 και
πριν την εκτέλεση της 3, το δεύτερο µεταξύ των λειτουργιών 3 και 4, κ.ο.κ., ενώ το
τελευταίο είναι µετά την εκτέλεση της λειτουργίας 26 και πριν την εκτέλεση της
λειτουργίας 27. Προσέξτε ότι ο έλεγχος µιας συνθήκης θεωρείται ξεχωριστή λειτουργία
και µια διεργασία µπορεί να διακοπεί αµέσως µετά τον έλεγχο κάποιας συνθήκης, αλλά
πριν την εκτέλεση της επόµενης εντολής. Π.χ., αν η διεργασία διακοπεί αφού γίνει ο
έλεγχος της γραµµής 17, η επόµενη εντολή που θα πρέπει να εκτελεστεί, όταν η
διεργασία ανακτήσει και πάλι τον έλεγχο της ΚΜΕ, είναι η εντολή if else. Το ΛΣ θα
πρέπει να θυµάται, ότι έκανε αποτίµηση της συνθήκης της while σε TRUE, ώστε να µην
επαναλάβει τη λειτουργία αυτή, αλλά να εκτελέσει την επόµενη εντολή που είναι η if
else (και πιο συγκεκριµένα η λειτουργία αποτίµησης της συνθήκης της if else της
γραµµής 18). Προφανώς, είναι δυνατόν, η διεργασία να διακοπεί αφού αποτιµήσει τόσο
τη συνθήκη της while, όσο και τη συνθήκη της if. Η περίπτωση αυτή θα συνέβαινε αν η
διακοπή λάµβανε χώρα π.χ., µετά την εκτέλεση και της λειτουργίας 18. Και σε αυτή την
περίπτωση το ΛΣ πρέπει να θυµάται αν θα συνεχίσει µε το µπλοκ προτάσεων του if ή µε
εκείνο του else, όταν η διεργασία αποκτήσει και πάλι τον έλεγχο της KME. □
Είναι σηµαντικό να γίνει κατανοητό, τι ενέργειες πραγµατοποιούνται όταν συµβαίνει µια
διακοπή. Το ΛΣ περιλαµβάνει ρουτίνες που είναι γνωστές ως χειριστές διακοπών
(interrupt handlers). Κάθε µια από αυτές είναι υπεύθυνη για την επεξεργασία κάποιου

2η έκδοση 29
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

είδους διακοπής. Κάθε φορά που προκαλείται µια διακοπή, οι ενέργειες που πρέπει να
πραγµατοποιηθούν περιγράφονται συνοπτικά στη συνέχεια:
• Ο µετρητής προγράµµατος και άλλοι χρήσιµοι καταχωρητές αποθηκεύονται
(συνήθως στο PCB της διεργασίας που ήταν η εκτελούµενη όταν συνέβη η διακοπή).
• Το είδος της διακοπής αποσαφηνίζεται και καλείται ο κατάλληλος χειριστής
διακοπής.
• Όταν ο χειριστής διακοπής ολοκληρώσει τις λειτουργίες που πρέπει να επιτελέσει,
αρχίζει να εκτελείται ο χρονοδροµολογητής που αποφασίζει ποια θα είναι η επόµενη
προς εκτέλεση διεργασία.
• Τέλος, ξεκινά η εκτέλεση της διεργασίας στην οποία αποδόθηκε η ΚΜΕ.

Παράδειγµα 2
Ας εστιάσουµε και πάλι στον (ψευδο-) κώδικα του Παραδείγµατος 2. Είναι γραµµένος σε
µορφή που θυµίζει κώδικα κάποιας σύγχρονης γλώσσας προγραµµατισµού (αφού
περιέχει δοµηµένες προτάσεις, όπως π.χ., η while και η if else). Προκειµένου να
εκτελεστεί, ο κώδικας θα πρέπει να µετατραπεί σε µορφή που να θυµίζει περισσότερο
γλώσσα µηχανής. Μια τέτοια µορφή παρουσιάζεται στη συνέχεια:
2012 b = 3;
2016 c = 4;
2020 p = 5;
2024 flag = 0;
2028 tmp = 1;
2032 label1:
2036 if (p <= 0) goto label2;
2040 if (flag == 0) goto label3;
2044 tmp = tmp * c;
2048 goto label4;
2052 label3:
2056 tmp = tmp * b;
2060 label4:
2064 flag = 1 – flag;
2068 p = p – 1;
2072 goto label1;
2076 label2:
2080 print tmp;
Κάθε ένας από τους αριθµούς που προηγούνται των εντολών στον κώδικα αποτελεί τη
θέση µνήµης από όπου διαβάζεται αυτή η εντολή (δηλαδή ο κώδικας της διεργασίας
βρίσκεται στις διευθύνσεις µνήµης 2012-2080, ενώ στη διεύθυνση µνήµης 2056
βρίσκεται η εντολή «tmp = tmp * b;»).
Ο αναγνώστης θα πρέπει να επενδύσει τον απαραίτητο χρόνο για να κατανοήσει γιατί
όταν ο παραπάνω κώδικας εκτελείται, επιτελεί ακριβώς τις ίδιες λειτουργίες µε εκείνες
που περιγράφονται στο Παράδειγµα 2 (είναι δηλαδή ισοδύναµος µε τον κώδικα του
Παραδείγµατος 2). Η µορφή κώδικα που περιγράφεται εδώ επιτρέπει να γίνει

2η έκδοση 30
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

περισσότερο κατανοητό γιατί οι έλεγχοι των δοµηµένων προτάσεων αποτελούν


ξεχωριστές λειτουργίες (όπως αναφέρθηκε στο Παράδειγµα 2).
Έστω ότι ξεκινά η εκτέλεση του προγράµµατος και ακριβώς πριν την πρώτη εκτέλεση
της εντολής στη θέση 2064, η διεργασία διακόπτεται. Στο σηµείο αυτό ισχύουν τα
ακόλουθα:
PC (program counter) = 2064, tmp = 3, flag = 0, p = 5, b = 3, c = 4.
Προκειµένου να είναι δυνατή η επανεκτέλεση της διεργασίας κάποια στιγµή στο µέλλον,
θα πρέπει το ΛΣ να “θυµάται” όλες τις παραπάνω τιµές. Έτσι, θα πρέπει αυτές να
φυλαχθούν στο PCB της διεργασίας. Στην πραγµατικότητα, τα πράγµατα είναι πιο
σύνθετα. Στο PCB δεν φυλάσσονται τιµές µεταβλητών, αφού αυτές φυλάσσονται στη
µνήµη, αλλά τιµές καταχωρητών του επεξεργαστή. Φυλάσσονται επίσης πληροφορίες για
το ποιες είναι οι διευθύνσεις µνήµης της διεργασίας, ώστε να µη χαθεί η πρόσβαση στις
σωστές µεταβλητές. Το βασικό σηµείο ωστόσο είναι να κατανοήσουµε ότι η
επανεκτέλεση µιας διεργασίας που είχε διακοπεί, προϋποθέτει τη λήψη µιας ακριβούς
“φωτογραφίας” της κατάστασης της διεργασίας την ώρα της διακοπής.
Ας θεωρήσουµε τώρα ότι συµβαίνει και πάλι διακοπή, αυτή τη φορά ακριβώς πριν τη
δεύτερη εκτέλεση της εντολής που βρίσκεται στη διεύθυνση µνήµης 2072. Στο σηµείο
αυτό ισχύουν τα ακόλουθα:
PC = 2072, tmp = 12, flag = 0, p = 3, b = 3, c = 4.
Τα περιεχόµενα του PC αλλά και κάποιες από τις τιµές των µεταβλητών (εκείνες που
φαίνονται µε έντονα γράµµατα) είναι τώρα διαφορετικές. Το ΛΣ θα πρέπει και πάλι να
αποθηκεύσει κατάλληλες πληροφορίες στο PCB της διεργασίας, προκειµένου να είναι σε
θέση να επανεκτελέσει την διεργασία αργότερα. □

Παράδειγµα 3 (Καλλές, Σγάρµπας, Ταµπακάς, «Σηµαντικά Σηµεία στη µελέτη του τόµου
Λειτουργικά Συστήµατα Ι της ΘΕ ΠΛΗ-11: Αρχές Τεχνολογίας Λογισµικού)
Έστω µια διεργασία που στόχος της είναι να πληροφορεί το χρήστη για το ρυθµό µε τον
οποίο αυξοµειώνεται το πλήθος όλων των διεργασιών που εξυπηρετούνται από το ΛΣ σε
κάποιο χρονικό διάστηµα. Έστω ότι ο αριθµός των διεργασιών που εκτελούνται την
τρέχουσα χρονική στιγµή στο σύστηµα δίνεται από τη συνάρτηση count-processes(). Το
πρόγραµµα που περιγράφεται στη συνέχεια επιτελεί 100 δειγµατοληψίες (δηλαδή καλεί
100 φορές τη συνάρτηση count-processes()) και αναφέρει αν ο αριθµός των διεργασιών
παρουσιάζει αύξηση ή µείωση από την τελευταία δειγµατοληψία, καθώς και τον
τρέχοντα αριθµό διεργασιών στο σύστηµα. Όταν εκτελείται αποτελεί και αυτό µια από
τις διεργασίες του συστήµατος.
Το πρόγραµµα δίνεται στη συνέχεια σε µορφή που είναι αρκετά κοντά σε µια
αναπαράσταση σε επίπεδο µηχανής (δηλαδή δεν περιέχει εντολές ανακύκλωσης, όπως
π.χ., while, repeat, for, κλπ.)
1004 print “Start”;
1008 i = 0;
1012 old_proc = 0;
1016 label1:
1020 if (i == 100)

2η έκδοση 31
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

1024 goto label2;


1028 new_proc = count_processes ();
1032 if (new_proc > old_proc)
1036 print “Up”;
1040 else if (old_proc > new_proc)
1044 print “Down”;
1048 print new_proc;
1052 old_proc = new_proc;
1056 i ++;
1060 goto label1:
1064 label2:
1068 print “End”;
Ας υποθέσουµε ότι οι πρώτες κλήσεις στην count_processes θα επέστρεφαν τους
αριθµούς 37, 41, 21, κλπ.
Έστω ότι η εκτέλεση του προγράµµατος ξεκινά από τη θέση µνήµης 1004 (όπου δείχνει
ο µετρητής προγράµµατος) και συνεχίζεται µέχρι και τη θέση µνήµης 1032. Πριν
εκτελεστεί η εντολή στη διεύθυνση 1036, η διαδικασία µας διακόπτεται και παύει να
χρησιµοποιεί την ΚΜΕ. Όταν αποκτηθεί και πάλι ο έλεγχος της ΚΜΕ από τη διεργασία,
πρέπει να ξέρουµε όχι µόνο το τρέχον σηµείο εκτέλεσης, αλλά και τη µορφή των
δεδοµένων τη στιγµή της διακοπής. Προκειµένου να επιτευχθεί αυτό, θα πρέπει να
φυλαχτούν στο PCB της διεργασίας πληροφορίες της µορφής:
Μετρητής = 1036, i = 0, new_proc = 37, old_proc = 0
Ας υποθέσουµε ότι, µετά την επανεκκίνηση, συµβαίνει και πάλι διακοπή πριν την
εκτέλεση της εντολής που βρίσκεται στη θέση 1052. Τότε στο PCB θα πρέπει να
φυλαχτούν οι ακόλουθες πληροφορίες:
Μετρητής = 1052, i = 0, new_proc = 37, old_proc = 0
Αν η διακοπή γινόταν πριν την εκτέλεση της εντολής που βρίσκεται στη διεύθυνση 1056,
τότε θα είχαµε
Μετρητής = 1056, i = 0, new_proc = 37, old_proc = 37
Και σε αυτό το παράδειγµα τα πράγµατα είναι πιο σύνθετα για λόγους αντίστοιχους
εκείνων που συζητήθηκαν στο προηγούµενο παράδειγµα. Ωστόσο, όπως
προαναφέρθηκε, το σηµαντικό είναι να γίνει κατανοητό πως η οµαλή επανατοποθέτηση
µιας διεργασίας στην ΚΜΕ απαιτεί την αποθήκευση διάφορων χρήσιµων πληροφοριών
τη στιγµή που γίνεται η διακοπή. □

2.6 Χρονοδροµολόγηση
Μια πολύ σηµαντική δοµή δεδοµένων που διατηρείται από το ΛΣ είναι η ουρά έτοιµων
διεργασιών (ready queue) ή ουρά εκτέλεσης. Η ουρά αυτή περιέχει τα PCB των
διεργασιών που είναι έτοιµες προς εκτέλεση. Μια ουρά έτοιµων διεργασιών για ένα
σύστηµα µε 5 έτοιµες διεργασίες Α, Β, Γ, ∆ και Ε αναπαρίσταται στο Σχήµα 5.

2η έκδοση 32
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

τέλος

PCB A PCB B PCB Γ PCB ∆ PCB Ε

Σχήµα 5: Η ουρά έτοιµων διεργασιών.


Η αρχική κατάσταση κάθε διεργασίας είναι έτοιµη. Εποµένως, το PCB κάθε διεργασίας
που εκκινείται εισάγεται στην ουρά έτοιµων διεργασιών. Είναι αξιοσηµείωτο ότι παρά το
όνοµά της, η υλοποίηση της ουράς έτοιµων διεργασιών δεν γίνεται πάντα µε µια απλή
ουρά FIFO.
Ο χρονοδροµολογητής διαλέγει µια από τις διεργασίες της ουράς έτοιµων διεργασιών για
να εκτελεστεί στην ΚΜΕ. Ο χρονοδροµολογητής εκτελείται µετά από κάποιο από τα
ακόλουθα γεγονότα:
• Η εκτελούµενη διεργασία τερµατίζει ή αναστέλλεται προκειµένου να επιτελέσει
κάποια λειτουργία Ε/Ε.
• Μια νέα διεργασία εισέρχεται στο σύστηµα.
• Προκαλείται διακοπή.
Μια διεργασία που εκτελείται µπορεί είτε να ανασταλεί (περιµένοντας π.χ., την
περάτωση πρόσβασης σε κάποιο πόρο), ή να διακοπεί (παρότι θα µπορούσε να συνεχίσει
την εκτέλεσή της αν εξακολουθούσε να κατέχει την ΚΜΕ), ή να τερµατίσει. Στην πρώτη
περίπτωση, ο διαχειριστής συσκευών Ε/Ε αναλαµβάνει δράση. Στην δεύτερη, η
κατάσταση της διεργασίας µετατρέπεται από εκτελούµενη σε έτοιµη και έτσι εισάγεται
στην ουρά των έτοιµων διεργασιών, ενώ στην τρίτη περίπτωση, το PCB της διεργασίας
καταστρέφεται. Το Σχήµα 6 παρέχει µια σχηµατική περιγραφή της χρονοδροµολόγησης
διεργασιών.
Υπάρχουν δύο µεγάλες κατηγορίες αλγορίθµων χρονοδροµολόγησης, οι προεκχωρητικοί
και οι µη-προεκχωρητικοί. Οι µη-προεκχωρητικοί αλγόριθµοι, αφού διαλέξουν τη
διεργασία που θα εκτελεστεί, της επιτρέπουν να κρατήσει την ΚΜΕ µέχρι είτε να
ανασταλεί (π.χ., γιατί περιµένει την περάτωση λειτουργιών πρόσβασης σε πόρους) ή να
τερµατίσει. Με άλλα λόγια από τη στιγµή που η ΚΜΕ αποδίδεται από τον
χρονοδροµολογητή σε µια διεργασία, η αποδέσµευση της ΚΜΕ και η επανα-απόδοσή της
σε άλλη διεργασία θα γίνει µόνο αν η πρώτη διεργασία δεν την χρειάζεται άλλο. Ένας
µη-προεκχωρητικός αλγόριθµος αγνοεί εποµένως τις διακοπές που προκαλούνται από
τον µηδενισµό του µετρητή του συστήµατος. Αντίθετα, σε έναν προεκχωρητικό
αλγόριθµο, η εκτέλεση της διεργασίας που κατέχει την ΚΜΕ διακόπτεται περιοδικά
(ανεξάρτητα µε το αν αυτή χρειάζεται να κάνει περαιτέρω υπολογισµούς ή όχι) και ο
χρονοδροµολογητής καλείται να αποφασίσει ποια θα είναι η επόµενη προς εκτέλεση

2η έκδοση 33
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

διεργασία. Εάν ο χρονοδροµολογητής αποφασίσει πως θα πρέπει να αποδώσει


περισσότερο χρόνο στην εκτελούµενη τη στιγµή της διακοπής διεργασία, αυτή θα
συνεχίσει να εκτελείται. Ωστόσο, µπορεί (και είναι και το σύνηθες) ο
χρονοδροµολογητής να αποφασίσει πως έχει έρθει η σειρά µιας άλλης διεργασίας για να
εκτελεστεί. Τότε, το PCB της εκτελούµενης τη στιγµή της διακοπής διεργασίας
τοποθετείται στην κατάλληλη ουρά (ανάλογα µε την κατάσταση της διεργασίας) και
εκτελούµενη γίνεται η νέα διεργασία που επιλέχθηκε.

είσοδος νέας
διεργασίας

τέλος κβάντου
χρόνου
... διακοπή λόγω τερµατισµού
λειτουργίας Ε/Ε
ουρά έτοιµων διεργασία 3
διεργασιών
διεργασία 2

διεργασία 1 Ε/Ε

δροµολογητής διαχειριστής
συσκευών Ε/Ε

η εκτελούµενη διεργασία
ζητά πρόσβαση σε
έξοδος ΚΜΕ κάποιο πόρο του
συστήµατος

Σχήµα 6: Σχηµατική περιγραφή της χρονοδροµολόγησης διεργασιών.


Οι στόχοι ενός χρονοδροµολογητή εξαρτώνται από το είδος του συστήµατος στο οποίο
εκτελείται. Οι στόχοι του χρονοδροµολογητή ενός συστήµατος οµαδικής επεξεργασίας
συνήθως είναι διαφορετικοί από εκείνους ενός συστήµατος διαµοιρασµού χρόνου. Στα
συστήµατα οµαδικής επεξεργασίας, ο χρονοδροµολογητής αποβλέπει στο να
ελαχιστοποιήσει τον µέσο χρόνο διεκπεραίωσης. Ο χρόνος διεκπεραίωσης µιας
διεργασίας είναι ο χρόνος που µεσολαβεί από την είσοδό της στο σύστηµα µέχρι την
περάτωση της εκτέλεσής της. Για παράδειγµα αν µια διεργασία εισέρχεται στο σύστηµα
την χρονική στιγµή t1 και τερµατίζει την χρονική στιγµή t2, o χρόνος διεκπεραίωσης της
διεργασίας είναι Χ∆ = t2 – t1 (αφού t2-t1 χρονικές µονάδες παρέµεινε στο σύστηµα). Ο
χρόνος διεκπεραίωσης συµπεριλαµβάνει οποιαδήποτε καθυστέρηση µπορεί να υπέστη η
διεργασία πριν ή και κατά τη διάρκεια εκτέλεσής της. Ο µέσος χρόνος διεκπεραίωσης n
διεργασιών p1, ..., pn, ορίζεται ως

2η έκδοση 34
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

ΜΧ∆ = (Χ∆1 + Χ∆2 + ... + Χ∆n) / n ,


όπου Χ∆1, ..., Χ∆n είναι οι χρόνοι διεκπεραίωσης των διεργασιών p1, ..., pn, αντίστοιχα.
Ο χρόνος αναµονής µιας διεργασίας είναι ο χρόνος που µια διεργασία ξοδεύει στο
σύστηµα χωρίς να κατέχει την ΚΜΕ. Για παράδειγµα, αν µια διεργασία εισέρχεται στο
σύστηµα την χρονική στιγµή t1, τερµατίζει την χρονική στιγµή t2 και καταναλώνει χρόνο
d στην ΚΜΕ, ο χρόνος αναµονής είναι XA = t2–t1–d. Ο µέσος χρόνος αναµονής n
διεργασιών p1, ..., pn, ορίζεται ως
ΜΧΑ = (ΧΑ1 + ΧΑ2 + ... + ΧΑn) / n ,
όπου ΧΑ1, ..., ΧΑn είναι οι χρόνοι αναµονής των διεργασιών p1, ..., pn, αντίστοιχα.
Στα συστήµατα οµαδικής επεξεργασίας είναι σηµαντικό να γίνεται επίσης βέλτιστη
χρήση της ΚΜΕ και να µεγιστοποιείται ο ρυθµός απόδοσης, δηλαδή ο αριθµός των
διεργασιών που διεκπεραιώνονται στη µονάδα του χρόνου. Αντίθετα, σε ένα σύστηµα
διαµοιρασµού χρόνου, ο σηµαντικότερος στόχος είναι να ελαχιστοποιηθεί ο χρόνος
απόκρισης, δηλαδή ο χρόνος που µεσολαβεί από τη χρονική στιγµή που ο χρήστης
υπέβαλε την διεργασία του µέχρι τη χρονική στιγµή που έλαβε κάποια απόκριση από το
σύστηµα.
Θα πρέπει να τονιστεί πως κάποιοι από τους παραπάνω στόχους µπορεί να είναι
αντικρουόµενοι. ∆εδοµένου ότι τα σηµερινά συστήµατα είναι γενικού σκοπού (και άρα
λειτουργούν, ανάλογα µε τις ανάγκες των χρηστών, άλλοτε ως συστήµατα οµαδικής
επεξεργασίας και άλλοτε ως συστήµατα απόκρισης), ο σχεδιασµός ενός καλού
αλγόριθµου χρονοδροµολόγησης είναι επίπονη εργασία.
Υπάρχουν συστήµατα στα οποία οι διεργασίες είναι τόσες πολλές, ώστε δεν είναι εφικτό
(ή δεν είναι αποδοτικό) να βρίσκονται όλες ταυτόχρονα στη µνήµη. Οι διεργασίες που
δεν χωρούν στη µνήµη κρατούνται σε κάποια δοµή δεδοµένων στο δίσκο και ένας
δεύτερος χρονοδροµολογητής, ο χρονοδροµολογητής µνήµης-δίσκου, αποφασίζει σε ποιες
από αυτές θα αποδοθεί η απαραίτητη µνήµη για την εκτέλεση τους. Με άλλα λόγια ένα
σύστηµα µπορεί να έχει περισσότερους από έναν χρονοδροµολογητές και περισσότερες
από µια ουρές χρονοδροµολόγησης. Το Σχήµα 7 αναπαριστά το είδος της
χρονοδροµολόγησης που παρέχεται από τους χρονοδροµολογητές ΚΜΕ και µνήµης-
δίσκου. Συχνά, παρόµοιοι αλγόριθµοι χρονοδροµολόγησης χρησιµοποιούνται και στις
δύο περιπτώσεις.

Χρονοδροµολογητής Χρονοδροµολογητής
µνήµης-δίσκου ΚΜΕ

Μνήµη
∆ίσκος ΚΜΕ

Σχήµα 7: Είδος δροµολόγησης που παρέχεται από τους χρονοδροµολογητές ΚΜΕ και µνήµης-δίσκου.

2η έκδοση 35
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

2.7 Αλγόριθµοι Χρονοδροµολόγησης


Στην ενότητα αυτή παρουσιάζονται οι πιο γνωστοί αλγόριθµοι χρονοδροµολόγησης.

2.7.1 Πρώτη Εισερχόµενη – Πρώτη Εξυπηρετούµενη (First Come –


First Served, FCFS)
Ο αλγόριθµος αυτός δροµολογεί τις διεργασίες µε τη σειρά που εισέρχονται στο
σύστηµα. Η ουρά έτοιµων διεργασιών που χρησιµοποιεί είναι µια ουρά FIFO. ∆ύο
δείκτες δείχνουν ο ένας στην αρχή και ο άλλος στο τέλος της ουράς. Όταν µια διεργασία
εισέρχεται στο σύστηµα τοποθετείται στο τέλος της ουράς. Η επόµενη προς εκτέλεση
διεργασία είναι αυτή που βρίσκεται στην αρχή της ουράς. Ο FCFS είναι µη-
προεκχωρητικός αλγόριθµος. Η εκτελούµενη διεργασία κρατά υπό την κατοχή της την
ΚΜΕ µέχρι είτε να χρειαστεί να επιτελέσει λειτουργίες Ε/Ε ή να τερµατίσει. Στην πρώτη
περίπτωση θα εισαχθεί και πάλι στην ουρά των έτοιµων διεργασιών όταν θα ξαναγίνει
έτοιµη. Η εισαγωγή θα γίνει στο τέλος της ουράς ακριβώς όπως αν η διεργασία είχε
µόλις εισέλθει στο σύστηµα.
Ο FCFS είναι ένας δίκαιος αλγόριθµος χρονοδροµολόγησης. Ωστόσο πολλές φορές δεν
είναι αποδοτικός ως προς τις υπόλοιπες παραµέτρους (χρόνος απόκρισης, µέσος χρόνος
διεκπεραίωσης, ρυθµός απόδοσης). Το πρόβληµα δηµιουργείται αν µια µεγάλη διεργασία
(δηλαδή µια διεργασία µε υψηλές τρέχουσες υπολογιστικές απαιτήσεις) εισέλθει στο
σύστηµα λίγο πριν από µια ή περισσότερες µικρές (ή σύντοµες) διεργασίες (δηλαδή
διεργασίες µε µικρές τρέχουσες υπολογιστικές απαιτήσεις).
Για να γίνει το πρόβληµα πιο κατανοητό, θεωρήστε µια µεγάλη διεργασία Α, που
χρειάζεται να εκτελέσει υπολογισµούς διάρκειας 100 µονάδων του χρόνου της ΚΜΕ πριν
ανασταλεί για Ε/Ε. Θεωρήστε επίσης µια µικρή διεργασία Β που απαιτεί µόλις 1 µονάδα
του χρόνου της ΚΜΕ πριν ανασταλεί για Ε/Ε. Αν η Α εισέλθει στο σύστηµα λίγο
νωρίτερα από την Β, η Α θα ξεκινήσει την εκτέλεσή της πρώτη, καθυστερώντας την
εκτέλεση της Β κατά 100 µονάδες και το σενάριο αυτό ενδεχόµενα να επαναληφθεί
πολλές φορές µέχρι κάποια από τις δύο διεργασίες να τερµατίσει. Ο χρήστης της
διεργασίας Β, που γνωρίζει ότι έχει ζητήσει από το σύστηµα την εκτέλεση µιας σύντοµης
διεργασίας, θα θεωρήσει τις µεγάλες καθυστερήσεις απόκρισης του συστήµατος
απαράδεκτες.
Είναι σηµαντικό µικρές υπολογιστικά διεργασίες να χρονοδροµολογούνται όσο το
δυνατόν συντοµότερα. Υπάρχουν διάφοροι λόγοι για αυτό. Ένας από αυτούς είναι πως
διαφορετικά δεν επιτυγχάνονται καλοί χρόνοι απόκρισης από το σύστηµα. Επιπλέον
όµως, οι µικρές υπολογιστικά διεργασίες, αφού εκτελέσουν τους λίγους υπολογισµούς
τους, θα συνεχίσουν µε λειτουργίες Ε/Ε που είναι εξαιρετικά πιο αργές, ενώ η ΚΜΕ
µπορεί να απασχοληθεί µε τις µεγάλες υπολογιστικά διεργασίες. Με τον τρόπο αυτό, η
ΚΜΕ και οι συσκευές Ε/Ε διατηρούνται απασχοληµένες ταυτόχρονα. Παρατηρήστε ότι η
εκτέλεση της Β πριν την Α επιφέρει χρονική καθυστέρηση µόνο µιας χρονικής µονάδας
στην Α, καθυστέρηση αµελητέα δεδοµένου ότι ο χρόνος απόκρισης της Α δεν µπορεί να
είναι µικρότερος από 100 µονάδες. Θα πρέπει να τονιστεί ωστόσο, πως παρότι η
χρονοδροµολόγηση µικρών διεργασιών πρώτων είναι µια γενικά αποδεκτή τακτική, θα
πρέπει να λαµβάνεται πρόνοια ώστε οι µεγάλες διεργασίες να µην καθυστερούνται επ’

2η έκδοση 36
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

άπειρο. Η καθυστέρηση χρονοδροµολόγησης µιας διεργασίας επ’ άπειρο λέγεται


παρατεταµένη στέρηση. Στην περίπτωση χρονοδροµολόγησης µικρών διεργασιών
πρώτων, παρατεταµένη στέρηση θα µπορούσε να συµβεί, π.χ., αν υπάρχει µια µεγάλη
διεργασία στο σύστηµα αλλά µικρότερες διεργασίες διαρκώς εισέρχονται στο σύστηµα
και της παίρνουν τη σειρά. Χρονοδροµολογητές που µπορεί να προκαλέσουν
παρατεταµένη στέρηση δεν είναι δίκαιοι και άρα δεν θεωρείται πως επιτελούν το έργο
τους µε µεγάλη επιτυχία.

2.7.2 Εκ Περιτροπής (Round Robin, RR)


Ο αλγόριθµος RR συχνά θεωρείται ότι αποτελεί την προεκχωρητική έκδοση του FCFS.
Όπως και ο FCFS, έτσι και ο RR χρησιµοποιεί µια FIFO ουρά. Οι διεργασίες που
εισέρχονται στο σύστηµα τοποθετούνται στο τέλος της ουράς, ενώ η επόµενη προς
εκτέλεση διεργασία είναι αυτή που βρίσκεται στην αρχή της ουράς. Ωστόσο, ο RR
αποφασίζει πως κάθε διεργασία θα εκτελεστεί στην ΚΜΕ µόνο για ένα προκαθορισµένο
χρονικό διάστηµα και στη συνέχεια θα παραχωρήσει τη σειρά της στην επόµενη
διεργασία στην ουρά. Το χρονικό αυτό διάστηµα λέγεται κβάντο χρόνου. Αν το κβάντο
χρόνου της εκτελούµενης διεργασίας τελειώσει χωρίς αυτή να έχει παραχωρήσει την
ΚΜΕ (π.χ., για να εκτελέσει Ε/Ε), ο RR αλλάζει την κατάστασή της σε έτοιµη και την
τοποθετεί στο τέλος της ουράς έτοιµων διεργασιών. Είναι φανερό πως ο αλγόριθµος RR
είναι προεκχωρητικός εξ ορισµού.
Ο αλγόριθµος RR είναι εξαιρετικά δίκαιος, αφού σε όλες τις διεργασίες, όχι µόνο
αποδίδεται περιοδικά το ίδιο µερίδιο χρόνου ΚΜΕ (δηλαδή µερίδιο ίσο µε το κβάντο
χρόνου), αλλά επιπρόσθετα η απόδοση αυτή πραγµατοποιείται βάσει της σειράς άφιξής
των διεργασιών στο σύστηµα. Επίσης, ο αλγόριθµος RR δεν προκαλεί µεγάλες
καθυστερήσεις στην εκτέλεση των µικρών διεργασιών. Ο RR είναι ένας αλγόριθµος που
έχει σχεδιαστεί ειδικά για συστήµατα διαµοιρασµού χρόνου και είναι εξαιρετικά
διαδεδοµένος στις µέρες µας, αφού συναντάται σχεδόν σε όλα τα ΛΣ (κάποιες όµως
φορές σε πιο πολύπλοκες µορφές από αυτήν που περιγράφτηκε πιο πάνω).
Ένα ενδιαφέρον ζήτηµα που ανακύπτει στον αλγόριθµο RR είναι το µέγεθος του κβάντο
χρόνου. Αν το κβάντο χρόνου είναι µεγάλο, µπορεί να προκληθούν µεγάλοι χρόνοι
απόκρισης σε µικρές αλληλεπιδραστικές διεργασίες. Για παράδειγµα, θεωρείστε ότι σε
ένα σύστηµα εισέρχεται µια µικρή διεργασία και τοποθετείται στο τέλος της ουράς
έτοιµων διεργασιών, πίσω από 10 άλλες διεργασίες που απαιτούν χρόνο υπολογισµών
µεγαλύτερο από το κβάντο χρόνου τους. Αν το κβάντο χρόνου είναι 1 sec, η εκτέλεση
της µικρής διεργασίας θα ξεκινήσει µετά από 10 ολόκληρα δευτερόλεπτα. Ο µέσος
χρήστης θα είναι πολύ δυσαρεστηµένος από τόσο αργό χρόνο απόκρισης. Το πρόβληµα
θα περιοριζόταν αρκετά (ή ίσως και θα έπαυε να υπάρχει) αν το κβάντο χρόνου οριστεί
να είναι µικρό. Τότε όµως δηµιουργείται ένα άλλο πρόβληµα. Η αφαίρεση της ΚΜΕ από
µια διεργασία που εκτελείται και η απόδοση της σε κάποια άλλη ονοµάζεται εναλλαγή
διεργασιών (process switch) ή εναλλαγή θέµατος (context switch) και, όπως έχει ήδη
αναφερθεί, απαιτεί την εκτέλεση ενός συνόλου λειτουργιών. Έτσι, η εναλλαγή
διεργασιών επιβαρύνει το σύστηµα (αφού το χρόνο που πραγµατοποιείται, η ΚΜΕ δεν
εκτελεί κάποια διεργασία χρήστη). Αν το κβάντο χρόνου είναι µικρό, εναλλαγές
διεργασιών γίνονται πολύ συχνά, γεγονός που µειώνει την αποδοτικότητα της ΚΜΕ.

2η έκδοση 37
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

2.7.3 Προτεραιοτήτων (Priority)


Η βασική ιδέα είναι να αποδίδεται σε κάθε διεργασία µια προτεραιότητα. Η
προτεραιότητα είναι συνήθως ένας αριθµός. Για παράδειγµα, σε ένα υπολογιστικό
σύστηµα που χρησιµοποιείται από το στρατό οι εργασίες των στρατηγών ενδεχόµενα
έχουν µεγαλύτερη προτεραιότητα από τις εργασίες των λοχαγών, που µε τη σειρά τους
µπορεί να έχουν µεγαλύτερη προτεραιότητα από εκείνες των στρατιωτών, κλπ. Ωστόσο,
η προτεραιότητα µπορεί να καθορίζεται και από εσωτερικούς παράγοντες που
σχετίζονται µε την ίδια τη διεργασία, όπως π.χ., οι απαιτήσεις της σε µνήµη, ο αριθµός
των ανοιχτών αρχείων που χρησιµοποιεί, οι υπολογιστικές της απαιτήσεις, κ.α.
Ο αλγόριθµος προτεραιοτήτων διαλέγει τη διεργασία που έχει την υψηλότερη
προτεραιότητα ως την επόµενη διεργασία προς εκτέλεση. Έτσι, η ουρά έτοιµων
διεργασιών είναι µια ουρά προτεραιότητας. (Υπάρχουν πολλοί διαφορετικοί τρόποι να
υλοποιηθεί µια ουρά προτεραιότητας, οι οποίοι ωστόσο δεν θα µας απασχολήσουν εδώ.)
Κάθε φορά που µια διεργασία εισάγεται στην ουρά, τοποθετείται σε αυτή βάσει της
προτεραιότητάς της. Κάθε εξαγωγή από την ουρά επιστρέφει το στοιχείο εκείνο µε τη
µεγαλύτερη προτεραιότητα. Στην µη-προεκχωρητική έκδοση του αλγορίθµου, η
εκτελούµενη διεργασία κρατά υπό την κατοχή της την ΚΜΕ µέχρι είτε να χρειαστεί να
επιτελέσει λειτουργίες Ε/Ε, ή να τερµατίσει. Στην προεκχωρητική έκδοση του
αλγορίθµου, η εκτελούµενη διεργασία διακόπτεται κάθε φορά που µια διεργασία γίνεται
έτοιµη. Αν η προτεραιότητα της διεργασίας που έγινε έτοιµη είναι µεγαλύτερη από
εκείνη της εκτελούµενης, η διεργασία αυτή καταλαµβάνει την ΚΜΕ, ενώ η εκτελούµενη
διεργασία γίνεται έτοιµη και εισάγεται στην ουρά εκτέλεσης.
Ο αλγόριθµος προτεραιοτήτων (στην απλή µορφή που παρουσιάστηκε παραπάνω)
µπορεί να οδηγήσει σε παρατεταµένη στέρηση. Αν στο σύστηµα εισέρχονται διαρκώς
διεργασίες µε µεγαλύτερες προτεραιότητες από εκείνη κάποιας άλλης διεργασίας, η
διεργασία µε τη χαµηλή προτεραιότητα δεν θα δροµολογηθεί ποτέ. Ωστόσο, κάτι τέτοιο
δεν είναι επιθυµητό. Προκειµένου να επιλυθεί το πρόβληµα αυτό, πολλές φορές ο
χρονοδροµολογητής αλλάζει δυναµικά, κατά τη διάρκεια εκτέλεσης, τις προτεραιότητες
των διεργασιών. Η κύρια ιδέα είναι πως η εκτελούµενη διεργασία διακόπτεται περιοδικά.
Ο χρονοδροµολογητής υπολογίζει τότε εκ νέου τις προτεραιότητες των διεργασιών. Για
παράδειγµα, η προτεραιότητα µιας διεργασίας που έχει χρησιµοποιήσει για πολύ χρόνο
την ΚΜΕ µπορεί να µειωθεί, ενώ µιας άλλης που ξεκίνησε µε µικρή προτεραιότητα αλλά
δεν έχει ακόµη χρησιµοποιήσει καθόλου την ΚΜΕ ίσως αυξηθεί (φυσικά, υπάρχουν
πάρα πολλές µέθοδοι βάσει των οποίων µπορεί να αλλάζει η προτεραιότητα των
διεργασιών, αλλά δεν θα επεκταθούµε εδώ). Στη συνέχεια, η ΚΜΕ αποδίδεται στην
διεργασία που έχει την µεγαλύτερη νέα προτεραιότητα.
Μια ειδική περίπτωση του αλγορίθµου προτεραιοτήτων είναι ο αλγόριθµος η
συντοµότερη διεργασία πρώτη (Shortest Job First, SJF), ο οποίος δίνει τη µεγαλύτερη
προτεραιότητα στη διεργασία που χρειάζεται την ΚΜΕ για το µικρότερο διάστηµα. Ο
SJF είναι µη προεκχωρητικός αλγόριθµος. Στην προεκχωρητική του µορφή είναι
γνωστός ως η εργασία µε το συντοµότερο εναποµείναντα χρόνο πρώτη (Shortest
Remaining Time First, SRTF). Βάσει όσων ειπώθηκαν πιο πάνω, κάθε φορά που µια
διεργασία γίνεται έτοιµη, ο SRTF εξετάζει µήπως ο χρόνος εκτέλεσης της νέας αυτής
διεργασίας είναι µικρότερος από τον εναποµείναντα χρόνο εκτέλεσης της τρέχουσας

2η έκδοση 38
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

εκτελούµενης διεργασίας. Αν αυτό ισχύει, η τρέχουσα εκτελούµενη διεργασία γίνεται


έτοιµη και ξεκινά να εκτελείται η άλλη διεργασία.
Ο αλγόριθµος SJF βελτιστοποιεί τον µέσο χρόνο διεκπεραίωσης. Ωστόσο, ο αλγόριθµος
(στη µορφή που περιγράφτηκε πιο πάνω) δεν είναι υλοποιήσιµος για τον εξής λόγο. Είναι
πολύ σπάνιο να είναι γνωστές εξ αρχής οι απαιτήσεις µιας διεργασίας για τους διάφορους
πόρους του συστήµατος (και πιο συγκεκριµένα δεν είναι συνήθως γνωστός ο χρόνος που
η διεργασία χρειάζεται να χρησιµοποιήσει την ΚΜΕ πριν χρειαστεί να ανασταλεί ξανά
για να εκτελέσει Ε/Ε). Το πρόβληµα µπορεί να ξεπεραστεί µε τη χρήση ειδικών
αλγορίθµων προσέγγισης των απαιτήσεων αυτών. Οι αλγόριθµοι αυτοί βασίζονται στην
συµπεριφορά της διεργασίας στο παρελθόν, προκειµένου να κάνουν πρόβλεψη για το
µέλλον (ουσιαστικά να “µαντέψουν” πως θα συµπεριφερθεί η διεργασία στο µέλλον).

2.7.4 Θεωρητική Μελέτη Απόδοσης Χρονοδροµολογητών


Θα συζητήσουµε στη συνέχεια ένα σύνολο από παραδείγµατα προκειµένου να γίνει
κατανοητή η θεωρητική µελέτη της απόδοσης των χρονοδροµολογητών.

Παράδειγµα 4
Θεωρήστε το ακόλουθο σύνολο πέντε διεργασιών.

∆ιεργασία Προτεραιότητα Χρόνος Εκτέλεσης


A 3 70 ms
B 1 10 ms
Γ 3 20 ms
∆ 4 10 ms
Ε 2 50 ms

Θεωρήστε ότι όλες οι διεργασίες εισέρχονται στο σύστηµα την χρονική στιγµή 0 µε τη σειρά
που εµφανίζονται στον πίνακα (δηλαδή µε σειρά Α, Β, Γ, ∆, Ε).
Μελετήστε το µέσο χρόνο διεκπεραίωσης και το µέσο χρόνο αναµονής αν ο αλγόριθµος
χρονοδροµολόγησης είναι ο ακόλουθος:
1. FCFS.
2. Μη-προεκχωρητικός Priority (θεωρήστε ότι µεγάλοι αριθµοί σηµατοδοτούν µεγάλες
προτεραιότητες).
3. SJF.
4. RR µε κβάντο χρόνου 10 ms.
Κάνετε την απλουστευτική παραδοχή ότι ο χρόνος εναλλαγής είναι 0.
Για κάθε έναν από τους αλγορίθµους θα φτιάξουµε ένα διάγραµµα που θα δείχνει πως
εκτελούνται οι διάφορες διεργασίες µε το πέρασµα του χρόνου. Το διάγραµµα αυτό είναι
γνωστό ως διάγραµµα Gantt.

2η έκδοση 39
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

1. FCFS
Α Β Γ ∆ Ε
0 70 80 100 110 160

Χρόνος σε ms
O αλγόριθµος FCFS τοποθετεί τις διεργασίες στην ουρά εκτέλεσης βάσει του τρόπου
που φθάνουν στο σύστηµα. Έτσι, αρχικά η ουρά περιέχει τις Α, Β, Γ, ∆, Ε µε αυτή την
σειρά (δηλαδή η Α είναι η πρώτη διεργασία και η Ε είναι η τελευταία). Ο FCFS
χρονοδροµολογεί πρώτα την Α η οποία χρειάζεται 70ms πριν τερµατίσει (ή ζητήσει να
εκτελέσει Ε/Ε). Έτσι, από τη χρονική στιγµή 0 µέχρι την χρονική στιγµή 70 ms
εκτελείται η Α. Στη συνέχεια δροµολογείται η Β για τα 10 ms που χρειάζεται. Η
επόµενη διεργασία που δροµολογείται είναι η Γ, η οποία ζητάει 20 ms. Η Γ αφήνει την
ΚΜΕ την χρονική στιγµή 100, οπότε και την καταλαµβάνει η ∆. Την χρονική στιγµή
110 η Ε αποκτά την ΚΜΕ, η οποία και την κρατά µέχρι τη χρονική στιγµή 160.
Από το διάγραµµα Gantt παρατηρούµε ότι Χ∆Α = 70 ms, Χ∆Β = 80 ms, Χ∆Γ = 100 ms,
Χ∆∆ = 110 ms, Χ∆Ε = 160 ms. Άρα, ΜΧ∆ = (Χ∆Α+Χ∆Β+Χ∆Γ+Χ∆∆+Χ∆Ε) / 5 =
(70+80+100+110+160) / 5 ms = 104 ms.
Επίσης, ΧΑΑ = 0 ms, ΧΑΒ = 70 ms, ΧΑΓ = 80 ms, ΧΑ∆ = 100 ms, ΧΑΕ = 110 ms.
Άρα, ΜΧΑ = (ΧΑΑ+ΧΑΒ+ΧΑΓ+ΧΑ∆+ΧΑΕ) / 5 = (0+70+80+100+110) / 5 ms = 72
ms.
2. Μη-προεκχωρητικός Priority.
Το διάγραµµα Gantt είναι το ακόλουθο:
∆ Α Γ Ε Β
0 10 80 100 150 160

Χρόνος σε ms
Ο αλγόριθµος αυτός δροµολογεί πρώτη τη διεργασία µε την µεγαλύτερη
προτεραιότητα. Έτσι, η ∆ θα είναι η πρώτη που θα εκτελεστεί. Στη συνέχεια, θα
εκτελεστεί µια από τις Α, Γ που έχουν τη δεύτερη µεγαλύτερη προτεραιότητα.
Θεωρούµε ότι εκτελείται πρώτα η Α (που εισήλθε πρώτη στην ουρά εκτέλεσης και στη
συνέχεια η Γ. Θα πρέπει να τονιστεί ότι η σειρά εκτέλεσης Γ, Α είναι εξίσου σωστή,
ενώ θα οδηγούσε και σε καλύτερο µέσο χρόνο διεκπεραίωσης, αφού η Γ είναι
συντοµότερη διεργασία από την Α. Αφού εκτελεστούν οι Α και Γ, εκτελείται η Ε και
τέλος η Β, που έχει τις µικρότερες προτεραιότητες.
Από το διάγραµµα Gantt παρατηρούµε ότι Χ∆Α = 80 ms, Χ∆Β = 160 ms, Χ∆Γ = 100
ms, Χ∆∆ = 10 ms, Χ∆Ε = 150 ms. Άρα, ΜΧ∆ = (Χ∆Α+Χ∆Β+Χ∆Γ+Χ∆∆+Χ∆Ε) / 5 =
(80+160+100+10+150) / 5 ms = 100 ms.

2η έκδοση 40
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

Επίσης, ΧΑΑ = 10 ms, ΧΑΒ = 150 ms, ΧΑΓ = 80 ms, ΧΑ∆ = 0 ms, ΧΑΕ = 100 ms.
Άρα, ΜΧΑ = (ΧΑΑ+ΧΑΒ+ΧΑΓ+ΧΑ∆+ΧΑΕ) / 5 = (10+150+80+0+100) / 5 ms = 68
ms.
3. SJF
Β ∆ Γ Ε Α
0 10 20 40 90 160

Χρόνος σε ms
Ο αλγόριθµος αυτός θα δροµολογήσει πρώτα τις σύντοµες διεργασίες Β και ∆ (η
σειρά δεν παίζει κανένα ρόλο). Στη συνέχεια θα δροµολογήσει την Γ που είναι η
επόµενη πιο σύντοµη, την Ε και τέλος την Α που είναι η µεγαλύτερη διεργασία.
Από το διάγραµµα Gantt παρατηρούµε ότι Χ∆Α = 160 ms, Χ∆Β = 10 ms, Χ∆Γ = 40
ms, Χ∆∆ = 20 ms, Χ∆Ε = 90 ms. Άρα, ΜΧ∆ = (Χ∆Α+Χ∆Β+Χ∆Γ+Χ∆∆+Χ∆Ε) / 5 =
(160+10+40+20+90) / 5 ms = 64 ms.
Επίσης, ΧΑΑ = 90 ms, ΧΑΒ = 0 ms, ΧΑΓ = 20 ms, ΧΑ∆ = 10 ms, ΧΑΕ = 40 ms. Άρα,
ΜΧΑ = (ΧΑΑ+ΧΑΒ+ΧΑΓ+ΧΑ∆+ΧΑΕ) / 5 = (90+0+20+10+40) / 5 ms = 32 ms.
4. RR

τερµατισµός Β τερµατισµός ∆ τερµατισµός Γ τερµατισµός Ε τερµατισµός Α

Α Β Γ ∆ Ε Α Γ Ε Α Ε Α Ε Α Ε Α Α
0 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160

Χρόνος σε ms
Ο αλγόριθµος αυτός θα αποδίδει περιοδικά 10 ms του χρόνου της ΚΜΕ σε κάθε µια
από τις διεργασίες της ουράς εκτέλεσης ξεκινώντας από εκείνη που είναι πρώτη
στην ουρά και συνεχίζοντας προς το τέλος της.
Από το διάγραµµα Gantt παρατηρούµε ότι Χ∆Α = 160 ms, Χ∆Β = 20 ms, Χ∆Γ = 70
ms, Χ∆∆ = 40 ms, Χ∆Ε = 140 ms. Άρα, ΜΧ∆ = (Χ∆Α+Χ∆Β+Χ∆Γ+Χ∆∆+Χ∆Ε) / 5 =
(160+20+70+40+130) / 5 ms = 86 ms.
Επίσης, ΧΑΑ = (160-70) = 90 ms, ΧΑΒ = 10 ms, ΧΑΓ = (70-20) = 50 ms, ΧΑ∆ = 30
ms, ΧΑΕ = (140-50) = 90 ms. Άρα, ΜΧΑ = (ΧΑΑ+ΧΑΒ+ΧΑΓ+ΧΑ∆+ΧΑΕ) / 5 =
(90+10+50+30+90) / 5 ms = 54 ms.
Παρατηρήστε ότι ο µέσος χρόνος διεκπεραίωσης, αλλά και ο µέσος χρόνος αναµονής για
τον αλγόριθµο SJF είναι αρκετά µικρότεροι από ότι για τους άλλους αλγορίθµους.
Παρατηρήστε επίσης ότι ο αλγόριθµος FCFS παρουσιάζει τους χειρότερους χρόνους
διεκπεραίωσης και αναµονής.
Έστω ΧΕΑ ο χρόνος εκτέλεσης της Α, ΧΕΒ ο χρόνος εκτέλεσης της Β, κ.ο.κ.
Παρατηρήστε ότι, για κάθε αλγόριθµο, ισχύει η σχέση ΜΧ∆ = ΜΧΑ + (ΧΕΑ + ΧΕΒ + ...

2η έκδοση 41
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

+ ΧΕΕ)/5. Ο ΜΧ∆ και ΜΧΕ µπορεί να είναι διαφορετικοί για κάθε έναν από τους
αλγόριθµους, αλλά η ποσότητα (ΧΕΑ + ΧΕΒ + ... + ΧΕΕ)/5 είναι προφανώς ίδια για όλους
τους αλγόριθµους. Άρα, ο αλγόριθµος που έχει το µεγαλύτερο ΜΧ∆ έχει και το
µεγαλύτερο ΜΧΑ και αντίστροφα.
Είναι τέλος αξιοσηµείωτο πως στην περίπτωση που όλες οι διεργασίες είναι διαθέσιµες
εξ αρχής (όπως σε αυτό το παράδειγµα), η µη-προεκχωρητική και η προεκχωρητική
έκδοση του Priority λειτουργούν µε ακριβώς τον ίδιο τρόπο. Το ίδιο προφανώς ισχύει και
για τους SJF και SRTF (αφού αυτοί δεν είναι παρά ειδικές περιπτώσεις των δύο
εκδόσεων του Priority). □

Παράδειγµα 5
Ας µελετήσουµε τώρα µια γενικευµένη µορφή του Θέµατος 7 της 4ης γραπτής εργασίας του
Ακ. Έτους 2003-04.
Θεωρείστε πως οι παρακάτω διεργασίες εισέρχονται στο σύστηµα τις χρονικές στιγµές που
αναγράφονται:

∆ιαδικασία Χρόνος Άφιξης Χρόνος Εκτέλεσης


Α 1 8
Β 2 4
Γ 3 9
∆ 4 5

Μελετήστε το µέσο χρόνο διεκπεραίωσης και το µέσο χρόνο αναµονής για τους
ακόλουθους αλγορίθµους:
1. FCFS
2. SJF
3. SRTF
4. RR µε κβάντο χρόνου 1 χρονική µονάδα
Κάνετε την απλουστευτική παραδοχή ότι ο χρόνος εναλλαγής είναι 0.
Τα διαγράµµατα Gantt για κάθε έναν από τους παραπάνω αλγόριθµους φαίνονται στο
Σχήµα 9. Κατά την πρώτη χρονική µονάδα το σύστηµα είναι ανενεργό, αφού δεν υπάρχει
καµία διεργασία στο σύστηµα.
1. Ο FCFS δροµολογεί τις διεργασίες µε τη σειρά άφιξής τους. Εποµένως, πρώτα
δροµολογείται η Α, στη συνέχεια η Β, στη συνέχεια η Γ, και τέλος η ∆. Από το 1ο
διάγραµµα Gantt του Σχήµατος 9 παρατηρούµε ότι Χ∆Α = (9-1) = 8 χρονικές
µονάδες, Χ∆Β = (13-2) = 11 χρονικές µονάδες, Χ∆Γ = (22-3) = 19 χρονικές µονάδες,
Χ∆∆ = (27-4) = 23 χρονικές µονάδες. Άρα, ΜΧ∆ = (Χ∆Α+Χ∆Β+Χ∆Γ+Χ∆∆) / 4 =
(8+11+19+23) / 4 χρονικές µονάδες = 15,25 χρονικές µονάδες.
Επίσης, ΧΑΑ = (9-8-1) = 0 χρονικές µονάδες, ΧΑΒ = (13-4-2) = 7 χρονικές µονάδες,
ΧΑΓ = (22-9-3) = 10 χρονικές µονάδες, ΧΑ∆ = (27-5-4) = 18 χρονικές µονάδες. Άρα,

2η έκδοση 42
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

ΜΧΑ = (ΧΑΑ+ΧΑΒ+ΧΑΓ+ΧΑ∆) / 4 = (0+7+10+18) / 4 χρονικές µονάδες = 8,75


χρονικές µονάδες.
2. Ο SJF ξεκινά µε την διεργασία Α, τη µόνη διαθέσιµη διεργασία στο σύστηµα την
χρονική στιγµή 1. Όταν η Α τερµατίζει την χρονική στιγµή 9, οι διεργασίες Β, Γ και
∆ βρίσκονται όλες στο σύστηµα. Ο SJF διαλέγει τη µικρότερη από αυτές, δηλαδή την
Β και την εκτελεί. Στη συνέχεια εκτελείται η ∆ και τέλος η Γ.
Από το 2ο διάγραµµα Gantt του Σχήµατος 9 παρατηρούµε ότι Χ∆Α = (9-1) = 8
χρονικές µονάδες, Χ∆Β = (13-2) = 11 χρονικές µονάδες, Χ∆Γ = (27-3) = 24 χρονικές
µονάδες, Χ∆∆ = (18-4) = 14 χρονικές µονάδες. Άρα, ΜΧ∆ = (Χ∆Α+Χ∆Β+Χ∆Γ+Χ∆∆)
/ 4 = (8+11+24+14) / 4 χρονικές µονάδες = 14,25 χρονικές µονάδες.
Επίσης, ΧΑΑ = (9-8-1) = 0 χρονικές µονάδες, ΧΑΒ = (13-4-2) = 7 χρονικές µονάδες,
ΧΑΓ = (27-9-3) = 15 χρονικές µονάδες, ΧΑ∆ = (18-5-4) = 9 χρονικές µονάδες. Άρα,
ΜΧΑ = (ΧΑΑ+ΧΑΒ+ΧΑΓ+ΧΑ∆) / 4 = (0+7+15+9) / 4 χρονικές µονάδες = 7,75
χρονικές µονάδες.
Παρατηρήστε ότι ο SJF είναι µόνο οριακά καλύτερος από τον FCFS σε αυτό το
παράδειγµα. Αυτό οφείλεται στο γεγονός ότι οι µικρές διεργασίες εισήλθαν στο
σύστηµα λίγο µετά την απόδοση της ΚΜΕ σε µια µεγάλη διεργασία, την Α.
∆εδοµένου ότι ο SJF δεν είναι προεκχωρητικός, θα συνεχίσει να εκτελεί την Α µέχρι
αυτή να ελευθερώσει την ΚΜΕ. Η εκτέλεση της µεγάλης αυτής διεργασίας πρώτης,
οδηγεί σε άσχηµους χρόνους διεκπεραίωσης και αναµονής (όπως φάνηκε στο
παράδειγµα αυτό).
3. Ο SRTF ξεκινά τη δροµολόγηση µε την διεργασία Α που είναι η µόνη διαθέσιµη την
χρονική στιγµή 1. Όταν όµως τη χρονική στιγµή 2 έρχεται η Β, ο SRTF συγκρίνει τον
χρόνο που αποµένει να εκτελεστεί η Α πριν απελευθερώσει την ΚΜΕ (που είναι 7
µονάδες) µε τον χρόνο που χρειάζεται την ΚΜΕ η Β (που είναι 4 µονάδες) και
διαλέγει να δροµολογήσει την Β, που θα απασχολήσει την ΚΜΕ για λιγότερο χρόνο.
Την χρονική στιγµή 3 γίνεται και πάλι διακοπή και ο SRTF καλείται να διαλέξει εκ
νέου ποια διεργασία θα εκτελεστεί στην ΚΜΕ. Ο υπολειπόµενος χρόνος της Α είναι 7
µονάδες, ο υπολειπόµενος χρόνος της Β είναι 3 µονάδες και ο απαιτούµενος χρόνος
της Γ είναι 9 µονάδες. Εποµένως, η Β συνεχίζει την εκτέλεσή της µέχρι να προκληθεί
και νέα διακοπή. Αυτό συµβαίνει την χρονική στιγµή 4, που στο σύστηµα εισέρχεται
η ∆. Οι απαιτούµενοι (υπολειπόµενοι) χρόνοι συγκρίνονται και πάλι και ο SRTF
αποφασίζει πως η Β εξακολουθεί να είναι η διεργασία µε τη χαµηλότερη απαίτηση σε
ΚΜΕ, οπότε και εκτελείται ξανά. Όταν η Β τελειώνει τη χρονική στιγµή 6,
εκτελούνται οι ∆, Α και Γ (µε αυτή τη σειρά).
Από το 3ο διάγραµµα Gantt του Σχήµατος 9 παρατηρούµε ότι Χ∆Α = (18-1) = 17
χρονικές µονάδες, Χ∆Β = (6-2) = 4 χρονικές µονάδες, Χ∆Γ = (27-3) = 24 χρονικές
µονάδες, Χ∆∆ = (11-4) = 7 χρονικές µονάδες. Άρα, ΜΧ∆ = (Χ∆Α+Χ∆Β+Χ∆Γ+Χ∆∆) /
4 = (17+4+24+7) / 4 χρονικές µονάδες = 13 χρονικές µονάδες.
Επίσης, ΧΑΑ = (18-8-1) = 9 χρονικές µονάδες, ΧΑΒ = (6-4-2) = 0 χρονικές µονάδες,
ΧΑΓ = (27-9-3) = 15 χρονικές µονάδες, ΧΑ∆ = (11-5-4) = 2 χρονικές µονάδες. Άρα,
ΜΧΑ = (ΧΑΑ+ΧΑΒ+ΧΑΓ+ΧΑ∆) / 4 = (9+0+15+2) / 4 χρονικές µονάδες = 6,5
χρονικές µονάδες.

2η έκδοση 43
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

4. Για να κατανοήσουµε τον τρόπο εκτέλεσης του RR θα πρέπει να µελετήσουµε σε


κάθε χρονική στιγµή τι περιέχει η ουρά έτοιµων εργασιών. Την χρονική στιγµή 0 το
σύστηµα είναι ανενεργό, ενώ την 1 έρχεται η Α και ξεκινά να εκτελείται στην ΚΜΕ.
Την χρονική στιγµή 2 γίνεται διακοπή και στο σύστηµα καταφθάνει η Β. Τώρα
υπάρχουν δύο επιλογές. Είτε ο χρονοδροµολογητής θα τοποθετήσει στην ουρά την Α
τη στιγµή της διακοπής και µετά θα τοποθετηθεί στην ουρά και η Β, ή θα γίνει το
αντίστροφο. Στην πρώτη περίπτωση, η πρώτη διεργασία στην ουρά είναι η Α και η
ΚΜΕ θα αποδοθεί και πάλι σε αυτήν για το επόµενο κβάντο χρόνου, ενώ στη
δεύτερη περίπτωση, η πρώτη διεργασία στην ουρά είναι η Β και άρα η ΚΜΕ θα
αποδοθεί στην Β για το επόµενο κβάντο χρόνου. Θεωρούµε στη συνέχεια ότι ο
χρονοδροµολογητής λειτουργεί όπως καθορίζεται από την πρώτη περίπτωση. Θα
πρέπει να τονιστεί πως εξίσου σωστό είναι και να θεωρηθεί ότι ακολουθείται η
δεύτερη περίπτωση.
Μπορούµε εποµένως να φτιάξουµε έναν πίνακα που θα παρέχει, σε κάθε χρονική
στιγµή, τις διεργασίες που εισέρχονται, τη διεργασία που απασχολεί την ΚΜΕ, τις
διεργασίες που βρίσκονται στην ουρά έτοιµων διεργασιών, καθώς και τις διεργασίες
που τερµατίζουν. Ο πίνακας φαίνεται στο Σχήµα 8. Στη γραµµή i της στήλης «ΚΜΕ»
καταγράφεται κάθε φορά η διεργασία που θα απασχολήσει την ΚΜΕ από τη χρονική
στιγµή i έως την (i+1). Η διεργασία αυτή ήταν η πρώτη διεργασία στην ουρά έτοιµων
διεργασιών την χρονική στιγµή i. Εποµένως, η ουρά διεργασιών την χρονική στιγµή
4 περιέχει τις διεργασίες Α, Γ, Β, ∆ µε τη σειρά που αναγράφονται (παρότι η Α δεν
συµπεριλαµβάνεται στη στήλη «Ουρά Έτοιµων ∆ιεργασιών» της γραµµής 4). Η
διεργασία Α είναι αυτή που θα κατέχει την ΚΜΕ την χρονική µονάδα που µεσολαβεί
από τη χρονική στιγµή 4 έως τη χρονική στιγµή 5.
Την χρονική στιγµή 1 εισέρχεται η Α και αµέσως καταλαµβάνει την ΚΜΕ. Την
χρονική στιγµή 2 εισέρχεται η Β και τοποθετείται στην ουρά µετά την Α. Την
χρονική στιγµή 3 εισέρχεται η Γ. Τη στιγµή της διακοπής η ουρά περιέχει την Β.
Μετά την διακοπή τοποθετείται στην ουρά και η διεργασία Α και στη συνέχεια η
διεργασία Γ που µόλις κατέφθασε στο σύστηµα. Αντίστοιχες ενέργειες λαµβάνονται
όταν την χρονική στιγµή 4 εισέρχεται η διεργασία ∆. Παρατηρήστε ότι η διεργασία Β
απασχολεί την ΚΜΕ από τη χρονική στιγµή 14 έως την 15 και άρα τερµατίζει την
χρονική στιγµή 15 (και όχι την 14). Οµοίως, οι διεργασίες ∆, Α και Γ τερµατίζουν τις
χρονικές στιγµές 22, 23 και 27, αντίστοιχα.

Χρονική Στιγµή Άφιξη ΚΜΕ Ουρά Έτοιµων ∆ιεργασιών Τερµατισµός


0 - - - -
1 Α Α - -
2 Β Α Β -
3 Γ Β Α, Γ -
4 ∆ Α Γ, Β, ∆ -
5 - Γ Β, ∆, Α -
6 - Β ∆, Α, Γ -
7 - ∆ Α, Γ, Β -

2η έκδοση 44
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

8 - Α Γ, Β, ∆ -
9 - Γ Β, ∆, Α -
10 - Β ∆, Α, Γ -
11 - ∆ Α, Γ, Β -
12 - Α Γ, Β, ∆ -
13 - Γ Β, ∆, Α -
14 - Β ∆, Α, Γ -
15 - ∆ Α, Γ Β
16 - Α Γ, ∆ -
17 - Γ ∆, Α -
18 - ∆ Α, Γ -
19 - Α Γ, ∆ -
20 - Γ ∆, Α -
21 - ∆ Α, Γ -
22 - Α Γ ∆
23 - Γ - Α
24 - Γ - -
25 - Γ - -
26 - Γ - -
27 - - - Γ

Σχήµα 8: Πίνακας Χρονοδροµολόγησης για τον αλγόριθµο RR.


Από το 4ο διάγραµµα Gantt του Σχήµατος 9 παρατηρούµε ότι Χ∆Α = (23-1) = 22
χρονικές µονάδες, Χ∆Β = (15-2) = 13 χρονικές µονάδες, Χ∆Γ = (27-3) = 24 χρονικές
µονάδες, Χ∆∆ = (22-4) = 18 χρονικές µονάδες. Άρα, ΜΧ∆ = (Χ∆Α+Χ∆Β+Χ∆Γ+Χ∆∆)
/ 4 = (22+13+24+18) / 4 χρονικές µονάδες = 19,25 χρονικές µονάδες.
Επίσης, ΧΑΑ = (23-8-1) = 14 χρονικές µονάδες, ΧΑΒ = (15-4-2) = 9 χρονικές µονάδες,
ΧΑΓ = (27-9-3) = 15 χρονικές µονάδες, ΧΑ∆ = (22-5-4) = 13 χρονικές µονάδες. Άρα,
ΜΧΑ = (ΧΑΑ+ΧΑΒ+ΧΑΓ+ΧΑ∆) / 4 = (14+9+15+13) / 4 χρονικές µονάδες = 12,75
χρονικές µονάδες. □

Άσκηση Αυτοαξιολόγησης 1 (Θέµα 7, Ερωτήσεις Πολλαπλής Επιλογής, Εξετάσεις


Ιουλίου 2003)
Έστω οι ακόλουθοι αλγόριθµοι χρονοδροµολόγησης,
(α) RR µε κβάντο ίσο µε 10ms,
(β) SJF, και
(γ) FCFS.

2η έκδοση 45
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

Έστω πέντε διεργασίες Α, Β, Γ, ∆ και Ε που εκτελούν υπολογισµούς στην ΚΜΕ µε


απαιτήσεις χρόνου ΧΕΑ = 10 ms, ΧΕB = 29ms, ΧΕΓ = 3 ms, ΧΕ∆ = 7 ms και ΧΕΕ = 12
ms, και βρίσκονται στην ουρά και οι πέντε τη χρονική στιγµή 0.
Ποιες από τις παρακάτω προτάσεις είναι σωστές;
5. Τη χρονική στιγµή 31, και για το σχήµα RR και για το SJF, η διεργασία που
εκτελείται στην ΚΜΕ είναι η ∆.
6. Στο σχήµα RR η διεργασία Β τελειώνει νωρίτερα από ότι στο σχήµα SJF.
7. Τη χρονική στιγµή 19, και για το σχήµα RR και για το FCFS, η διεργασία που
εκτελείται στην ΚΜΕ είναι η Β.
8. Η διεργασία Γ τελειώνει νωρίτερα στο σχήµα RR από το FCFS.
Σκιαγράφηση Λύσης: Αρκεί να φτιάξετε τα διαγράµµατα Gantt για να δείτε ποιες είναι
οι σωστές προτάσεις.
Σηµείωση: Εντελώς αντίστοιχο είναι και το Θέµα 7, Ερωτήσεις Πολλαπλής Επιλογής,
Εξετάσεις Ιουνίου 2002.

2η έκδοση 46
1. FCFS
idle Α Β Γ ∆
0 1 9 13 22 27

Χρόνος σε χρονικές µονάδες


2. SJF
idle Α Β ∆ Γ
0 1 9 13 18 27

Χρόνος σε χρονικές µονάδες


3. SRTF
idle Α Β Β Β ∆ Α Γ
0 1 2 3 4 6 11 18 27

Χρόνος σε χρονικές µονάδες


4. RR

τερµατισµός Β τερµατισµός ∆ τερµατισµός Α τερµατισµός Γ

idle Α Α Β Α Γ Β ∆ Α Γ Β ∆ Α Γ Β ∆ Α Γ ∆ Α Γ ∆ Α Γ Γ Γ Γ
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

Χρόνος σε χρονικές µονάδες


Σχήµα 9: ∆ιαγράµµατα Gannt για τους αλγόριθµους χρονοδροµολόγησης του Παραδείγµατος 6.
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

Άσκηση Αυτοαξιολόγησης 2 (Θέµα 5, Εξετάσεις Ιουλίου 2003)


Έστω οι ακόλουθες 5 διεργασίες Α, Β, Γ, ∆ και Ε που περιµένουν στην ουρά προς
εκτέλεση. Ο χρόνος εκτέλεσης της Α είναι 10 χρονικές µονάδες, της Β είναι 6 χρονικές
µονάδες, της Γ είναι 2 χρονικές µονάδες, της ∆ είναι 4 χρονικές µονάδες, και της Ε είναι
8 χρονικές µονάδες. Βρείτε το µέσο χρόνο διεκπεραίωσης χρησιµοποιώντας τους
ακόλουθους αλγόριθµους χρονοδροµολόγησης:
5. RR µε κβάντο 3 χρονικές µονάδες,
6. SJF
7. FCFS
Ποιος αλγόριθµος χρονοδροµολόγησης έχει τον µικρότερο µέσο χρόνο διεκπεραίωσης
και γιατί; Ποιος αλγόριθµος έχει τον µικρότερο µέσο χρόνο αναµονής;
Σκιαγράφηση Λύσης: Αρκεί και πάλι να φτιάξουµε τα διαγράµµατα Gantt. Στη
συνέχεια δουλεύουµε µε τον ίδιο τρόπο, όπως στα λυµένα παραδείγµατα, για να βρούµε
το µέσο χρόνο διεκπεραίωσης. Η λύση µπορεί να συγκριθεί µε την ενδεικτική επίλυση
(του Θέµατος 5, Εξετάσεις Ιουλίου, 2003) για να επιβεβαιωθεί η ορθότητα της.
Ο αναγνώστης θα πρέπει να είναι ήδη σε θέση να απαντήσει το τελευταίο ερώτηµα. Τον
µικρότερο δυνατό χρόνο διεκπεραίωσης (όπως αναφέρθηκε και σε προηγούµενη
ενότητα) θα τον έχει ο SJF. Το ίδιο ισχύει φυσικά και για τον µέσο χρόνο αναµονής
(θυµηθείτε την αντίστοιχη συζήτηση στο Παράδειγµα 5). Όπως έχει ήδη αναφερθεί,
µπορεί να αποδειχθεί, ότι για ένα σύνολο N διεργασιών οι οποίες φτάνουν την ίδια
χρονική στιγµή στο σύστηµα, ο αλγόριθµος SJF δίνει το βέλτιστο δυνατό αποτέλεσµα,
όσον αφορά το µέσο χρόνο διεκπεραίωσης. ∆ιαισθητικά, αυτό συµβαίνει γιατί ο χρόνος
διεκπεραίωσης κάθε διεργασίας ισούται σε αυτήν την περίπτωση µε τη διάρκεια
εκτέλεσής της συν τους χρόνους διεκπεραίωσης των διεργασιών που µπήκαν στη CPU
πριν από αυτήν. Εποµένως είναι αναµενόµενο πως όσο πιο µικρές διεργασίες
εκτελούνται πρώτες τόσο λιγότερο επιβαρύνεται ο χρόνος διεκπεραίωσης των επόµενων
διεργασιών. Ας επιµείνουµε λίγο περισσότερο στο σηµείο αυτό µε ένα παράδειγµα.
Θεωρήστε 4 διεργασίες Α, Β, Γ και ∆, µε χρόνους εκτέλεσης ΧΕΑ, ΧΕΒ, ΧΕΓ και ΧΕ∆, οι
οποίες βρίσκονται στο σύστηµα την χρονική στιγµή 0. Έστω ότι η σειρά µε την οποία οι
διεργασίες δροµολογούνται είναι Α, Β, Γ, ∆. Τότε, ο χρόνος διεκπεραίωσης της Α είναι
ΧΕΑ, ο χρόνος διεκπεραίωσης της Β είναι (ΧΕΑ+ ΧΕΒ), ο χρόνος διεκπεραίωσης της Γ
είναι (ΧΕΑ+ ΧΕΒ+ ΧΕΓ), ενώ ο χρόνος διεκπεραίωσης της ∆ είναι (ΧΕΑ + ΧΕΒ + ΧΕΓ +
ΧΕ∆). Εποµένως, ο µέσος χρόνος διεκπεραίωσης είναι ΜΧ∆ = (ΧΕΑ + (ΧΕΑ+ ΧΕΒ) +
(ΧΕΑ+ ΧΕΒ+ ΧΕΓ) + (ΧΕΑ + ΧΕΒ + ΧΕΓ + ΧΕ∆)) / 4 = (4 ΧΕΑ + 3 ΧΕΒ + 2 ΧΕΓ + ΧΕ∆) /
4.
Παρατηρήστε ότι ο χρόνος εκτέλεσης της διεργασίας που επιλέγεται να εκτελεστεί
πρώτη εµφανίζεται στον τύπο του ΜΧ∆ µε τον µεγαλύτερο πολλαπλασιαστικό
συντελεστή (στο παράδειγµα µας µε συντελεστή 4), ο χρόνος εκτέλεσης της διεργασίας
που επιλέγεται να εκτελεστεί δεύτερη εµφανίζεται στον τύπο του ΜΧ∆ µε τον δεύτερο
µεγαλύτερο πολλαπλασιαστικό συντελεστή (στο παράδειγµά µας µε συντελεστή 3),
κ.ο.κ. Το συµπέρασµα αυτό ισχύει γενικότερα σε ένα σύστηµα n διεργασιών.
Συµπεραίνουµε εποµένως, ότι δροµολογώντας τις διεργασίες σε σειρά αύξοντα χρόνου
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

διεκπεραίωσης, ελαχιστοποιούµε το παραπάνω (ζυγισµένο) άθροισµα και άρα και το


ΜΧ∆.
Προσοχή: Το ότι ο SJF είναι βέλτιστος ως προς τον µέσο χρόνο διεκπεραίωσης και τον
µέσο χρόνο αναµονής δεν σηµαίνει πως η απάντηση στην ερώτηση «Ποιος αλγόριθµος
έχει τον µικρότερο µέσο χρόνο διεκπεραίωσης (ή αναµονής);», για κάποιο συγκεκριµένο
παράδειγµα, είναι ο SJF. Ο λόγος για αυτό είναι πως και κάποιος άλλος αλγόριθµος
µπορεί να επιτυγχάνει εξίσου καλό µέσο χρόνο διεκπεραίωσης (ή αναµονής) για κάποιο
συγκεκριµένο παράδειγµα. Θα πρέπει εποµένως κάθε φορά να φτιάχνουµε το διάγραµµα
Gantt για να βλέπουµε αν όντως ο SJF υπερέχει έναντι των υπολοίπων ή αν κάποιοι από
τους υπόλοιπους είναι εξίσου καλοί µε αυτόν για το συγκεκριµένο παράδειγµα. □

Άσκηση Αυτοαξιολόγησης 3 (Απλουστευµένη Μορφή Θέµατος 3, Προαιρετική Εργασία,


Ακ. Έτος 2001-2002)
Θεωρείστε τις ακόλουθες πέντε διεργασίες:

∆ιεργασία Χρόνος Άφιξης Χρόνος Εκτέλεσης


Α 0 3
Β 1 4
Γ 3 2
∆ 4 4
Ε 6 3

Σας ζητείται να βρείτε πόσες χρονικές µονάδες απαιτούνται συνολικά για να


ολοκληρωθεί η εκτέλεση όλων των διεργασιών. Σας ζητείται επίσης να δηµιουργήσετε
πίνακα, ο οποίος θα απεικονίζει ποια διεργασία βρίσκεται στην KME κάθε χρονική
µονάδα και ποιες διεργασίες περιέχει η ουρά εκτέλεσης κάθε χρονική στιγµή.
Σκιαγράφηση Λύσης: Θα πρέπει να φτιαχτεί διάγραµµα Gantt για να απαντηθεί το
πρώτο ερώτηµα. Στη συνέχεια θα πρέπει να σχεδιαστεί ο πίνακας που ζητείται στο
δεύτερο ερώτηµα µε τον τρόπο που παρουσιάστηκε στο Παράδειγµα 6. Ο πίνακας θα
έχει την εξής µορφή:

Χρονική Στιγµή Άφιξη ΚΜΕ Ουρά Εκτέλεσης Τερµατισµός


0 Α - Α
... ... ... ...

Αν ο αναγνώστης νιώθει µεγάλη εξοικείωση µε το θέµα της δροµόλογησης µπορεί να


φτιάξει απευθείας τον πίνακα και να απαντήσει και το πρώτο ερώτηµα µε βάση αυτόν.

Άσκηση Αυτοαξιολόγησης 4 (Θέµα 2β, Τελικές Εξετάσεις Ιουλίου 2002)


Θεωρήστε ότι πέντε διεργασίες εισέρχονται σε ένα σύστηµα όλες την ίδια χρονική
στιγµή 0 µε τη σειρά που υπαγορεύει ο παρακάτω πίνακας.

2η έκδοση 49
2ο Κεφάλαιο ∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

∆ιεργασία ∆ιάρκεια
Α 3
Β 1
Γ 2
∆ 1
Ε 1

1. Αν χρησιµοποιείται ο αλγόριθµος RR µε κβάντο χρόνου 1 χρονική µονάδα,


υπολογίστε τον µέσο χρόνο διεκπεραίωσης των πέντε διεργασιών. Παρουσιάστε
πίνακα που θα απεικονίζει κάθε χρονική στιγµή ποια διεργασία απασχολεί την ΚΜΕ
και ποιες βρίσκονται στην ουρά εκτέλεσης.
2. Βρείτε µια σειρά άφιξης η οποία να είναι δυνατό να οδηγήσει σε µέσο χρόνο
διεκπεραίωσης ίσο µε 4 µόνο χρονικές µονάδες. Φτιάξτε και εδώ πίνακα (ή
διάγραµµα Gantt) για να τεκµηριώσετε την απάντησή σας. Εξηγήστε συνοπτικά γιατί
σε αυτή την περίπτωση επιτυγχάνεται καλύτερος µέσος χρόνος διεκπεραίωσης σε
σχέση µε αυτόν του προηγούµενου ερωτήµατος.
Σκιαγράφηση Λύσης: Το ερώτηµα (α) θα πρέπει ο αναγνώστης να είναι σε θέση να το
απαντήσει. Ενδιαφέρον παρουσιάζει κύρια το ερώτηµα (β). Όταν ζητείται να επιτευχθεί
καλός χρόνος διεκπεραίωσης, µια από τις πρώτες σκέψεις που πρέπει να κάνουµε είναι τι
χρόνος διεκπεραίωσης θα είχε επιτευχθεί, αν οι εργασίες είχαν έρθει µε σειρά η
συντοµότερη πρώτα (δηλαδή, η σειρά ήταν Β,∆,Ε,Γ,Α). Αν αυτό δεν µας οδηγήσει στη
λύση, τότε προσπαθούµε να κάνουµε µικρές τροποποιήσεις σε αυτή τη σειρά,
προκειµένου να οδηγηθούµε στο ζητούµενο αποτέλεσµα.

2.8 Βιβλιογραφικές Αναφορές


Όλα τα βιβλία λειτουργικών συστηµάτων [ΒΗ73, BH03, G00, SGG07, St03, TanI]
καλύπτουν το υλικό που παρουσιάζεται στο κεφάλαιο αυτό. Κάποιες από τις βασικές
έννοιες που αναφέρθηκαν εδώ περιγράφονται στις αναφορές [A00, BA90]. Αλγόριθµοι
χρονοδροµολόγησης διαφορετικών εκδόσεων του λειτουργικού συστήµατος UNIX
περιγράφονται στις αναφορές [B86, MM01], ενώ θέµατα χρονοδροµολόγησης στο
λειτουργικό σύστηµα Windows παρουσιάζονται στην εργασία [SR00]. Οι διαφορετικές
εκδόσεις των δύο αυτών λειτουργικών συστηµάτων πρωτοστατούν σήµερα.

2η έκδοση 50
3ο Κεφάλαιο
∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

3.1 Ταυτόχρονη Εκτέλεση ∆ιεργασιών


Για να γίνει κατανοητό τι σηµαίνει ταυτόχρονη εκτέλεση διεργασιών, θα µελετήσουµε
ένα σύνολο από παραδείγµατα τέτοιων εκτελέσεων και θα συζητήσουµε διάφορα θέµατα
που τις αφορούν.
Ας επιστρέψουµε στο Παράδειγµα 2 του 2ου κεφαλαίου. Θεωρείστε ότι στο σύστηµα
υπάρχουν ταυτόχρονα δύο διεργασίες, Α και Β, που επιθυµούν να εκτελέσουν τον απλό
κώδικα του Παραδείγµατος. Θεωρείστε επίσης ότι όλες οι µεταβλητές που
χρησιµοποιούνται στον κώδικα είναι τοπικές µεταβλητές (δηλαδή κάθε µια από τις
διεργασίες έχει τα δικά της αντίγραφα µεταβλητών και όταν µια διεργασία µεταβάλλει
µια από αυτές τις µεταβλητές, η άλλη διεργασία δεν βλέπει καµία αλλαγή).
Κάθε µια από τις διεργασίες θα εκτελέσει τα 28 βήµατα που παρουσιάζονται στο
Παράδειγµα 2. ∆εδοµένου ωστόσο ότι οι δύο διεργασίες είναι έτοιµες για εκτέλεση την
ίδια χρονική στιγµή, ο χρονοδροµολογητής µπορεί να εκτελεί για ένα χρονικό διάστηµα
τη µια από αυτές, στη συνέχεια να τη διακόπτει και να συνεχίζει µε την εκτέλεση της
άλλης, κ.ο.κ. Εποµένως, πολλές δυνατές “ταυτόχρονες” εκτελέσεις είναι δυνατές. Το
απλούστερο σενάριο είναι να εκτελεστεί εξ ολοκλήρου πρώτα η Α και στη συνέχεια η Β.
Φυσικά, το να συµβεί το αντίθετο (δηλαδή να εκτελεστεί εξ ολοκλήρου πρώτα η Β και
µετά η Α) είναι εξίσου πιθανό. Στην πιο συνηθισµένη περίπτωση ωστόσο, αυτό που
συµβαίνει είναι πως τα βήµατα εκτέλεσης των δύο διεργασιών µπερδεύονται µε
αυθαίρετο τρόπο. Το Σχήµα 10 περιγράφει ένα σενάριο εκτέλεσης (η γραµµή κώδικα που
εκτελεί η κάθε διεργασία αναγράφεται στα δεξιά της στήλης). Ένας άλλος τρόπος
αναπαράστασης της ίδιας εκτέλεσης παρουσιάζεται στο Σχήµα 11. Στο Σχήµα 11, ο
κατακόρυφος άξονας είναι ο άξονας του χρόνου και οι εντολές που εκτελούν
διαφορετικές διεργασίες παρουσιάζονται σε διαφορετικές στήλες του πίνακα. Χρονικά, η
λειτουργίες εκτελούνται µε τη σειρά που εµφανίζονται από πάνω προς τα κάτω
(ανεξάρτητα από τη στήλη στην οποία ανήκουν). Η περιγραφή που παρέχεται στο Σχήµα
11 είναι πιο ευανάγνωστη από εκείνη του Σχήµατος 10 και είναι εκείνη που θα
υιοθετηθεί για τη συνέχεια του εκπαιδευτικού υλικού αυτού. Μια δεύτερη δυνατή
εκτέλεση των δύο διεργασιών παρουσιάζεται στο Σχήµα 12.
Ένα µεγάλο σύνολο άλλων δυνατών συνδυασµών είναι εξίσου πιθανό να συµβούν. Ο
αναγνώστης θα πρέπει να θυµάται, πως κάθε διεργασία είναι δυνατό να διακοπεί σε
οποιοδήποτε σηµείο της εκτέλεσής της (ας θυµηθούµε τη σχετική συζήτηση στο
προηγούµενο κεφάλαιο) και άρα όλοι οι δυνατοί συνδυασµοί βηµάτων των δύο
διεργασιών θεωρούνται εξίσου πιθανοί να προκύψουν. Η προκύπτουσα εκτέλεση είναι
άγνωστη και, ακόµα χειρότερα, είναι συνήθως διαφορετική από τη µια εκτέλεση του
κώδικα στην άλλη (παρότι ο κώδικας που εκτελείται είναι πάντα ο ίδιος). Αυτή την
αδυναµία αναπαραγωγής µιας εκτέλεσης, την εκφράζουµε µε τον όρο µη-ντετερµινισµός.
Ο προγραµµατιστής θα πρέπει να εξασφαλίσει ότι δεν θα εµφανιστεί σφάλµα σε καµία
δυνατή εκτέλεση. Για το λόγο αυτό, ο προγραµµατισµός ταυτόχρονα εκτελούµενων
διεργασιών είναι εξαιρετικά δύσκολος, ενώ η αποσφαλµάτωσή του θεωρείται ένα από τα
δυσκολότερα καθήκοντα.

2η έκδοση 52
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Β: tmp = 1 (γρ. 1)
Α: tmp = 1 (γρ. 1)
Έλεγχος της συνθήκης της while (γρ. 2)
Έλεγχος της συνθήκης της if (γρ. 3)
tmp = 1 * 3 = 3 (γρ. 4)
flag = 1 (γρ. 5)
p=4 (γρ. 6)
Έλεγχος της συνθήκης της while (γρ. 7)
Έλεγχος της συνθήκης της if (γρ. 8)
tmp = 3 * 4 = 12 (γρ. 9)
flag = 0 (γρ. 10)
p=3 (γρ. 11)
Έλεγχος της συνθήκης της while (γρ. 12)
Β: Έλεγχος της συνθήκης της while (γρ. 2)
Έλεγχος της συνθήκης της if (γρ. 3)
tmp = 1 * 3 = 3 (γρ. 4)
flag = 1 (γρ. 5)
p=4 (γρ. 6)
Έλεγχος της συνθήκης της while (γρ. 7)
Έλεγχος της συνθήκης της if (γρ. 8)
tmp = 3 * 4 = 12 (γρ. 9)
flag = 0 (γρ. 10)
p=3 (γρ. 11)
Έλεγχος της συνθήκης της while (γρ. 12)
Έλεγξε τη συνθήκη της if (γρ. 13)
tmp = 12 * 3 = 36 (γρ. 14)
flag = 1 (γρ. 15)
p=2 (γρ. 16)
Έλεγχος της συνθήκης της while (γρ. 17)
Έλεγχος της συνθήκης της if (γρ. 18)
tmp = 36 * 4 = 144 (γρ. 19)
flag = 0 (γρ. 20)
Α: Έλεγχος της συνθήκης της if (γρ. 13)
Β: p = 1 (γρ. 21)
Έλεγχος της συνθήκης της while (γρ. 22)
Έλεγχος της συνθήκης της if (γρ. 23)
tmp = 144 * 3 = 432 (γρ. 24)
flag = 1 (γρ. 25)
p=0 (γρ. 26)
Α: tmp = 12 * 3 = 36 (γρ. 14)
flag = 1 (γρ. 15)
p=2 (γρ. 16)
Έλεγχος της συνθήκης της while (γρ. 17)
Έλεγχος της συνθήκης της if (γρ. 18)
tmp = 36 * 4 = 144 (γρ. 19)
flag = 0 (γρ. 20)
p=1 (γρ. 21)
Έλεγχος της συνθήκης της while (γρ. 22)
Έλεγχος της συνθήκης της if (γρ. 23)
tmp = 144 * 3 = 432 (γρ. 24)
Β: Έλεγχος της συνθήκης της while (γρ. 27)

2η έκδοση 53
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Α: flag = 1 (γρ. 25)


p=0 (γρ. 26)
Έλεγχος της συνθήκης της while (γρ. 27)
print 432 (γρ. 28)
Β: print 432 (γρ. 28)

Σχήµα 10: 1ο Παράδειγµα ∆υνατής Ταυτόχρονης Εκτέλεσης

∆ιεργασία Α ∆ιεργασία Β
tmp = 1 (γρ. 1)
tmp = 1 (γρ. 1)
Έλεγχος της συνθήκης της while (γρ. 2)
Έλεγχος της συνθήκης της if (γρ. 3)
tmp = 1 * 3 = 3 (γρ. 4)
flag = 1 (γρ. 5)
p=4 (γρ. 6)
Έλεγχος της συνθήκης της while (γρ. 7)
Έλεγχος της συνθήκης της if (γρ. 8)
tmp = 3 * 4 = 12 (γρ. 9)
flag = 0 (γρ. 10)
p=3 (γρ. 11)
Έλεγχος της συνθήκης της while (γρ. 12)
Έλεγχος της συνθήκης της while (γρ. 2)
Έλεγχος της συνθήκης της if (γρ. 3)
tmp = 1 * 3 = 3 (γρ. 4)
flag = 1 (γρ. 5)
p=4 (γρ. 6)
Έλεγχος της συνθήκης της while (γρ. 7)
Έλεγχος της συνθήκης της if (γρ. 8)
tmp = 3 * 4 = 12 (γρ. 9)
flag = 0 (γρ. 10)
p=3 (γρ. 11)
Έλεγχος της συνθήκης της while (γρ. 12)
Χρόνος Έλεγχος της συνθήκης της if (γρ. 13)
tmp = 12 * 3 = 36 (γρ. 14)
flag = 1 (γρ. 15)
p=2 (γρ. 16)
Έλεγχος της συνθήκης της while (γρ. 17)
Έλεγχος της συνθήκης της if (γρ. 18)
tmp = 36 * 4 = 144 (γρ. 19)
flag = 0 (γρ. 20)
Έλεγχος της συνθήκης της if (γρ. 13)
p=1 (γρ. 21)
Έλεγχος της συνθήκης της while (γρ. 22)
Έλεγχος της συνθήκης της if (γρ. 23)
tmp = 144 * 3 = 432 (γρ. 24)
flag = 1 (γρ. 25)
p=0 (γρ. 26)
tmp = 12 * 3 = 36 (γρ. 14)
flag = 1 (γρ. 15)

2η έκδοση 54
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

p=2 (γρ. 16)


Έλεγχος της συνθήκης της while (γρ. 17)
Έλεγχος της συνθήκης της if (γρ. 18)
tmp = 36 * 4 = 144 (γρ. 19)
flag = 0 (γρ. 20)
p=1 (γρ. 21)
Έλεγχος της συνθήκης της while (γρ. 22)
Έλεγχος της συνθήκης της if (γρ. 23)
tmp = 144 * 3 = 432 (γρ. 24)
Έλεγχος της συνθήκης της while (γρ. 27)
flag = 1 (γρ. 25)
p=0 (γρ. 26)
Έλεγχος της συνθήκης της while (γρ. 27)
print 432 (γρ. 28)
print 432 (γρ. 28)

Σχήµα 11: 1ο Παράδειγµα ∆υνατής Ταυτόχρονης Εκτέλεσης: Πιο Ευανάγνωστη Παρουσίαση

∆ιεργασία Α ∆ιεργασία Β
tmp = 1 (γρ. 1)
tmp = 1 (γρ. 1)
Έλεγχος της συνθήκης της while (γρ. 2)
Έλεγχος της συνθήκης της while (γρ. 2)
Έλεγχος της συνθήκης της if (γρ. 3)
Έλεγχος της συνθήκης της if (γρ. 3)
tmp = 1 * 3 = 3 (γρ. 4)
tmp = 1 * 3 = 3 (γρ. 4)
flag = 1 (γρ. 5)
flag = 1 (γρ. 5)
p=4 (γρ. 6)
p=4 (γρ. 6)
Έλεγχος της συνθήκης της while (γρ. 7)
Έλεγχος της συνθήκης της while (γρ. 7)
Έλεγχος της συνθήκης της if (γρ. 8)
Έλεγχος της συνθήκης της if (γρ. 8)
tmp = 3 * 4 = 12 (γρ. 9)
tmp = 3 * 4 = 12 (γρ. 9)
flag = 0 (γρ. 10)
flag = 0 (γρ. 10)
p=3 (γρ. 11)
p=3 (γρ. 11)
Έλεγχος της συνθήκης της while (γρ. 12)
Έλεγχος της συνθήκης της if (γρ. 13)
tmp = 12 * 3 = 36 (γρ. 14)
flag = 1 (γρ. 15)
p=2 (γρ. 16)
Έλεγχος της συνθήκης της while (γρ. 17)
Έλεγχος της συνθήκης της if (γρ. 18)

2η έκδοση 55
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Έλεγχος της συνθήκης της while (γρ. 12)


Έλεγχος της συνθήκης της if (γρ. 13)
tmp = 12 * 3 = 36 (γρ. 14)
flag = 1 (γρ. 15)
p=2 (γρ. 16)
Έλεγχος της συνθήκης της while (γρ. 17)
Έλεγχος της συνθήκης της if (γρ. 18)
tmp = 36 * 4 = 144 (γρ. 19)
flag = 0 (γρ. 20)
p=1 (γρ. 21)
tmp = 36 * 4 = 144 (γρ. 19)
flag = 0 (γρ. 20)
p=1 (γρ. 21)
Έλεγχος της συνθήκης της while (γρ. 22)
Έλεγχος της συνθήκης της while (γρ. 22)
Έλεγχος της συνθήκης της if (γρ. 23)
Έλεγχος της συνθήκης της if (γρ. 23)
tmp = 144 * 3 = 432 (γρ. 24)
flag = 1 (γρ. 25)
p=0 (γρ. 26)
Έλεγχος της συνθήκης της while (γρ. 27)
tmp = 144 * 3 = 432 (γρ. 24)
flag = 1 (γρ. 25)
p=0 (γρ. 26)
Έλεγχος της συνθήκης της while (γρ. 27)
print 432 (γρ. 28)
print 432 (γρ. 28)

Σχήµα 12: 2ο Παράδειγµα ∆υνατής Ταυτόχρονης Εκτέλεσης

Η εντολή (γλωσσική έκφραση)


cobegin s1; s2; ... sn; coend
υποδηλώνει ότι τα µπλοκ κώδικα s1, s2, ..., sn εκτελούνται ταυτόχρονα. Η σειρά µε την
οποία θα εκτελεστούν οι εντολές s1, s2, ..., sn είναι άγνωστη.

Παράδειγµα 6
∆ίνεται το ακόλουθο πρόγραµµα:
shared int tally;
begin
tally := 0;
cobegin
increase();
increase();
coend
end

2η έκδοση 56
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

όπου increase() είναι µια ρουτίνα (που θα δοθεί στη συνέχεια). Τι συµβαίνει όταν
εκτελείται ο παραπάνω κώδικας;
Με βάση όσα ήδη ειπώθηκαν, όταν η εντολή cobegin coend εκτελείται, δηµιουργούνται
δύο διεργασίες που κάθε µια εκτελεί την ρουτίνα increase(). Οι δύο διεργασίες
εκτελούνται ταυτόχρονα. Ενδιαφέρον ωστόσο παρουσιάζει και το γεγονός ότι η tally
είναι µια διαµοιραζόµενη (shared) µεταβλητή. Μια διαµοιραζόµενη µεταβλητή µπορεί να
γραφτεί ή να αναγνωσθεί από περισσότερες από µια διεργασίες που εκτελούνται
ταυτόχρονα. Οι διαµοιραζόµενες µεταβλητές δηλώνονται και αρχικοποιούνται πριν
δηµιουργηθούν οι διεργασίες που θα τις διαχειριστούν. (Στο παράδειγµά µας, οι δύο
αυτές διεργασίες είναι οι διεργασίες που εκτελούν την increase() ταυτόχρονα.)
Η τιµή της tally είναι µια θέση µνήµης που µπορεί να προσπελασθεί και από τις δύο
διεργασίες. Όταν η µία γράφει µια τιµή στην tally, η τιµή αυτή µπορεί να διαβαστεί και
από την άλλη διεργασία. □

Παράδειγµα 7
Έστω ότι ο κώδικας της increase() του Παραδείγµατος 7 είναι ο ακόλουθος:
void increase()
int tmp, i; /* τοπικές µεταβλητές */
begin
tmp = tally;
tmp = tmp+1;
tally = tmp;
end
Έχουµε δύο διεργασίες που εκτελούνται ταυτόχρονα στο σύστηµα µας, έστω ότι η πρώτη
είναι η Α και η δεύτερη η Β. Ο κώδικας που εκτελεί η κάθε διεργασία έχει τη µορφή που
φαίνεται στο Σχήµα 13 (επειδή η µεταβλητή tmp είναι τοπική, έχει αντικατασταθεί στο
Σχήµα 13 από δύο µεταβλητές, tmpA, tmpB, µία για κάθε διεργασία):
Κώδικας διεργασίας A Κώδικας διεργασίας B
1. tmpΑ = tally; 1. tmpB = tally;
2. tmpΑ = tmpΑ +1; 2. tmpB = tmpB +1;
3. tally = tmpΑ; 3. tally = tmpB;

Σχήµα 13: Κώδικες που εκτελούν οι διεργασίες Α και Β.


Κάθε διεργασία εκτελεί µια ανάγνωση και µια εγγραφή στη διαµοιραζόµενη µεταβλητή
tally, δηλαδή συνολικά δύο προσβάσεις στην tally.
Ποιες είναι οι δυνατές τιµές που µπορεί να έχει η tally µετά τον τερµατισµό και των δύο
διεργασιών, δεδοµένου ότι η αρχική της τιµή είναι 0;
Αν οι διεργασίες εκτελεστούν σειριακά, η tally θα αυξηθεί κατά δύο (η πρώτη διεργασία
που θα εκτελεστεί θα αυξήσει την τιµή της σε 1 και στη συνέχεια η δεύτερη θα αυξήσει
την τιµή της σε 2).
Θα µπορούσε όµως να συµβεί το ακόλουθο σενάριο:

2η έκδοση 57
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

∆ιεργασία Α ∆ιεργασία Β
tmpΑ = tally;
tmpΑ = tmpΑ +1;
tmpB = tally;
tmpB = tmpB +1;
tally = tmpΑ;
tally = tmpB;

Σε αυτή την περίπτωση, η διεργασία Α αποθηκεύει στην µεταβλητή tmpA την τιµή της
tally (δηλαδή το 0). Στη συνέχεια αυξάνει την tmpA κατά 1, αλλά πριν προλάβει να
γράψει την αυξηµένη τιµή στην tally, διακόπτεται και αρχίζει να εκτελείται η διεργασία
Β. Με παρόµοιο τρόπο, η Β αποθηκεύει στην µεταβλητή tmpΒ την τιµή της tally (ας
θυµηθούµε ότι η τιµή της tally δεν έχει ακόµη αλλαχθεί και άρα εξακολουθεί να είναι 0).
Στη συνέχεια αυξάνει την tmpA κατά 1 και διακόπτεται. Η διεργασία Α γράφει την τιµή
της tmpA (δηλαδή το 1) στην tally και τερµατίζει. Τέλος, η διεργασία Β γράφει την τιµή
της tmpB (δηλαδή και πάλι το 1) στην tally και τερµατίζει και αυτή. Άρα, η tally έχει
τελικά την τιµή 1.
Το σενάριο που περιγράφτηκε πιο πάνω δεν είναι το µόνο που έχει σαν αποτέλεσµα την
τελική τιµή 1 για την tally. Τι θα συνέβαινε, π.χ., αν η διεργασία Β εκτελούσε την
τελευταία εντολή της πριν από την εκτέλεση της τελευταίας εντολής της Α; Τι θα
συνέβαινε αν η εκτέλεση των δύο διεργασιών γινόταν εντολή-εντολή εναλλάξ; □

Παράδειγµα 8
Έστω τώρα ότι ο κώδικας της increase() του Παραδείγµατος 7 είναι ο ακόλουθος:
void increase()
int tmp, i; /* τοπικές µεταβλητές */
begin
for i=0 to 5 do
begin
tmp = tally;
tmp = tmp+1;
tally = tmp;
end
end
Έχουµε δύο διεργασίες που εκτελούνται ταυτόχρονα στο σύστηµα µας, έστω ότι η πρώτη
είναι η Α και η δεύτερη η Β. Σε κάθε ανακύκλωση της for εντολής, κάθε διεργασία
εκτελεί µια ανάγνωση και µια εγγραφή στη διαµοιραζόµενη µεταβλητή tally, δηλαδή
συνολικά δύο προσβάσεις στην tally.
Ο κώδικας που εκτελεί η κάθε διεργασία έχει εποµένως την παρακάτω µορφή:
Κώδικας διεργασίας A Κώδικας διεργασίας B
for iΑ = 1 to 5 do for iB = 1 to 5 do
begin begin
1. tmpΑ = tally; 1. tmpB = tally;
2. tmpΑ = tmpΑ +1; 2. tmpB = tmpB +1;

2η έκδοση 58
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

3. tally = tmpΑ; 3. tally = tmpB;


end end

όπου οι µεταβλητές tmpA, tmpB, iA και iB είναι τοπικές (µη διαµοιραζόµενες)


µεταβλητές.
Ας µελετήσουµε µερικές δυνατές εκτελέσεις και τις τιµές που θα έχει η tally στο τέλος
κάθε µιας από αυτές. Ας υποθέσουµε πρώτα ότι οι διεργασίες εκτελούνται σειριακά, η
µια µετά την άλλη. Η πρώτη διεργασία (που θα µπορούσε να είναι είτε η Α ή η Β)
αυξάνει την τιµή της tally σε 5. Στη συνέχεια, ξεκινά η δεύτερη διεργασία, η οποία
αρχικά διαβάζει την τιµή 5 στην tally και αυξάνει την tally κατά 1 πέντε φορές (µια φορά
σε κάθε ανακύκλωση της for). Εποµένως, στο τέλος κάθε σειριακής εκτέλεσης, η tally
έχει την τιµή 10.
Το ερώτηµα τώρα είναι τι τιµή θα έχει η tally, αν οι δύο διεργασίες εκτελούν από µια
εντολή η κάθε µια πριν γίνει διακοπή και εκτελεστεί η άλλη (δηλαδή αν η εκτέλεση των
διεργασιών γινόταν εντολή-εντολή εναλλάξ). Συνίσταται ισχυρά στον αναγνώστη να
επαληθεύσει, ότι σε αυτή την περίπτωση η τελική τιµή της tally θα είναι 5. Ο
αναγνώστης θα πρέπει να φτιάξει ένα πίνακα εκτέλεσης, όπως αυτοί που φαίνονται στα
Σχήµατα 11 και 12 και να εξετάσει ποια είναι η τιµή της tally µετά την εκτέλεση κάθε
εντολής.
Το ερώτηµα που θα µας απασχολήσει στη συνέχεια είναι να προσδιοριστεί αν το 5 είναι η
µικρότερη τιµή που µπορεί να έχει τελικά η tally. Με άλλα λόγια, θέλουµε να
προσδιορίσουµε την µικρότερη δυνατή τελική τιµή που µπορεί να έχει η tally.
Ισχυριζόµαστε ότι η ελάχιστη αυτή τιµή είναι το 2. Θα πρέπει να παρουσιάσουµε
σενάριο κατά το οποίο η tally έχει την τιµή 2 µετά τον τερµατισµό των δύο διεργασιών.
Χρησιµοποιούµε το γεγονός ότι ο χρονοδροµολογητής µπορεί να διακόπτει µια
διεργασία σε οποιοδήποτε σηµείο της εκτέλεσής της ως εξής. Παίζουµε το ρόλο του
χρονοδροµολογητή και προσπαθούµε να «µαγειρέψουµε» τις εναλλαγές από την
εκτέλεση της µιας διεργασίας στην άλλη µε τέτοιο τρόπο, ώστε να προκύψει τελικά το
ζητούµενο (στην περίπτωσή µας το ζητούµενο είναι η τελική τιµή της tally να είναι 2).
Παραθέτουµε στη συνέχεια το σενάριο αυτό:
• Η tally έχει αρχικά την τιµή 0.
• Η διεργασία A αποθηκεύει την τιµή 0 στην τοπική µεταβλητή tmpA και πριν
εκτελέσει οποιαδήποτε άλλη εντολή εκτελείται η διεργασία B.
• Η διεργασία B αποθηκεύει στη µεταβλητή tmpB επίσης την τιµή 0. Στη συνέχεια η
ανακύκλωση εκτελείται 4 φορές από τη διεργασία B και αποµένει να εκτελεστεί µια
µόνο τελευταία φορά. Η τιµή της tally είναι στο σηµείο αυτό 4. Πριν εκτελεστεί η
τελευταία ανακύκλωση της εντολής for της διεργασίας B, εκτελείται η διεργασία A.
• Η διεργασία A συνεχίζει την εκτέλεση της από το σηµείο στο οποίο είχε διακοπεί,
δηλαδή εκτελεί τη δεύτερη εντολή της πρώτης ανακύκλωσης της for. Προσέξτε πως
η µεταβλητή tmpA έχει τιµή 0, ενώ η tally έχει τιµή 4, κάτι που ποτέ η διεργασία Α
δεν θα µάθει, αφού στην αµέσως επόµενη εντολή (εντολή 3) πανω-γράφει την τιµή
της tally µε την τιµή 1. Πριν προχωρήσει στην εκτέλεση των υπολοίπων 4
ανακυκλώσεών της, εκτελείται ξανά η διεργασία B.

2η έκδοση 59
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

• Η διεργασία B αποµένει να εκτελέσει την τελευταία της ανακύκλωση. ∆ιαβάζει


εποµένως την tally (εντολή 1) στη µεταβλητή tmpB. Προφανώς, η τιµή που βρίσκει
στην tally είναι η τιµή 1 (την οποία η διεργασία A µόλις έγγραψε εκεί). Προσέξτε
πως όλη η δουλειά της διεργασίας B (4 αυξήσεις στην µεταβλητή tally) έχει τώρα
χαθεί. Πριν η διεργασία B εκτελέσει τις εντολές 2 και 3 της τελευταίας της
ανακύκλωσης, εκτελείται και πάλι η διεργασία Α.
• Η διεργασία Α εκτελείται µέχρι τέλους, αλλάζοντας την τιµή της tally σε 5.
• Αποµένει τέλος να εκτελεστούν οι εντολές 2 και 3 της διεργασίας B, προκειµένου και
αυτή να τερµατίσει. Προσέξτε ότι η µεταβλητή tmpB έχει την τιµή 1, ενώ η tally έχει
τιµή 5, κάτι που ποτέ η διεργασία 1 δεν θα µάθει, αφού θα αυξήσει την τιµή της tmpB
σε 2 (εντολή 2), στη συνέχεια θα πανω-γράψει την τιµή της tally µε την τιµή 2
(εντολή 3) και θα τερµατίσει και αυτή.
• Η τελική τιµή της µεταβλητής tally είναι 2.
Το παραπάνω σενάριο περιγράφεται συνοπτικά (και µε λίγο συµπυκνωµένο τρόπο) στο
Σχήµα 14.
∆ιεργασία Α ∆ιεργασία Β
tmpΑ = tally;
/* Στην tmpA αποθηκεύεται η τιµή 0 */
tmpB = tally; tmpB = tmpB +1; tally = tmpB;
/* Η tally έχει τιµή 1 */
tmpB = tally; tmpB = tmpB +1; tally = tmpB;
/* Η tally έχει τιµή 2 */
tmpB = tally; tmpB = tmpB +1; tally = tmpB;
/* Η tally έχει τιµή 3 */
tmpB = tally; tmpB = tmpB +1; tally = tmpB;
/* Η tally έχει τιµή 4 */
tmpΑ = tmpΑ +1; tally = tmpΑ;
/* Η tally έχει τιµή 1 */
tmpB = tally;
/* Στην tmpB αποθηκεύεται η τιµή 1 */

tmpΑ = tally; tmpΑ = tmpΑ +1; tally = tmpΑ;


/* Η tally έχει τιµή 2 */
tmpΑ = tally; tmpΑ = tmpΑ +1; tally = tmpΑ;
/* Η tally έχει τιµή 3 */
tmpΑ = tally; tmpΑ = tmpΑ +1; tally = tmpΑ;
/* Η tally έχει τιµή 4 */
tmpΑ = tally; tmpΑ = tmpΑ +1; tally = tmpΑ;
/* Η tally έχει τιµή 5 */ tmpB = tmpB +1; tally = tmpB;
/* Η tally έχει τιµή 2 */

Σχήµα 14: Σενάριο που οδηγεί στην µικρότερη τιµή για τη διαµοιραζόµενη µεταβλητή tally.

2η έκδοση 60
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Παρατήρηση
Το σετ εντολών:
tmp = tally;
tmp = tmp+1;
tally = tmp;
µπορεί να γραφτεί µε πιο συνοπτικό τρόπο ως εξής:
tally = tally + 1;
H tally είναι µια διαµοιραζόµενη µεταβλητή. Η εντολή “tally = tally +1;” προϋποθέτει
δύο προσπελάσεις στην tally. Για να γίνει αυτό, η τιµή της tally θα πρέπει να διαβαστεί
σε µια τοπική µεταβλητή, η τιµή της τοπικής µεταβλητής να αυξηθεί και η νέα τιµή να
γραφτεί στην tally. Εποµένως, κάθε διεργασία που πρέπει να εκτελέσει την εντολή “tally
= tally +1;” θα εκτελέσει, στην πράξη, το µπλοκ των τριών εντολών “tmp = tally; tmp =
tmp +1; tally = tmp;”.
Γενικότερα, ο αναγνώστης θα πρέπει να θυµάται, ότι δεν είναι δυνατόν να εκτελεστούν
σαν µια αδιαίρετη εντολή περισσότερες από µια προσπελάσεις σε µια διαµοιραζόµενη
µεταβλητή (π.χ., ανάγνωση και τροποποίηση της τιµής της). Έτσι, εντολές όπως η “tally =
tally + 1;” υποκρύπτουν την εκτέλεση περισσότερων της µιας εντολών (στην περίπτωσή
µας των τριών εντολών που παρουσιάστηκαν πιο πάνω). □

Άσκηση Αυτοαξιολόγησης 5
1. Θεωρήστε και πάλι τον κώδικα της increase() του Παραδείγµατος 9. Για κάθε τιµή
x, 2 < x ≤ 10, βρείτε ένα σενάριο στο οποίο η tally έχει τελική τιµή x.
2. Θεωρήστε και πάλι τον κώδικα της increase() του Παραδείγµατος 9. Για κάθε τιµή
του x, 1 < x ≤ 10, υπάρχει ένα µόνο σενάριο στο οποίο η tally έχει τελική τιµή x;
Προσπαθήστε να βρείτε όσο το δυνατό περισσότερα τέτοια σενάρια.
3. (Άσκηση Αυτοαξιολόγησης 3.1, Τόµος Γ, Λειτουργικά Συστήµατα Ι) Έστω ότι ο
κώδικας της increase() είναι αυτός που φαίνεται στη συνέχεια:
void increase()
int tmp, i; /* τοπικές µεταβλητές */
begin
for i=0 to 50 do
tally = tally + 1;
end
Ποια είναι η µικρότερη και ποια η µεγαλύτερη τελική τιµή που µπορεί να έχει η tally,
όταν ο παραπάνω κώδικας εκτελείται ταυτόχρονα από δύο διεργασίες; Ποιες είναι οι
δυνατές τελικές τιµές που µπορεί να έχει η tally;
4. Έστω ότι ο κώδικας της increase() του Παραδείγµατος 9 εκτελείται ταυτόχρονα από
3 και όχι από 2 διεργασίες. Ποια είναι η µικρότερη και ποια η µεγαλύτερη τελική
τιµή που µπορεί να έχει η tally σε αυτή την περίπτωση;
Υπόδειξη: ∆είξτε ότι η µικρότερη τελική τιµή που µπορεί να έχει η tally είναι και πάλι 2. To σενάριο
που οδηγεί στην τιµή αυτή δεν είναι πολύ διαφορετικό από αυτό που περιγράφεται στο Σχήµα 14.
Είναι ωστόσο λίγο πιο πολύπλοκο, λόγω της ύπαρξης της τρίτης διεργασίας (της οποίας οι αυξήσεις
στην tally θα πρέπει επίσης να πανω-γραφτούν).

2η έκδοση 61
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

5. Ποιες είναι οι δυνατές τελικές τιµές που µπορεί να έχει η tally στην περίπτωση του
ερωτήµατος 3;
6. Επαναλάβετε τα ερωτήµατα 4 και 5 για 4 διεργασίες, 5 διεργασίες και γενικότερα n
διεργασίες.
Υπόδειξη: ∆είξτε ότι ανεξάρτητα από τον αριθµό των διεργασιών που εµπλέκονται, η µικρότερη
τελική τιµή που µπορεί να έχει η tally είναι 2.

3.2 Η Ανάγκη Συγχρονισµού


Για να γίνει κατανοητή η ανάγκη συγχρονισµού θα µελετήσουµε διάφορα παραδείγµατα.
Κάποια από αυτά περιγράφονται στην αναφορά ∆. Καλλές, Κ. Σγάρµπας, Β. Ταµπακάς,
Σηµαντικά σηµεία στη µελέτη του τόµου «Λειτουργικά Συστήµατα Ι» της ΘΕ ΠΛΗ-11
«Αρχές Τεχνολογίας Λογισµικού».
Ας θεωρήσουµε δυο διεργασίες που εκτελούνται ταυτόχρονα. Για παράδειγµα, έστω ότι
η µια διαχειρίζεται ένα έγγραφο στο Word και η άλλη ένα φύλλο στο Excel. Έστω ότι
θέλουµε να τυπώσουµε τα δυο ανοιγµένα αρχεία (.doc, .xls) στον εκτυπωτή. Έστω ότι
δίνουµε εντολή στο ένα πρόγραµµα να τυπώσει και πριν καλά-καλά αρχίσει η εκτύπωση
δίνουµε την ίδια εντολή και στο άλλο πρόγραµµα. Τι θα συµβεί;
Κανονικά δεν θα έπρεπε να συµβεί κάτι παράξενο. Όσο η µία διεργασία εκτύπωσης θα
χρησιµοποιεί τον εκτυπωτή, η άλλη θα περιµένει. Η δεύτερη θα αρχίσει µόλις τελειώσει
η πρώτη. Έτσι θα πάρουµε δυο διαφορετικά τυπωµένα χαρτιά που το καθένα θα
αντιστοιχεί σε µια διεργασία. Παρότι φαίνεται απλό, υπάρχει µια σηµαντική εργασία που
γίνεται από το λειτουργικό σύστηµα για να µην µπερδευτούν οι εκτυπώσεις και να µην
πάρουµε ένα χαρτί που θα γράφει κάτι τέτοιο:

ΛΙΣΤΑ ΑΓΟΡΩΝ Αγαπητέ κ. Παπασταύρου, Είδος Ποσότητα Τιµή


Τεµαχίου/Κιλού Σύνολο Η τελευταία σας εργασία στα Σταφύλια 2
λειτουργικά συστήµατα 1,80€ ήταν ιδιαίτερα προσεγµένη, 3,60€ όµως θα
συνιστούσα µια Κεφαλογραβιέρα Παρνασσού επανάληψη 1,5 στην ύλη του
2,75€ κεφαλαίου 3 4,12€ που αφορά Μπαταρίες 4 τον συντονισµό 0,25€
των 1€ διαδικασιών. Με Κόλλα δερµάτων 1 0,5€ 0,5€ εκτίµηση, ο
καθηγητής σας.

Στο παράδειγµά µας, δυο διεργασίες εκτελούνται ταυτόχρονα και χρησιµοποιούν τον ίδιο
πόρο συστήµατος, τον εκτυπωτή. Το ΛΣ οφείλει να συντονίσει τις διεργασίες που
επιχειρούν να χρησιµοποιήσουν τον ίδιο πόρο, ώστε η µία να περιµένει την άλλη.
Το παραπάνω (λίγο απλουστευτικό) σενάριο περιγράφεται µόνο για λόγους κατανόησης
της ανάγκης συγχρονισµού. Όπως έχει ήδη αναφερθεί, οι διεργασίες του χρήστη δεν
έχουν από µόνες τους πρόσβαση σε συσκευές Ε/Ε, όπως ο εκτυπωτής, και άρα το
παραπάνω σενάριο µπερδεµένης εκτύπωσης δεν θα µπορούσε να συµβεί, αφού το ΛΣ
είναι εκείνο που θα έχει τον έλεγχο και των δύο εκτυπώσεων. Επίσης, λόγω της τεχνικής
της παροχέτευσης, η εντολή print από κάθε διεργασία του παραδείγµατος θα προκαλέσει
απλά την εγγραφή των προς εκτύπωση δεδοµένων πρώτα στο δίσκο. Είναι ωστόσο
αξιοσηµείωτο ότι παρά τη χρήση της τεχνικής της παροχέτευσης, η ανάγκη

2η έκδοση 62
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

συγχρονισµού εξακολουθεί να υφίσταται. Θεωρήστε πως οι διεργασίες του τοποθετούν


πληροφορίες για το αρχείο που θέλουν να εκτυπώσουν σε µια ουρά, την οποία το ΛΣ
εξερευνά στη συνέχεια, προκειµένου να διεκπεραιώσει την πραγµάτωση των
εκτυπώσεων. Έστω ότι η ουρά υλοποιείται µε έναν κυκλικό πίνακα, όπως φαίνεται στο
Σχήµα 15. Η µεταβλητή front δείχνει τη θέση του πίνακα στην οποία αναγράφονται
πληροφορίες για το επόµενο προς εκτύπωση αρχείο, ενώ η µεταβλητή back δείχνει τη
θέση του πίνακα στην οποία θα πρέπει να καταγραφούν πληροφορίες για το επόµενο
αρχείο που θα ζητηθεί να εκτυπωθεί.

front
2 back
1
file2.txt 3
file1.txt

6
4

Σχήµα 15: Ουρά στην οποία καταγράφονται πληροφορίες για τα προς εκτύπωση αρχεία.

Θεωρείστε και πάλι ότι δύο διεργασίες Α και Β θέλουν να εκτυπώσουν από ένα αρχείο η
κάθε µια. Η Α διαβάζει την κοινή µεταβλητή back για να βρει σε ποια θέση του πίνακα
θα πρέπει να τοποθετήσει πληροφορίες για το αρχείο που θέλει να εκτυπώσει. Έστω ότι η
µεταβλητή back έχει την τιµή 3 (όπως φαίνεται στο σχήµα). Στη συνέχεια, η Β διαβάζει
και εκείνη την back και την βρίσκει επίσης 3 (αφού η Α δεν πρόλαβε να αλλάξει την τιµή
της σε 4). Στη συνέχεια, η Α τοποθετεί πληροφορίες για το αρχείο της στη θέση 3 του
πίνακα και αλλάζει την τιµή της back σε 4. Το ίδιο ακριβώς θα κάνει όµως και η Β,
πανω-γράφοντας τις πληροφορίες που έγραψε η Α στη θέση 3. Έτσι, η εκτύπωση της Α
δεν θα πραγµατοποιηθεί. Για να αποφευχθούν τέτοια ανεπιθύµητα σενάρια, απαιτείται να
υπάρχει συντονισµός των διεργασιών κατά την πρόσβαση στις κοινές µεταβλητές front
και back.
Ας δούµε τώρα ένα άλλο παράδειγµα µε λίγη περισσότερη λεπτοµέρεια. Θεωρείστε έναν
τραπεζικό λογαριασµό που περιέχει 500€ και έστω ότι υπάρχουν δυο κάρτες ΑΤΜ
συνδεδεµένες µε αυτό το λογαριασµό, τις οποίες διαχειρίζονται δύο διαφορετικά άτοµα
(π.χ., ένας χρήστης και η αδελφή του).
Κάθε ΑΤΜ είναι συνδεδεµένο µε έναν κεντρικό υπολογιστή της τράπεζας, ο οποίος
ελέγχει τις κινήσεις των τραπεζικών λογαριασµών. Επειδή ο ίδιος υπολογιστής
εξυπηρετεί µερικές εκατοντάδες ΑΤΜ, χρησιµοποιεί αναγκαστικά την τεχνική της

2η έκδοση 63
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

δροµολόγησης, ώστε να εξυπηρετεί τις διεργασίες που ζητούνται να εκτελεστούν


ταυτόχρονα µέσω των διαφορετικών ΑΤΜ.
Έστω ότι η ρουτίνα που εκτελείται κατά τη λειτουργία της ανάληψης από τον τραπεζικό
λογαριασµό είναι η ακόλουθη:
shared int balance;
boolean withdraw(int amount)
begin
if (amount > balance)
begin
print “∆εν επιτρέπεται ανάληψη τόσο µεγάλου ποσού!”;
return FALSE;
end
else
begin
balance = balance – amount;
print “Το νέο σας ποσό είναι”;
print balance;
return TRUE;
end
end
Πιο αναλυτικά, οι λειτουργίες που συµβαίνουν κατά την ανάληψη είναι:
1. ∆ιάβασµα του ποσού της ανάληψης και αποθήκευσή του στη µεταβλητή amount.
2. ∆ιάβασµα του υπολοίπου του λογαριασµού που είναι αποθηκευµένο στη
διαµοιραζόµενη µεταβλητή balance.
3. Αν amount > balance εκτύπωση µηνύµατος "∆εν επιτρέπεται ανάληψη τόσο µεγάλου
ποσού" και τερµατισµός εκτέλεσης.
4. ∆ιαφορετικά, αφαίρεση του amount από το balance και ορισµός του αποτελέσµατος
της αφαίρεσης ως νέα τιµή της διαµοιραζόµενης µεταβλητής balance.
5. Απόδοση του ποσού amount στον πελάτη.
6. Εκτύπωση του µηνύµατος "Το νέο σας υπόλοιπο είναι <balance>" και τερµατισµός
εκτέλεσης.
Ας υποθέσουµε ότι κάποια µέρα ο χρήστης και η αδελφή του πηγαίνουν ταυτόχρονα σε
δύο διαφορετικά ΑΤΜ (έστω ATM-1 και ATM-2) θέλοντας να κάνουν ανάληψη 500€ ο
καθένας. Τι θα συµβεί;
Αν η αδελφή του χρήστη είναι πιο τυχερή από τον ίδιο θα προλάβει να τελειώσει την
ανάληψη της πριν η ανάληψη του χρήστη ξεκινήσει. Έτσι, εκείνη θα πάρει τα 500€ από
το κοινό βιβλιάριο και ο χρήστης θα διαβάσει το µήνυµα «∆εν έχετε τόσα χρήµατα στο
λογαριασµό σας». Στην περίπτωση τώρα που ο χρήστης είναι πιο τυχερός, θα συµβεί
ακριβώς το αντίθετο. Υπάρχει όµως και ένα ακόµη σενάριο στο οποίο η τύχη ευνοεί και
τους δυο (εις βάρος φυσικά της τράπεζας). Ας προσπαθήσουµε να το διερευνήσουµε. Το
σενάριο παρουσιάζεται στο Σχήµα 16.

2η έκδοση 64
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Το σενάριο αυτό θα οδηγούσε σε ανάληψη συνολικού ποσού 1000€ από ένα λογαριασµό
που περιέχει µόνο 500€ και είναι ένας καλός τρόπος µε τον οποίο ο χρήστης και η
αδελφή του µπορούν να κλέψουν την τράπεζα! Όλα αυτά φυσικά θα συµβούν µόνο στην
περίπτωση που ο προγραµµατιστής του υπολογιστή της τράπεζας δεν γνωρίζει καλά το
αντικείµενο των λειτουργικών συστηµάτων και δεν έχει µεριµνήσει για το συντονισµό
των διεργασιών που διαχειρίζονται τον κοινό πόρο (που στο παράδειγµα αυτό δεν είναι
άλλος από την διαµοιραζόµενη µεταβλητή balance).
Στο παραπάνω παράδειγµα υποθέσαµε ότι οι εντολές για κάθε ΑΤΜ εκτελούνται µια-µια
εναλλάξ. Υπάρχουν ωστόσο και άλλα σενάρια που οδηγούν στην κλοπή της τράπεζας µε
τον τρόπο που περιγράφτηκε πιο πάνω. Συνίσταται ισχυρά στον αναγνώστη να
προσπαθήσει να βρει µερικές ακόµη τέτοιες εκτελέσεις.
ATM-1 (διεργασία χρήστη) ATM-2 (διεργασία αδελφής χρήστη)
amount = 500
amount = 500
balance = 500
balance = 500
Χρόνος Έλεγχος if (είναι TRUE)
Έλεγχος if (είναι TRUE)
balance = balance – amount
balance = balance – amount
print “Το νέο σας ποσό είναι”
print balance
print “Το νέο σας ποσό είναι”
print balance

Σχήµα 16: Σενάριο που οδηγεί σε ανάληψη 1000€ από ένα λογαριασµό που περιέχει µόνο 500€.

Άσκηση Αυτοαξιολόγησης 6
Έστω ότι ένας τραπεζικός λογαριασµός περιέχει 500€ και έστω ότι υπάρχουν τρεις
κάρτες ΑΤΜ συνδεδεµένες µε αυτόν το λογαριασµό, τις οποίες διαχειρίζονται τρία
διαφορετικά άτοµα (π.χ., ένας χρήστης και οι δυο γονείς του).
Ας θεωρήσουµε ότι ο χρήστης θέλει να κάνει µια αγορά των 1500€ και, επειδή τα 500€
του λογαριασµού δεν επαρκούν, έχει συνεννοηθεί µε τους γονείς του να καταθέσουν τα
υπόλοιπα 1000€ που χρειάζεται. Οι γονείς του έχουν συνεννοηθεί µεταξύ τους να
καταθέσουν ο κάθε ένας από 500€.
Κάθε ΑΤΜ είναι συνδεδεµένο µε έναν κεντρικό υπολογιστή της τράπεζας, ο οποίος
ελέγχει τις κινήσεις των τραπεζικών λογαριασµών. Επειδή ο ίδιος υπολογιστής
εξυπηρετεί µερικές εκατοντάδες ΑΤΜ, χρησιµοποιεί αναγκαστικά την τεχνική της
δροµολόγησης ώστε να εξυπηρετεί τις διεργασίες που ζητούνται να εκτελεστούν
ταυτόχρονα µέσω των διαφορετικών ΑΤΜ.
Έστω ότι η ρουτίνα που εκτελείται κατά τη λειτουργία της κατάθεσης από τον τραπεζικό
λογαριασµό είναι η ακόλουθη:
shared int balance;
boolean deposit(int amount)

2η έκδοση 65
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

begin
balance = balance + amount;
print “Το νέο σας ποσό είναι”;
print balance;
return TRUE;
end
Θεωρείστε ότι και οι δύο γονείς φθάνουν σε διαφορετικά υποκαταστήµατα της τράπεζας
ταυτόχρονα. Περιγράψτε ένα «ατυχές» σενάριο στο οποίο, παρότι και οι δύο γονείς θα
καταθέσουν από 500€ ο καθένας, ο λογαριασµός δεν θα έχει περισσότερα από 1000€ στο
τέλος (αντί για 1500€ που θα έπρεπε να περιέχει). Αυτός θα είναι και ένας καλός τρόπος
για να κλέβει η τράπεζα τους χρήστες της!

3.3 Το Πρόβληµα του Αµοιβαίου Αποκλεισµού


Από τις προηγούµενες ενότητες θα πρέπει να έχει γίνει κατανοητό ότι υπάρχουν
καταστάσεις στις οποίες δύο ή περισσότερες διεργασίες προσπελάζουν έναν κοινό πόρο
(π.χ., διαβάζουν ή γράφουν µια κοινή µεταβλητή) και το τελικό αποτέλεσµα εξαρτάται
από τη σειρά µε την οποία εκτελείται η κάθε µια. Τέτοιες καταστάσεις ονοµάζονται
συνθήκες ανταγωνισµού (race conditions). Το τµήµα ενός προγράµµατος στο οποίο
γίνεται προσπέλαση κάποιου κοινού πόρου (π.χ., κάποιων κοινών µεταβλητών)
ονοµάζεται κρίσιµο τµήµα (ή κρίσιµη περιοχή). Το υπόλοιπο µέρος του προγράµµατος
ονοµάζεται µη-κρίσιµο τµήµα. Είναι ίσως φανερό, πως για την αποφυγή προβληµάτων θα
πρέπει να µην µπορούν δύο ή περισσότερες διεργασίες να εκτελούν ταυτόχρονα κρίσιµα
τµήµατα του κώδικά τους που προσπελάζουν τις ίδιες κοινές µεταβλητές. Με άλλα λόγια,
η εκτέλεση τέτοιων κρίσιµων τµηµάτων θα πρέπει να γίνεται ατοµικά. Χρειαζόµαστε
εποµένως µια µέθοδο που θα εγγυάται ότι αν µια διεργασία χρησιµοποιεί κάποιο κοινό
πόρο, οι άλλες διεργασίες αποκλείονται από την εκτέλεση της ίδιας ενέργειας. Το
πρόβληµα εύρεσης µιας τέτοιας µεθόδου είναι γνωστό ως πρόβληµα του αµοιβαίου
αποκλεισµού.
Μια λύση στο πρόβληµα του αµοιβαίου αποκλεισµού απαιτεί την σχεδίαση κατάλληλου
κώδικα εισόδου, δηλαδή κώδικα που εκτελείται από κάθε διεργασία αµέσως πριν την
είσοδό της στο κρίσιµο τµήµα (δηλαδή πριν την εκτέλεση των λειτουργιών του κρίσιµου
τµήµατος), καθώς και την σχεδίαση κατάλληλου κώδικα εξόδου, δηλαδή κώδικα που
εκτελείται από τη διεργασία αµέσως µετά την έξοδο από το κρίσιµο τµήµα (δηλαδή,
αµέσως µετά την περάτωση εκτέλεσης των λειτουργιών του κρίσιµου τµήµατος της
διεργασίας). Άρα, αν <κρίσιµο τµήµα> είναι το µπλοκ εντολών που απαιτείται να
εκτελέσει η διεργασία στο κρίσιµό τµήµα της, τότε ο κώδικας που εκτελείται από τη
διεργασία πρέπει να έχει τη µορφή:
<κώδικας εισόδου>;
<κρίσιµο τµήµα>;
<κώδικας εξόδου>;
<µη-κρίσιµο τµήµα>;
Οι κώδικες εισόδου και εξόδου θα πρέπει να σχεδιαστούν µε τέτοιο τρόπο ώστε να
εγγυώνται τα ακόλουθα:

2η έκδοση 66
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

1. Αµοιβαίο αποκλεισµό. Όταν µια διεργασία εκτελεί το κρίσιµο τµήµα της καµία
άλλη διεργασία δεν επιτρέπεται να εκτελεί το κρίσιµο τµήµα της (δηλαδή το κρίσιµο
τµήµα πρέπει να εκτελείται ατοµικά).
2. Πρόοδος. Αν καµία διεργασία δεν εκτελεί το κρίσιµο τµήµα της και υπάρχει κάποια
διεργασία που επιθυµεί να εισέλθει (να εκτελέσει) στο κρίσιµο τµήµα της, τότε µόνο
εκείνες οι διεργασίες που δεν εκτελούν το µη-κρίσιµο τµήµα τους θα πρέπει να
µπορούν να επηρεάζουν την απόφαση του ποια διεργασία θα εισέλθει τελικά στο
κρίσιµο τµήµα της, και η επιλογή αυτή δεν µπορεί να αναβάλλεται επ’ αόριστο
(δηλαδή, τελικά θα πρέπει κάποια διεργασία να εισέλθει στο κρίσιµο τµήµα της).
Η πρώτη συνθήκη υποδηλώνει πως δύο διεργασίες δεν µπορούν να βρίσκονται ποτέ
ταυτόχρονα στο κρίσιµο τµήµα τους. Έτσι, αν µια διεργασία βρίσκεται στο κρίσιµο
τµήµα της, οι υπόλοιπες θα πρέπει είτε να εκτελούν το µη-κρίσιµο τµήµα τους ή να
εκτελούν επαναληπτικά κάποιο µέρος του κώδικα εισόδου (π.χ., κάποια εντολή
ανακύκλωσης της οποίας η συνθήκη θα αλλάξει µόνο όταν η διεργασία που βρίσκεται
στο κρίσιµο τµήµα εξέλθει από αυτό, εκτελώντας τον κώδικα εξόδου). Μια πιο
προσεκτική µελέτη της δεύτερης συνθήκης µας οδηγεί στο συµπέρασµα ότι αν µια
διεργασία εκτελεί το µη-κρίσιµο τµήµα της δεν επιτρέπεται να µπλοκάρει άλλες
διεργασίες από το να εισέλθουν στο κρίσιµο τµήµα τους. Επίσης, η δεύτερη συνθήκη
υποδηλώνει ότι αν µια ή περισσότερες διεργασίες θέλουν να εισέλθουν στο κρίσιµο
τµήµα, τουλάχιστον µία θα πρέπει τελικά να το καταφέρει.
Θεωρούµε ότι η εκτέλεση του κρίσιµου τµήµατος απαιτεί πεπερασµένο χρόνο για κάθε
διεργασία. Το ίδιο απαιτείται να ισχύει για τον κώδικα εξόδου.
Μια λύση που ικανοποιεί τις παραπάνω συνθήκες θεωρείται σωστή (αν και ίσως όχι
δίκαιη) λύση στο πρόβληµα του αµοιβαίου αποκλεισµού. Προφανώς, οι συνθήκες αυτές
θα πρέπει να ισχύουν στην γενική περίπτωση που δεν έχει γίνει κανενός είδους υπόθεση
για το πως δροµολογούνται οι διεργασίες, µε τι ταχύτητα εκτελείται η κάθε µια, κ.ο.κ.
Μια λύση στο πρόβληµα του αµοιβαίου αποκλεισµού µπορεί επιπλέον να ικανοποιεί µια
από τις ακόλουθες συνθήκες δικαιοσύνης:
1. Αποφυγή Παρατεταµένης Στέρησης. ∆εν επιτρέπεται µια διεργασία να περιµένει
επ’ αόριστο για να εισέλθει στην κρίσιµη περιοχή της.
2. Άνω Φραγµένη Καθυστέρηση. ∆εδοµένου ότι µια διεργασία Α ενδιαφέρεται να
εισέλθει στο κρίσιµο τµήµα της, θα πρέπει να υπάρχει ένα άνω όριο στον αριθµό των
φορών που κάθε άλλη διεργασία µπορεί να εισέλθει στο κρίσιµο τµήµα της πριν η Α
τελικά καταφέρει να εισέλθει στο κρίσιµο τµήµα της.
Συνήθως οι διεργασίες εκτελούν επαναληπτικά ένα κρίσιµο τµήµα ακολουθούµενο από
ένα µη-κρίσιµο τµήµα (ή το αντίστροφο). Έτσι, τις περισσότερες φορές η γενική µορφή
του κώδικα των διεργασιών θα έχει κάποια από τις µορφές του Σχήµατος 17 (στη
συνέχεια, συνήθως χρησιµοποιείται η 1η από αυτές).
1η Εναλλακτική 2η Εναλλακτική 3η Εναλλακτική
repeat repeat while (TRUE)
begin begin
begin
<κώδικας εισόδου>; <κώδικας εισόδου>;
<κώδικας εισόδου>;

2η έκδοση 67
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

<κρίσιµο τµήµα>; <κρίσιµο τµήµα>; <κρίσιµο τµήµα>;


<κώδικας εξόδου>; <κώδικας εξόδου>; <κώδικας εξόδου>;
<µη κρίσιµο τµήµα>; <µη κρίσιµο τµήµα>; <µη κρίσιµο τµήµα>;
end end end
forever; until FALSE;

Σχήµα 17: Εναλλακτικές ισοδύναµες γενικές µορφές κώδικα διεργασιών.

Θα πρέπει να γίνει σαφές, ότι η ατοµική εκτέλεση του κρίσιµου τµήµατος µιας
διεργασίας δεν συνεπάγεται σε καµία περίπτωση ότι η διεργασία δεν θα διακοπεί ενόσω
εκτελεί το κρίσιµο τµήµα της. Τέτοιες διακοπές µπορούν φυσικά να συµβαίνουν.
Ωστόσο, όλες οι άλλες διεργασίες που ενδιαφέρονται να µπουν στο κρίσιµο τµήµα τους
θα πρέπει να είναι µπλοκαρισµένες στον κώδικα εισόδου, δηλαδή να εκτελούν κάποια
εντολή ανακύκλωσης που είναι σίγουρο πως θα τερµατίσει µόνο αν η διεργασία που
βρίσκεται στο κρίσιµο τµήµα εξέλθει από αυτό. Για παράδειγµα, έστω ότι δύο διεργασίες
Α και Β ενδιαφέρονται να εισέλθουν στο κρίσιµο τµήµα τους. Η εκτέλεση που
παρουσιάζεται στο Σχήµα 18 θα µπορούσε να συµβεί.

∆ιεργασία Α ∆ιεργασία Β
εκτέλεση µέρους του κώδικα εισόδου;
εκτέλεση κώδικα εισόδου;
εκτέλεση µέρους του κρίσιµου τµήµατος;
εκτέλεση µέρους του κώδικα εισόδου;
/* Ο κώδικας εισόδου θα πρέπει να έχει τέτοια
µορφή που να µην είναι δυνατό στην Α να
εισέλθει στο κρίσιµο τµήµα όσο βρίσκεται εκεί
η Β*/
εκτέλεση µέρους του κρίσιµου τµήµατος;

εκτέλεση µέρους του κώδικα εισόδου;


εκτέλεση υπόλοιπου του κρίσιµου τµήµατος;
εκτέλεση µέρους του κώδικα εισόδου;
εκτέλεση κώδικα εξόδου;
εκτέλεση κρίσιµου τµήµατος;
/* Σε αυτό το σηµείο επιτρέπεται να εισέλθει .
η Α */ .
. .
.
.

Σχήµα 18: ∆ιακοπές ενόσω µια διεργασία εκτελεί το κρίσιµο τµήµα της.

Στο Σχήµα 18 χρησιµοποιείται γραµµατοσειρά κόκκινου χρώµατος για τον κώδικα


εισόδου και γραµµατοσειρά πράσινου χρώµατος για τον κώδικα εξόδου.

2η έκδοση 68
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

3.4 Λύσεις µε τη Βοήθεια του Υλικού


Στην ενότητα αυτή, θα συζητηθούν λύσεις στο πρόβληµα του αµοιβαίου αποκλεισµού,
βασισµένες στην υπόθεση ότι το υλικό παρέχει κάποιες εντολές που µπορούν να
εκτελεστούν ατοµικά. Οι εντολές αυτές εκτελούνται σε ένα κύκλο λειτουργίας ΚΜΕ και
άρα είναι εγγυηµένο από το υλικό, ότι ακόµη και αν πολλές διεργασίες επιθυµούν να
εκτελέσουν ταυτόχρονα κάποιες τέτοιες εντολές, αυτές θα εκτελεστούν τελικά σειριακά
µε κάποια σειρά (η σειρά είναι ωστόσο αυθαίρετη, ενώ υποθέσεις για αυτήν δεν
επιτρέπονται).
Μια τέτοια εντολή είναι η Test&Set() που περιγράφεται στο Σχήµα 19.
boolean Test&Set(boolean lock)
int tmp; /* τοπική µεταβλητή */
begin
tmp = lock;
lock = TRUE;
return(tmp);
end

Σχήµα 19: Η εντολή Test&Set().

H Test&Set() παίρνει ως όρισµα µια boolean µεταβλητή lock. Η Test&Set() αποθηκεύει


την τιµή TRUE στη lock και επιστρέφει την παλιά τιµή της lock. Έτσι, αν η lock είχε
τιµή FALSE κατά την κλήση της Test&Set(), η Test&Set() επιστρέφει FALSE,
διαφορετικά επιστρέφει TRUE. Σε κάθε περίπτωση, η τιµή της lock µετά την εκτέλεση
της Test&Set() είναι TRUE. Παρατηρήστε ότι η Test&Set() είναι µια σύνθετη εντολή
που εκτελεί δύο προσπελάσεις στη διαµοιραζόµενη µεταβλητή lock. Παρόλα αυτά
υποθέτουµε ότι το υλικό υποστηρίζει την ατοµική εκτέλεση της εντολής αυτής.
Ας εξετάσουµε τώρα πως µπορεί να επιλυθεί το πρόβληµα του αµοιβαίου αποκλεισµού,
δεδοµένου ότι το υλικό παρέχει την εντολή Test&Set() και εγγυάται ότι αυτή θα
εκτελεστεί ατοµικά (δηλαδή όσο µια διεργασία εκτελεί την Test&Set() καµία άλλη
διεργασία δεν µπορεί να ξεκινήσει να εκτελεί την Test&Set()).
Κάθε µια από τις διεργασίες εκτελεί τον κώδικα του Σχήµατος 20.
shared boolean lock; /* κοινή µεταβλητή, αρχικά, FALSE */
int tmp; /* τοπική µεταβλητή της εκάστοτε διεργασίας*/
repeat
begin
tmp = Test&Set(lock);
while (tmp == TRUE) do tmp = Test&Set(lock);
<κρίσιµο τµήµα>;
lock = FALSE;
<µη-κρίσιµο τµήµα>;
end
forever;

Σχήµα 20: Λύση του προβλήµατος αµοιβαίου αποκλεισµού µε χρήση της εντολής Test&Set().

2η έκδοση 69
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Η µεταβλητή lock έχει αρχικά την τιµή FALSE, ενώ το υλικό εγγυάται ότι η Test&Set()
θα εκτελεστεί ατοµικά. Η πρώτη διεργασία, έστω Α, που θα εκτελέσει την Test&Set() θα
πάρει ως αποτέλεσµα FALSE, ενώ η τιµή της διαµοιραζόµενης µεταβλητής lock θα
αλλάξει σε TRUE. Η τιµή FALSE θα αποθηκευτεί στην τοπική µεταβλητή tmp της Α και
η συνθήκη της while για την Α θα αποτιµηθεί σε FALSE. Η Α θα εισέλθει εποµένως στο
κρίσιµο τµήµα της. Η Test&Set() επιστρέφει TRUE σε κάθε άλλη διεργασία που την
καλεί (αφού µετά την περάτωση της πρώτης κλήσης της, που έγινε από την Α, η τιµή της
lock έχει αλλάξει σε TRUE). Έτσι, οι υπόλοιπες διεργασίες θα µπλοκάρουν στη while
του κώδικα εισόδου. Όταν η Α βγει από το κρίσιµο τµήµα της, θα αλλάξει την τιµή της
lock σε FALSE για να δώσει τη δυνατότητα σε κάποια από τις διεργασίες που
ενδιαφέρονται να εισέλθουν στο κρίσιµο τµήµα τους να το πραγµατοποιήσει.

Άσκηση Αυτοαξιολόγησης 7 (Θέµα 1, Ερώτηµα 5, Εργασία 4, Ακ. Έτος 2001-2002 )


Θα ήταν σωστή η λύση του Σχήµατος 20 αν η Test&Set() δεν ήταν εγγυηµένο πως θα
εκτελεστεί ατοµικά;
Λύση
Ας υποθέσουµε ότι η Test&Set() δεν εκτελείται ατοµικά. Θα παρουσιάσουµε µια
εκτέλεση δύο διεργασιών (έστω ότι οι διεργασίες ονοµάζονται 0 και 1), στην οποία και οι
δύο διεργασίες βρίσκονται την ίδια χρονική στιγµή στο κρίσιµο τµήµα τους. Στην πράξη,
κάθε µια από τις διεργασίες εκτελεί τον κώδικα που φαίνεται στο Σχήµα 21 (και µπορεί
να διακόπτεται σε οποιοδήποτε σηµείο της εκτέλεσης της).
∆ιεργασία 0 ∆ιεργασία 1
repeat repeat
begin begin
tmp0 = lock; tmp1 = lock;
lock = TRUE; lock = TRUE;
while (tmp0 == TRUE) do while (tmp1 == TRUE) do
tmp0 = Test&Set(lock); tmp1 = Test&Set(lock);
<κρίσιµο τµήµα>; <κρίσιµο τµήµα>;
lock = FALSE; lock = FALSE;
<µη-κρίσιµο τµήµα>; <µη-κρίσιµο τµήµα>;
end end
forever; forever;

Σχήµα 21: Κώδικας που εκτελείται στην πράξη από δύο διεργασίες 0 και 1 όταν η Test&Set()
χρησιµοποιείται για επίτευξη αµοιβαίου αποκλεισµού.

Στο Σχήµα 21, έχει αποδοθεί διαφορετικό όνοµα στην τοπική µεταβλητή tmp κάθε
διεργασίας, προκειµένου να γίνει σαφές ότι η tmp δεν αποτελεί διαµοιραζόµενη
µεταβλητή. Αντίθετα, κάθε διεργασία χρησιµοποιεί το δικό της αντίγραφο της tmp. Η
εκτέλεση που παραβιάζει τη συνθήκη του αµοιβαίου αποκλεισµού παρουσιάζεται στο
Σχήµα 22.
∆ιεργασία 0 ∆ιεργασία 1
tmp0 = lock;

2η έκδοση 70
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

tmp1 = lock;
lock = 1;
Έλεγχος while /* είναι FALSE */
<κρίσιµο τµήµα>;
lock = 1;
Έλεγχος while /* είναι FALSE */
<κρίσιµο τµήµα>;

Σχήµα 22: Εκτέλεση που παραβιάζει τη συνθήκη αµοιβαίου αποκλεισµού αν η Test&Set() δεν εκτελείται
ατοµικά.

Η διεργασία 0 αποθηκεύει την αρχική τιµή του lock (δηλαδή την τιµή FALSE) στην
tmp0. Η διεργασία 0 διακόπτεται στο σηµείο αυτό και δροµολογείται η διεργασία 1.
Αφού υποθέσαµε ότι η Test&Set() δεν εκτελείται ατοµικά, η διεργασία 1 µπορεί να
ξεκινήσει και αυτή να εκτελεί την Test&Set() (προσέξτε ότι κάτι τέτοιο δεν ήταν
επιτρεπτό όταν ήταν εγγυηµένο πως η Test&Set() θα εκτελεστεί ατοµικά). Αποθηκεύει
και αυτή την τιµή FALSE στην µεταβλητή tmp1, αλλάζει την τιµή του lock σε FALSE
και εισέρχεται στο κρίσιµο τµήµα της. Στο σηµείο αυτό, η διεργασία 1 διακόπτεται και
συνεχίζει να εκτελείται η διεργασία 0. Η διεργασία 0 αλλάζει µε τη σειρά της το lock σε
TRUE, αποτιµά τη συνθήκη της while σε FALSE (αφού η τοπική µεταβλητή της tmp0
έχει την τιµή FALSE) και εισέρχεται και αυτή στο κρίσιµο τµήµα της. □

Άσκηση Αυτοαξιολόγησης 8 (Μέρος Θέµατος 2, Εργασία 4, Ακ. Έτος 2001-2002 )


Ας µην ξεχνιόµαστε! Τώρα που έχουµε µια λύση του προβλήµατος του αµοιβαίου
αποκλεισµού στα χέρια µας, πως µπορούµε να την χρησιµοποιήσουµε για να επιλύσουµε
τα προβλήµατα συγχρονισµού που περιγράφτηκαν στην Ενότητα 3.2;
Θα περιγράψουµε τις εξής δύο ρουτίνες:
boolean deposit(int amount), και
boolean withdraw(int amount),
για τις οποίες θα πρέπει να ισχύουν τα ακόλουθα:
Η deposit() καταθέτει το ποσό amount σε έναν κοινό τραπεζικό λογαριασµό, ενώ η
withdraw() πραγµατοποιεί ανάληψη του ποσού amount από τον λογαριασµό.
Και οι δύο διαδικασίες θα πρέπει να µπορούν να εκτελεστούν από πολλές διεργασίες
ταυτόχρονα, χωρίς να προκύπτουν προβλήµατα συγχρονισµού (π.χ., χωρίς να είναι
δυνατόν οι χρήστες να µπορούν να κλέψουν την τράπεζα ή το αντίστροφο), ενώ το
τελικό αποτέλεσµα θα πρέπει να είναι σωστό. ∆εν επιτρέπονται υποθέσεις σε ότι αφορά
την ταχύτητα ή το πλήθος των επεξεργαστών.
Η λύση θα πρέπει να επιτυγχάνει αµοιβαίο αποκλεισµό χρησιµοποιώντας την ατοµική
εντολή Test&Set() που παρέχεται από το υλικό.
Λύση
Οι µη-ατοµικές εκδόσεις των ρουτινών deposit() και withdraw() έχουν συζητηθεί στην
Ενότητα 3.2. Το κύριο σηµείο της άσκησης αυτής είναι να αποφασιστεί ποιο είναι το
κρίσιµο τµήµα κατά την εκτέλεση των deposit() και withdraw(), προκειµένου να το

2η έκδοση 71
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

περικλείσουµε µεταξύ του κώδικα εισόδου και του κώδικα εξόδου για να είµαστε
σίγουροι ότι θα εκτελεστεί ατοµικά.
Το κρίσιµο τµήµα στην deposit() είναι η ανάγνωση και η εγγραφή της κοινής µεταβλητής
balance. Ο σωστός κώδικας για την deposit() παρουσιάζεται στο Σχήµα 23.
shared int balance;
boolean deposit(int amount)
int tmp; /* τοπική µεταβλητή */
begin
tmp = Test&Set(lock);
while (tmp == TRUE) do tmp = Test&Set(lock);
balance = balance + amount;
print “Το νέο σας ποσό είναι”;
print balance;
lock = FALSE;
return TRUE;
end

Σχήµα 23: Ατοµική έκδοση της deposit().

Ας δούµε τώρα τι αλλαγές χρειάζεται η withdraw(). Κάθε προσπέλαση (ανάγνωση ή


εγγραφή) της διαµοιραζόµενης µεταβλητής balance πρέπει να είναι εγγυηµένο πως θα
εκτελεστεί ατοµικά. Ο σωστός κώδικας για την withdraw() παρουσιάζεται στο Σχήµα 24.
shared int balance;
boolean withdraw(int amount)
begin
tmp = Test&Set(lock);
while (tmp == TRUE) do tmp = Test&Set(lock);
if (amount > balance)
begin
print “∆εν επιτρέπεται ανάληψη τόσο µεγάλου ποσού!”;
lock = FALSE;
return FALSE;
end
else
begin
balance = balance – amount;
print “Το νέο σας ποσό είναι”;
print balance;
lock = FALSE;
return TRUE;
end
end

Σχήµα 24: Ατοµική έκδοση της withdraw().

Ο αναγνώστης θα πρέπει να επενδύσει αρκετό χρόνο για να κατανοήσει σε βάθος γιατί οι


κακές εκτελέσεις που περιγράφτηκαν στην Ενότητα 3.2 (η τράπεζα κλέβει τους πελάτες
της ή οι πελάτες κλέβουν την τράπεζα) δεν θα προκύψουν αν οι αναλήψεις και οι

2η έκδοση 72
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

καταθέσεις πραγµατοποιούνται χρησιµοποιώντας τις εκδόσεις των deposit() και


withdraw() που παρουσιάζονται στα Σχήµατα 23 και 24. □

Άσκηση Αυτοαξιολόγησης 9
Ικανοποιεί η λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που παρουσιάζεται στο
Σχήµα 13 κάποια από τις συνθήκες δικαιοσύνης;
Λύση
Θα εξετάσουµε πρώτα αν η λύση µπορεί να οδηγήσει σε παρατεταµένη στέρηση. Για να
αποδείξουµε ότι µια λύση µπορεί να οδηγήσει σε παρατεταµένη στέρηση θα πρέπει να
βρούµε σενάριο στο οποίο κάποιες διεργασίες εισέρχονται συνεχώς στο κρίσιµο τµήµα,
ενώ κάποια άλλη παρότι επιθυµεί να εισέλθει στο κρίσιµο τµήµα της θα παραµείνει για
πάντα µπλοκαρισµένη στον κώδικα εισόδου. Ας δοκιµάσουµε να “µαγειρέψουµε” ένα
τέτοιο σενάριο για την σχετικά απλή περίπτωση δύο διεργασιών. Το σενάριο
περιγράφεται στο Σχήµα 25.
∆ιεργασία 0 ∆ιεργασία 1
tmp = Test&Set(lock);
Έλεγχος συνθήκης while /* είναι FALSE */
tmp = Test&Set(lock);
Έλεγχος συνθήκης while /* είναι TRUE */
<κρίσιµο τµήµα>;
lock = FALSE;
<µη-κρίσιµο τµήµα>;
tmp = Test&Set(lock);
Έλεγχος συνθήκης while /* είναι FALSE */
Έλεγχος συνθήκης while /* είναι TRUE */
Έλεγχος συνθήκης while /* είναι TRUE */
Έλεγχος συνθήκης while /* είναι TRUE */
<κρίσιµο τµήµα>;
lock = FALSE;
<µη-κρίσιµο τµήµα>;
tmp = Test&Set(lock);
Έλεγχος συνθήκης while /* είναι FALSE */
Έλεγχος συνθήκης while /* είναι TRUE */
. Έλεγχος συνθήκης while /* είναι TRUE */
. .
. .
.

Σχήµα 25: Εκτέλεση που οδηγεί σε παρατεταµένη στέρηση για τη διεργασία 1 στη λύση που χρησιµοποιεί
την Test&Set().

Στην εκτέλεση του Σχήµατος 25 θεωρούµε ότι ξεκινά να εκτελείται πρώτη η διεργασία 0.
Εκτελεί την Test&Set() και εισέρχεται στο κρίσιµο τµήµα της. Η διεργασία 1
δροµολογείται και προσπαθεί αποτυχηµένα να εισέλθει στο κρίσιµο τµήµα της. Η
διεργασία 1 µπλοκάρεται στη while του κώδικα εισόδου. Όταν η διεργασία 0 εξέλθει
από το κρίσιµο τµήµα της, η τιµή της µεταβλητής lock αλλάζει σε FALSE. Πριν όµως
προλάβει να δροµολογηθεί η 1, η 0 προλαβαίνει και τελειώνει το µη-κρίσιµο τµήµα της,

2η έκδοση 73
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

καλεί εκ νέου την Test&Set() και αλλάζει και πάλι την τιµή της lock σε TRUE. H
διεργασία 1 δροµολογείται και πάλι, χωρίς ωστόσο να καταφέρει και αυτή τη φορά να
εισέλθει στο κρίσιµο τµήµα της. Το σενάριο αυτό επαναλαµβάνεται επ’ άπειρο. Η
διεργασία 0 θα εισέλθει στο κρίσιµο τµήµα της άπειρες φορές, αλλά η διεργασία 1 δεν θα
καταφέρει ποτέ να εκτελέσει το κρίσιµο τµήµα της. Η λύση του Σχήµατος 13 δεν είναι
εποµένως δίκαιη αφού οδηγεί σε παρατεταµένη στέρηση.
Αξίζει να παρατηρήσουµε ότι αν µια λύση ικανοποιεί τη συνθήκη της άνω φραγµένης
καθυστέρησης, τότε ικανοποιεί και τη συνθήκη της αποφυγής παρατεταµένης στέρησης.
Άρα, αν µια λύση δεν ικανοποιεί τη συνθήκη της αποφυγής παρατεταµένης στέρησης,
τότε δεν µπορεί να ικανοποιεί και τη συνθήκη της άνω φραγµένης καθυστέρησης. Αρκεί
εποµένως να αποδείξουµε ότι µια λύση οδηγεί σε παρατεταµένη στέρηση για να
συµπεράνουµε ότι καµία από τις δύο συνθήκες δικαιοσύνης δεν ικανοποιείται. □

Άσκηση Αυτοαξιολόγησης 10 (Άσκηση Αυτοαξιολόγησης 3.19 Τόµου Γ)


Θεωρείστε ότι το υλικό εγγυάται ότι η εντολή Fetch&Add(), που περιγράφεται στο
Σχήµα 26 εκτελείται ατοµικά. Λύστε το πρόβληµα του αµοιβαίου αποκλεισµού
χρησιµοποιώντας την Fetch&Add(). (Μόνο η ατοµική εκτέλεση της Fetch&AddA() και
όχι της Test&Set() είναι εγγυηµένη από το υλικό.)
int Fetch&Add(int a, int b)
int tmp; /* τοπική µεταβλητή */
begin
tmp = a;
a = a + b;
return(tmp);
end

Σχήµα 26: Η εντολή Fetch&Add().

Ικανοποιεί η λύση που παρουσιάσατε κάποια από τις συνθήκες δικαιοσύνης;


Υπόδειξη: Χρησιµοποιείστε µια διαµοιραζόµενη µεταβλητή lock ως πρώτη παράµετρο στην Fetch&Add()
και µια τοπική µεταβλητή στην οποία θα αποθηκεύετε πάντα µια σταθερή τιµή (π.χ., την τιµή 1) πριν
καλέσετε την Fetch&Add(), ως δεύτερη παράµετρο.

Άσκηση Αυτοαξιολόγησης 11
Θεωρείστε ότι το υλικό εγγυάται ότι η εντολή Swap(), που περιγράφεται στο Σχήµα 27
εκτελείται ατοµικά. Λύστε το πρόβληµα του αµοιβαίου αποκλεισµού χρησιµοποιώντας
τη Swap().
int Swap(int a, int b)
int tmp; /* τοπική µεταβλητή */
begin
tmp = a;
a = b;
b = tmp;
return(tmp);

2η έκδοση 74
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

end

Σχήµα 27: Η εντολή Swap().

Ικανοποιεί η λύση που παρουσιάσατε κάποια από τις συνθήκες δικαιοσύνης;


Υπόδειξη: Χρησιµοποιείστε µια διαµοιραζόµενη µεταβλητή lock ως πρώτη παράµετρο στη Swap() και µια
τοπική µεταβλητή στην οποία θα αποθηκεύετε πάντα µια σταθερή τιµή πριν καλέσετε τη Swap(), ως
δεύτερη παράµετρο.

Άσκηση Αυτοαξιολόγησης 12
Θεωρείστε ότι το υλικό εγγυάται ότι η εντολή RMW() (Read-Modify-Write()), που
περιγράφεται στο Σχήµα 28 εκτελείται ατοµικά. Λύστε το πρόβληµα του αµοιβαίου
αποκλεισµού χρησιµοποιώντας τη RMW().
int RMW(int a, function f)
int tmp; /* τοπική µεταβλητή */
begin
tmp = a;
a = f(a);
return(tmp);
end

Σχήµα 28: Η εντολή RMW().

Η RMW() παίρνει δύο παραµέτρους, µια µεταβλητή a και µια συνάρτηση f(). Αλλάζει
την τιµή της a σύµφωνα µε την f (δηλαδή εφαρµόζει την f στην a και αποθηκεύει το
αποτέλεσµα και πάλι στην a), ενώ επιστρέφει την αρχική τιµή της a.
Ικανοποιεί η λύση που παρουσιάσατε κάποια από τις συνθήκες δικαιοσύνης;
Υπόδειξη: Επιλέξτε την κατάλληλη συνάρτηση f().

Άσκηση Αυτοαξιολόγησης 13 (Μέρος Θέµατος 2, Εργασία 4, Ακ. Έτος 2001-2002)


Περιγράψτε τις ρουτίνες:
boolean deposit(int amount), και
boolean withdraw(int amount),
που περιγράφτηκαν στην Άσκηση Αυτοαξιολόγησης 8. Η λύση σας θα πρέπει να
επιτυγχάνει αµοιβαίο αποκλεισµό χρησιµοποιώντας:
1. Την ατοµική εντολή Fetch&Add() που παρέχεται από το υλικό,
2. Την ατοµική εντολή Swap() που παρέχεται από το υλικό.
3. Την ατοµική εντολή RMW() που παρέχεται από το υλικό.

2η έκδοση 75
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

3.5 Λύσεις µε χρήση Λογισµικού µόνο


Στην ενότητα αυτή θα συζητηθούν λύσεις στο πρόβληµα του αµοιβαίου αποκλεισµού,
χωρίς να χρησιµοποιείται η υπόθεση ότι το υλικό υποστηρίζει την ατοµική εκτέλεση
σύνθετων εντολών (όπως π.χ., η Test&Set()).

3.5.1 Πρώτες Προσπάθειες προς την λύση


Η ανεύρεση λύσης στο πρόβληµα του αµοιβαίου αποκλεισµού, χωρίς τη βοήθεια του
υλικού, είναι επίπονη εργασία. Για να γίνει αυτό κατανοητό, καθώς και για να οξύνουµε
τη διαίσθηση του αναγνώστη, θα παρουσιάσουµε στη συνέχεια µερικές προσπάθειες
επίλυσης του προβλήµατος, όχι απαραίτητα σωστές, και θα συζητήσουµε τα προβλήµατα
που ενδεχόµενα παρουσιάζει η κάθε µια.
Ας θυµηθούµε πως η εργασία που έχουµε να κάνουµε είναι να σχεδιάσουµε κώδικα
εισόδου και κώδικα εξόδου που θα ικανοποιούν τις συνθήκες του αµοιβαίου
αποκλεισµού και της προόδου. Η πρώτη προτεινόµενη λύση στο πρόβληµα του
αµοιβαίου αποκλεισµού για δύο διεργασίες φαίνεται στο Σχήµα 29, όπου το µπλοκ
εντολών κόκκινου χρώµατος αποτελεί τον κώδικα εισόδου και εκείνο πράσινου
χρώµατος αποτελεί τον κώδικα εξόδου.
Κοινές Μεταβλητές
shared boolean in; /* αρχικά, FALSE */
Κώδικας ∆ιεργασίας 0 Κώδικας ∆ιεργασίας 1
repeat repeat
begin begin
while (in == TRUE) do noop; while (in == TRUE) do noop;
in = TRUE; in = TRUE;
<κρίσιµο τµήµα>; <κρίσιµο τµήµα>;
in = FALSE; in = FALSE;
<µη-κρίσιµο τµήµα>; <µη-κρίσιµο τµήµα>;
end end
forever; forever;

Σχήµα 29: Πρώτη προτεινόµενη λύση µε χρήση λογισµικού στο πρόβληµα του αµοιβαίου αποκλεισµού.

Κάθε διεργασία εξετάζει την τιµή της κοινής µεταβλητής in, η οποία υποδηλώνει αν
κάποια διεργασία επιθυµεί να εισέλθει στο κρίσιµο τµήµα της. Αν η τιµή της in είναι
FALSE, η διεργασία την αλλάζει σε TRUE και εισέρχεται στο κρίσιµο τµήµα της. Αν
ωστόσο η τιµή της in είναι TRUE, η διεργασία περιµένει στη while. Ακριβέστερα, µέχρι
η τιµή της in να γίνει FALSE, η διεργασία εκτελεί επαναληπτικά την εντολή noop που
δεν κάνει καµία χρήσιµη ενέργεια (έχει δηλαδή µπλοκάρει στη while).
Είναι σωστή η παραπάνω λύση; Μπορούµε να βρούµε ένα κακό σενάριο στο οποίο και οι
δύο διεργασίες βρίσκονται ταυτόχρονα στο κρίσιµο τµήµα τους; Θα ήταν καλό αν ο
αναγνώστης επενδύσει λίγο χρόνο στην ανεύρεση τέτοιου σεναρίου, πριν διαβάσει το
σενάριο που περιγράφεται στη συνέχεια.

2η έκδοση 76
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Στο κακό σενάριο, η διεργασία 0 ελέγχει τη συνθήκη της while και βρίσκει την τιµή της
in να είναι FALSE. Αποφασίζει λοιπόν πως η διεργασία 1 δεν προτίθεται να εισέλθει στο
κρίσιµο τµήµα της και άρα πως αυτή µπορεί να αλλάξει την τιµή της in σε TRUE και να
εισέλθει στο κρίσιµο τµήµα της. Πριν προλάβει όµως να κάνει οτιδήποτε, διακόπτεται
και δροµολογείται η 1. Η 1 ελέγχει τη συνθήκη της while, βρίσκει και αυτή την τιµή της
in να είναι FALSE, συµπεραίνει ότι η 0 δεν προτίθεται να εισέλθει στο κρίσιµο τµήµα
της και εποµένως ότι µπορεί να αλλάξει την τιµή της in σε TRUE και να εισέλθει στο
κρίσιµο τµήµα της. Η συνέχεια είναι εύκολο να προβλεφθεί. Και οι δύο διεργασίες
θέτουν την τιµή της in σε TRUE και εισέρχονται στο κρίσιµο τµήµα τους. Το παραπάνω
σενάριο περιγράφεται πιο αναλυτικά στο Σχήµα 30.
∆ιεργασία Α ∆ιεργασία Β
Έλεγχος συνθήκης while; /* είναι FALSE */
Έλεγχος συνθήκης while; /*είναι FALSE */
free = TRUE;
<κρίσιµο τµήµα>;
free = TRUE;
<κρίσιµο τµήµα>;

Σχήµα 30: Σενάριο που καταστρατηγεί τη συνθήκη του αµοιβαίου αποκλεισµού για την 1η προτεινόµενη
λύση.

Εφόσον υπάρχει σενάριο που µπορεί να οδηγήσει τις δύο διεργασίες να εκτελούν το
κρίσιµο τµήµα την ίδια χρονική στιγµή, η συνθήκη του αµοιβαίου αποκλεισµού δεν
ισχύει για την προτεινόµενη λύση του Σχήµατος 29 και άρα η λύση αυτή δεν είναι
σωστή.
Αξίζει να αναρωτηθούµε ποιο είναι το πρόβληµα στη λύση αυτή και να προσπαθήσουµε
να το διορθώσουµε. Το πρόβληµα φαίνεται να είναι πως κάθε διεργασία πρώτα ελέγχει
αν η in είναι FALSE και µετά αλλάζει την τιµή της in σε TRUE. Με άλλα λόγια, κάθε
διεργασία πρώτα ελέγχει αν προτίθεται η άλλη διεργασία να µπει στο κρίσιµο τµήµα της
(έλεγχος συνθήκης while) και στη συνέχεια εκφράζει τη δική της πρόθεση να µπει στο
κρίσιµο τµήµα (αλλάζοντας την τιµή της in σε TRUE). Ως δεύτερη προτεινόµενη λύση,
ας δοκιµάσουµε εποµένως να κάνουµε ακριβώς το αντίθετο. Ο κώδικας των δύο
διεργασιών φαίνεται στο Σχήµα 31.
Κοινές µεταβλητές
shared boolean flag[2]; /* αρχικά, FALSE */
Κώδικας ∆ιεργασίας 0 Κώδικας ∆ιεργασίας 1
repeat repeat
begin begin
flag[0] = TRUE; flag[1] = TRUE;
while (flag[1] == TRUE) do noop; while (flag[0] == TRUE) do noop;
<κρίσιµο τµήµα>; <κρίσιµο τµήµα>;
flag[0] = FALSE; flag[1] = FALSE;
<µη-κρίσιµο τµήµα>; <µη-κρίσιµο τµήµα>;
end end

2η έκδοση 77
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

forever; forever;

Σχήµα 31: ∆εύτερη προτεινόµενη λύση µε χρήση λογισµικού στο πρόβληµα του αµοιβαίου αποκλεισµού.

Στη λύση του Σχήµατος 31, χρησιµοποιούνται δύο κοινές µεταβλητές, µία για κάθε
διεργασία. Η µεταβλητή flag[i] χρησιµοποιείται από τη διεργασία i για να δηλώσει την
πρόθεση της να εισέλθει στο κρίσιµο τµήµα. Αρχικά, η διεργασία i θέτει την flag[i] στην
τιµή TRUE για να υποδηλώσει ότι θέλει να εισέλθει στο κρίσιµο τµήµα της. Στη
συνέχεια ελέγχει µήπως η άλλη διεργασία (δηλαδή, η διεργασία 1-i) προτίθεται να
εισέλθει στο κρίσιµο τµήµα της. Αν αυτό δεν συµβαίνει, τότε η i εισέρχεται στο κρίσιµο
τµήµα. ∆ιαφορετικά περιµένει µέχρι η διεργασία 1-i να αλλάξει την τιµή της flag
µεταβλητή της σε FALSE.
Είναι σωστή η παραπάνω λύση; Μήπως υπάρχει σενάριο στο οποίο και οι δύο διεργασίες
ενδιαφέρονται να εισέλθουν στο κρίσιµο τµήµα αλλά καµία τελικά δεν τα καταφέρνει
γιατί και οι δύο εκτελούν επαναληπτικά επ’ άπειρο την noop της while; Θα ήταν καλό αν
ο αναγνώστης προσπαθήσει λίγο πριν διαβάσει το κακό σενάριο που περιγράφεται στη
συνέχεια.
∆ιεργασία 0 ∆ιεργασία 1
flag[0] = ΤRUE;
flag[1] = TRUE;

Έλεγχος συνθήκης while; /* είναι TRUE */


Έλεγχος συνθήκης while; /*είναι TRUE */
Έλεγχος συνθήκης while; /* είναι TRUE */
Έλεγχος συνθήκης while; /* είναι TRUE */
Έλεγχος συνθήκης while; /* είναι TRUE */
. Έλεγχος συνθήκης while; /* είναι TRUE */
. Έλεγχος συνθήκης while; /* είναι TRUE */
. .
.
/* Η συνθήκη της while δεν θα είναι ποτέ .
FALSE και η διεργασία 0 θα παραµείνει
µπλοκαρισµένη στη while για πάντα */ /* Η συνθήκη της while δεν θα είναι ποτέ
FALSE και η διεργασία 1 θα παραµείνει
µπλοκαρισµένη στη while για πάντα */

Σχήµα 32: Σενάριο που καταστρατηγεί τη συνθήκη της προόδου για τη 2η προτεινόµενη λύση.

Αφού εκτελεστούν οι λειτουργίες που περιγράφονται στο Σχήµα 32, οι δύο διεργασίες θα
παραµείνουν µπλοκαρισµένες στην while και καµία δεν θα εισέλθει τελικά στο κρίσιµο
τµήµα της. Το σύστηµα βρίσκεται σε αδιέξοδο αφού η διεργασία 0 περιµένει από τη
διεργασία 1 να αλλάξει την τιµή της µεταβλητής flag[1] σε FALSE, ενώ αντίστοιχα η
διεργασία 1 περιµένει από τη διεργασία 0 να αλλάξει την τιµή της flag[0] σε FALSE.
Άρα και οι δύο διεργασίες θα περιµένουν για πάντα. Εποµένως, το σύστηµα δεν
προοδεύει (καταστρατηγείται η συνθήκη της προόδου), το οποίο σηµαίνει ότι και αυτή η
λύση δεν είναι σωστή.
Ας δούµε τώρα µια ακόµη προτεινόµενη λύση που περιγράφεται στο Σχήµα 33.

2η έκδοση 78
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Κοινές Μεταβλητές
shared int turn; /* αρχικά, 0 ή 1, δεν παίζει ρόλο */
Κώδικας ∆ιεργασίας 0 Κώδικας ∆ιεργασίας 1
repeat repeat
begin begin
while (turn == 1) do noop; while (turn == 0) do noop;
<κρίσιµο τµήµα>; <κρίσιµο τµήµα>;
turn = 1; turn = 0;
<µη-κρίσιµο τµήµα>; <µη-κρίσιµο τµήµα>;
end end
forever; forever;

Σχήµα 33: Τρίτη προτεινόµενη λύση µε χρήση λογισµικού (λύση της αυστηρής εναλλαγής) στο πρόβληµα
του αµοιβαίου αποκλεισµού.

Ας µελετήσουµε αν η παραπάνω λύση είναι σωστή. Η συνθήκη του αµοιβαίου


αποκλεισµού ικανοποιείται. Κάθε χρονική στιγµή, η κοινή µεταβλητή turn έχει τιµή είτε
0 ή 1 και έτσι µία µόνο από τις διεργασίες µπορεί να εισέλθει στο κρίσιµο τµήµα της,
ενώ η άλλη θα µπλοκάρει στη while του κώδικα εισόδου. Έστω π.χ., ότι η διεργασία 1
είναι αυτή που εισέρχεται πρώτη στο κρίσιµο τµήµα της. Όσο η 1 βρίσκεται στο κρίσιµο
τµήµα της, η turn θα έχει την τιµή 1. Εποµένως, αν η διεργασία 0 θελήσει να εισέλθει στο
κρίσιµο τµήµα της, θα µπλοκάρει στη while του κώδικα εισόδου. Όταν η 1 εξέλθει από
το κρίσιµο τµήµα της, θα αλλάξει την turn σε 0 και τότε η 0 θα µπορέσει να εισέλθει στο
κρίσιµο τµήµα της. Παρατηρήστε ότι η 1 δεν θα µπορέσει να µπει στο κρίσιµο τµήµα της
για δεύτερη φορά, αν η 0 δεν αλλάξει την τιµή της turn σε 1. H λύση του Σχήµατος 33
προϋποθέτει την αυστηρή εναλλαγή των διεργασιών κατά την εισαγωγή τους στα
κρίσιµα τµήµατά τους.
Το ερώτηµα τώρα είναι αν ικανοποιείται η συνθήκη της προόδου. ∆υστυχώς, υπάρχει
σενάριο στο οποίο η µια διεργασία ενδιαφέρεται να εισέλθει στο κρίσιµο τµήµα της αλλά
δεν τα καταφέρνει, παρότι η άλλη διεργασία εκτελεί το µη-κρίσιµο τµήµα της.
Συνίσταται στον αναγνώστη να επενδύσει λίγο χρόνο στην ανακάλυψη του σεναρίου
αυτού πριν συνεχίσει. Το σενάριο περιγράφεται στο Σχήµα 34 (όπου έχουµε υποθέσει ότι
η αρχική τιµή της turn είναι 1).
Κώδικας ∆ιεργασίας 0 Κώδικας ∆ιεργασίας 1
Έλεγχος συνθήκης while; /* είναι FALSE */
<κρίσιµο τµήµα>;
turn = 0;
µέρος µη-κρίσιµου τµήµατος;
Έλεγχος συνθήκης while; /* είναι FALSE */
<κρίσιµο τµήµα>;
turn = 1;
<µη-κρίσιµο τµήµα>;
Έλεγχος συνθήκης while; /* είναι TRUE */
Έλεγχος συνθήκης while; /* είναι TRUE */
µέρος µη-κρίσιµου τµήµατος;
Έλεγχος συνθήκης while; /* είναι TRUE */

2η έκδοση 79
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Έλεγχος συνθήκης while; /* είναι TRUE */


Έλεγχος συνθήκης while; /* είναι TRUE */
µέρος µη-κρίσιµου τµήµατος;
Έλεγχος συνθήκης while; /* είναι TRUE */
. .
. .
. .

/* παρότι η 1 είναι στο µη-κρίσιµο τµήµα της, η


0 δεν µπορεί να εισέλθει (πάλι) στο κρίσιµο
τµήµα της */

Σχήµα 34: Σενάριο που καταστρατηγεί τη συνθήκη της προόδου για την 3η προτεινόµενη λύση.

Στο σενάριο του Σχήµατος 34, η διεργασία 0 έχει ένα µεγάλο µη-κρίσιµο τµήµα, ενώ
αντίθετα το µη-κρίσιµο τµήµα της 1 είναι πολύ µικρό. Η διεργασία 1 ξεκινά πρώτη.
Εισέρχεται και εξέρχεται από το κρίσιµο τµήµα της. Στη συνέχεια, η διεργασία 0
εισέρχεται και εξέρχεται από το κρίσιµο τµήµα της. Είναι και πάλι η σειρά της 1 να
εισέλθει και να εξέλθει από το κρίσιµο τµήµα της. Όµως, η 1 δεν ενδιαφέρεται άµεσα να
ξανα-εισέλθει στο κρίσιµο τµήµα της γιατί εκτελεί ένα πολύ µεγάλο µη-κρίσιµο τµήµα.
Η 0 που ενδιαφέρεται να ξανα-εισέλθει, πρέπει να περιµένει να εισέλθει και πάλι η 1 πριν
ξαναέρθει η σειρά της. Η 1 παρότι εκτελεί το µη-κρίσιµο τµήµα της αποτρέπει την
είσοδο της 0 στο κρίσιµο τµήµα της, το οποίο καταστρατηγεί τη συνθήκη της προόδου.
Εποµένως, και η λύση αυτή δεν είναι σωστή.

Άσκηση Αυτοαξιολόγησης 14
Σχολιάστε την ορθότητα της λύσης του προβλήµατος του αµοιβαίου αποκλεισµού για
δύο διεργασίες που περιγράφεται στο Σχήµα 35.
Κοινές Μεταβλητές
shared boolean flag[2]; /* αρχικά, TRUE */
Κώδικας ∆ιεργασίας 0 Κώδικας ∆ιεργασίας 1
repeat repeat
begin begin
while (flag[1] == TRUE) do noop; while (flag[0] == TRUE) do noop;
flag[0] = TRUE; flag[1] = TRUE;
<κρίσιµο τµήµα>; <κρίσιµο τµήµα>;
flag[0] = FALSE; flag[1] = FALSE;
<µη-κρίσιµο τµήµα>; <µη-κρίσιµο τµήµα>;
end end
forever; forever;

Σχήµα 35: Τέταρτη προτεινόµενη λύση µε χρήση λογισµικού στο πρόβληµα του αµοιβαίου αποκλεισµού
για δύο διεργασίες.

Άσκηση Αυτοαξιολόγησης 15

2η έκδοση 80
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Η Ελένη και ο ∆ήµος είναι γείτονες και µοιράζονται µια αυλή. Η Ελένη έχει µια γάτα,
ενώ ο ∆ήµος έχει ένα σκύλο. Τόσο στη γάτα όσο και στο σκύλο αρέσει να παίζουν στην
αυλή. Ωστόσο, όσες φορές έχει τύχει τα δύο κατοικίδια να συναντηθούν στην αυλή, έχει
γίνει µεγάλος καβγάς. Έτσι, η Ελένη και ο ∆ήµος αποφάσισαν πως δεν πρέπει να
αφήνουν τα κατοικίδια να βρίσκονται την ίδια ώρα στην αυλή. Άρα, θα πρέπει µε κάποιο
τρόπο να συντονίζονται πριν αφήσουν τα κατοικίδια τους να βγαίνουν στην αυλή.
Το ερώτηµα είναι πως θα το πετύχουν αυτό; Η αυλή είναι µεγάλη και δεν είναι δυνατόν
για την Ελένη να ελέγξει αν ο σκύλος του ∆ήµου είναι στην αυλή ρίχνοντας µια µατιά
από το παράθυρο του σπιτιού της. Θα µπορούσε βέβαια να πεταχτεί στο σπίτι του ∆ήµου
και να τον ρωτήσει, αλλά αυτό δεν είναι πολύ βολικό ιδιαίτερα τις πολύ βροχερές µέρες.
Θα µπορούσε επίσης να ανοίξει το παράθυρο και να του φωνάξει, αλλά ο ∆ήµος µπορεί
να µην ακούσει. Τέλος, θα µπορούσε να τον πάρει τηλέφωνο, αλλά ούτε και αυτή η λύση
είναι εγγυηµένο πως θα είναι πάντοτε εξυπηρετική, αφού µπορεί ο ∆ήµος να µην
βρίσκεται στο σπίτι, ή να µην ακούει το χτύπο του τηλεφώνου επειδή βρίσκεται στο
µπάνιο, κλπ.
Αφού ξοδεύουν µερικές ώρες να σκέπτονται ποιος είναι ο πιο σωστός τρόπος,
αποφασίζουν να δοκιµάσουν τον ακόλουθο αλγόριθµο. Καθένας τοποθετεί στο σπίτι του
ένα κοντάρι, όπου µπορεί να ανεβάζει µια σηµαία. Το κοντάρι κάθε ενός έχει
τοποθετηθεί σε κατάλληλο σηµείο του σπιτιού του ώστε να µπορεί να το βλέπει ο άλλος
από το δικό του σπίτι. Κάθε φορά που η Ελένη θέλει να αφήσει τη γάτα της στην αυλή
κάνει τα εξής:
1. Ρίχνει µια µατιά στο κοντάρι του ∆ήµου.
2. Αν δεν υπάρχει σηµαία:
i. Τοποθετεί σηµαία στο δικό της κοντάρι.
ii. Αφήνει τη γάτα της στον κήπο.
3. Αν υπάρχει σηµαία περιµένει λίγο και ξανα-ρίχνει µια µατιά στο κοντάρι του ∆ήµου
(δηλαδή πηγαίνει στο βήµα 1).
4. Όταν η γάτα επιστρέψει κατεβάζει τη σηµαία της από το κοντάρι.
Κάθε φορά που ο ∆ήµος θέλει να αφήσει το σκύλο του στην αυλή κάνει αντίστοιχες
ενέργειες:
1. Ρίχνει µια µατιά στο κοντάρι της Ελένης.
2. Αν δεν υπάρχει σηµαία:
i. Τοποθετεί σηµαία στο δικό του κοντάρι.
ii. Αφήνει τον σκύλο του στον κήπο.
3. Αν υπάρχει σηµαία περιµένει λίγο και ξανα-ρίχνει µια µατιά στο κοντάρι της Ελένης
(δηλαδή πηγαίνει στο βήµα 1).
4. Όταν ο σκύλος επιστρέψει κατεβάζει τη σηµαία του από το κοντάρι.
Επιτυγχάνει η λύση που ακολουθήθηκε από την Ελένη και τον ∆ήµο αµοιβαίο
αποκλεισµό ή θα έχουµε νέα µάχη των κατοικίδιων στην αυλή; Σας θυµίζει κάτι η
παραπάνω προτεινόµενη λύση;

2η έκδοση 81
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Άσκηση Αυτοαξιολόγησης 16
Η Ελένη και ο ∆ήµος συνεδριάζουν και πάλι. Αυτή τη φορά αποφασίζουν τον ακόλουθο
αλγόριθµο για το γνωστό τους πρόβληµα:
Κάθε φορά που η Ελένη θέλει να αφήσει τη γάτα της στην αυλή κάνει τα εξής:
5. Τοποθετεί τη σηµαία στο κοντάρι της.
6. Εξετάζει αν το κοντάρι του ∆ήµου έχει σηµαία και αν ναι περιµένει µέχρι ο ∆ήµος να
κατεβάσει τη σηµαία από το κοντάρι του.
7. Αν όχι, αφήνει τη γάτα της στην αυλή.
8. Όταν η γάτα επιστρέψει κατεβάζει τη σηµαία από το κοντάρι της.
Αντίστοιχες ενέργειες γίνονται και από το ∆ήµο όταν θέλει να αφήσει το σκύλο του στην
αυλή:
1. Τοποθετεί τη σηµαία στο κοντάρι του.
2. Εξετάζει αν το κοντάρι της Ελένης έχει σηµαία και αν ναι περιµένει µέχρι η Ελένη να
κατεβάσει τη σηµαία από το κοντάρι της.
3. Αν όχι, αφήνει το σκύλο του στην αυλή.
4. Όταν ο σκύλος επιστρέψει κατεβάζει τη σηµαία από το κοντάρι του.
Επιλύει η νέα λύση το πρόβληµα του αµοιβαίου αποκλεισµού;

Άσκηση Αυτοαξιολόγησης 17
Η Ελένη και ο ∆ήµος προσπαθούν για άλλη µια φορά (µήπως θα πρέπει να ανοίξουν ένα
βιβλίο πληροφορικής;). Τη φορά αυτή αποφασίζουν να χρησιµοποιήσουν µία µόνο
σηµαία ως εξής. ∆ένουν ένα σκοινί που ενώνει τα δύο κοντάρια. Το σκοινί µπορεί να
κινείται προς µια µόνο κατεύθυνση µετακινώντας τη σηµαία από το ένα κοντάρι στο
άλλο. Κάθε φορά που η σηµαία φθάνει στο κοντάρι της Ελένης, η Ελένη θα πρέπει να τη
λύσει και να την ξαναδέσει στη σωστή πλευρά του σκοινιού για να µπορέσει να ξανα-
ταξιδέψει πίσω στο ∆ήµο. Το ίδιο θα πρέπει να κάνει και ο ∆ήµος κάθε φορά που παίρνει
τη σηµαία, ώστε να µπορεί η σηµαία να επιστραφεί στην Ελένη.
Κάθε φορά που η Ελένη θέλει να αφήσει τη γάτα της στην αυλή κάνει τα εξής:
5. Αν η σηµαία είναι στο κοντάρι της, αφήνει τη γάτα της στην αυλή.
6. Στην αντίθετη περίπτωση περιµένει τον ∆ήµο να της στείλει τη σηµαία.
7. Όταν η γάτα επιστρέψει από την αυλή, λύνει τη σηµαία από το κοντάρι της, τη δένει
ξανά στη σωστή πλευρά του σκοινιού και στέλνει τη σηµαία στο ∆ήµο (για να
µπορέσει να αφήσει και αυτός το σκύλο του στην αυλή).
Με παρόµοιο τρόπο όταν ο ∆ήµος θέλει να αφήσει το σκύλο του στην αυλή κάνει τα
εξής:
1. Αν η σηµαία είναι στο κοντάρι του, αφήνει τον σκύλο του στην αυλή.
2. Στην αντίθετη περίπτωση περιµένει την Ελένη να του στείλει τη σηµαία.

2η έκδοση 82
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

3. Όταν ο σκύλος επιστρέψει από την αυλή, λύνει τη σηµαία από το κοντάρι του, τη
δένει ξανά στη σωστή πλευρά του σκοινιού και στέλνει τη σηµαία στην Ελένη (για
να µπορέσει να αφήσει και αυτή τη γάτα της στην αυλή).
Θα έχουµε τώρα νέες µάχες των κατοικίδιων στην αυλή; Τι θα συµβεί αν η Ελένη (ή ο
∆ήµος) λείψουν σε ταξίδι για µερικούς µήνες;

3.5.2 Λύση του Peterson


Ενδεχόµενα, ο αναγνώστης έχει ήδη πειστεί πως το να βρεθεί µια λύση στο πρόβληµα
του αµοιβαίου αποκλεισµού δεν είναι πάρα πολύ εύκολη υπόθεση. Στη συνέχεια
παρουσιάζεται µια σωστή λύση του προβλήµατος για δύο διεργασίες που παρουσιάστηκε
από τον Peterson.
Κοινές Μεταβλητές
shared in turn; /* αρχικά 0 ή 1 */
shared boolean flag[2]; /* αρχικά FALSE */
∆ιεργασία 0 ∆ιεργασία 1
repeat repeat
begin begin
1. flag[0] = TRUE; 1. flag[1] = TRUE;
2. turn = 0; 2. turn = 1;
3. while (turn == 0 AND flag[1] == TRUE) do 3. while (turn == 1 AND flag[0] == TRUE) do
noop; noop;
4. <κρίσιµο τµήµα>; 4. <κρίσιµο τµήµα>;
5. flag[0] = FALSE; 5. flag[1] = FALSE;
6. <µη-κρίσιµο τµήµα>; 6. <µη-κρίσιµο τµήµα>;
end end
forever; forever;

Σχήµα 36: Η λύση του Peterson.

Στη λύση του Peterson χρησιµοποιείται τόσο µια κοινή µεταβλητή turn που υποδηλώνει
ποιος έχει σειρά να εισέλθει στο κρίσιµο τµήµα, όσο και από µια µεταβλητή flag για
κάθε µια από τις διεργασίες, προκειµένου αυτές να δηλώνουν την πρόθεσή τους να
εισέλθουν ή όχι στο κρίσιµο τµήµα. Κάθε διεργασία i, που επιθυµεί να εισέλθει στο
κρίσιµο τµήµα, δηλώνει αρχικά την πρόθεσή της αλλάζοντας τη µεταβλητή flag[i] σε
TRUE και τη µεταβλητή turn σε i. Η διεργασία στη συνέχεια γίνεται ευγενική. Ελέγχει
αν είναι η σειρά της να εισέλθει στο κρίσιµο τµήµα και αν επιθυµεί η άλλη να εισέλθει.
Αν και οι δύο αυτές συνθήκες είναι αληθείς, τότε η διεργασία παραχωρεί τη σειρά της
στην άλλη διεργασία και περιµένει µέχρι αυτή να εξέλθει από το κρίσιµο τµήµα της και
να αλλάξει τη µεταβλητή της flag σε FALSE.
Εξηγούµε στη συνέχεια γιατί ο αλγόριθµος του Peterson είναι σωστός. Ας υποθέσουµε
ότι µια διεργασία, π.χ., η 1, εκτελεί πρώτη τον κώδικα εισόδου και εισέρχεται στο
κρίσιµο τµήµα της. Αν η διεργασία 0 εκτελέσει τον κώδικα εισόδου ενόσω η 1 βρίσκεται
στο κρίσιµο τµήµα, θα βρει το turn == 0 και το flag[1] == TRUE και θα µπλοκάρει στη
while. Αντίστοιχες ενέργειες θα συµβούν αν η 0 εκτελέσει πρώτη τον κώδικα εισόδου.

2η έκδοση 83
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Ας δούµε τώρα τι θα συµβεί αν και οι δύο διεργασίες εκτελέσουν τον κώδικα εισόδου
σχεδόν ταυτόχρονα. Έστω ότι και οι δύο θέτουν την µεταβλητή τους flag σε TRUE. Το
σηµαντικό σηµείο είναι να αναρωτηθούµε ποια διεργασία θα εκτελέσει τελευταία τη
γραµµή 2 του κώδικα. Έστω ότι αυτή είναι η 0 (το σενάριο στην αντίθετη περίπτωση
είναι συµµετρικό). Τότε η 1 θα µπορέσει να εισέλθει στο κρίσιµο τµήµα της, ενώ οι δύο
συνθήκες της while για την 0 θα είναι TRUE και έτσι η 0 θα µπλοκάρει στη while µέχρι
η 1 να εξέλθει από το κρίσιµο τµήµα της (και να αλλάξει τη µεταβλητή flag[1]).
Φυσικά, η παραπάνω συζήτηση δεν αποτελεί απόδειξη. Ωστόσο, θα πρέπει διαισθητικά
να έχει γίνει κατανοητό γιατί η παραπάνω λύση ικανοποιεί τη συνθήκη του αµοιβαίου
αποκλεισµού. Αν ο αναγνώστης δεν έχει ακόµη βεβαιωθεί για αυτό, καλό θα ήταν να
επενδύσει περισσότερο χρόνο προσπαθώντας να βρει κάποιο κακό σενάριο και
κατανοώντας τους λόγους που κάτι τέτοιο αποτυγχάνει. Η διαδικασία αυτή συνήθως
βοηθά στην καλύτερη κατανόηση της ορθότητας ενός αλγορίθµου.
Ας δούµε τώρα γιατί ο αλγόριθµος του Peterson ικανοποιεί τη δεύτερη συνθήκη, τη
συνθήκη της προόδου. Καταρχήν δεν είναι ποτέ δυνατό και οι δύο διεργασίες να
µπλοκάρουν στη while. Αυτό οφείλεται στο ότι δεν είναι δυνατό η µεταβλητή turn να
έχει τιµή και 0 και 1 ταυτόχρονα. ∆εύτερον, αν η µια διεργασία (π.χ., η 0) δεν είναι στο
κρίσιµο τµήµα της δεν µπορεί να αποτρέψει την άλλη από το να εισέλθει στο κρίσιµο
τµήµα της (µία ή περισσότερες φορές), αφού η µεταβλητή της flag (στην περίπτωση µας,
η flag[0]) θα είναι FALSE και άρα η δεύτερη συνθήκη της while για την άλλη διεργασία
(στην περίπτωσή µας, τη διεργασία 1) δεν θα είναι αληθής.
Στη σηµείο αυτό ίσως ο αναγνώστης να αναρωτιέται το εξής ερώτηµα. «Καλά όλα αυτά,
αλλά πως ο κ. Peterson κατάφερε να σκεφτεί τη σωστή του λύση;». Προφανώς δεν είναι
εύκολο να δοθεί απάντηση στο ερώτηµα αυτό. Μπορούµε ωστόσο να αναρωτηθούµε
ποιος θα ήταν ένας καλός τρόπος να εργαστεί κάποιος προκειµένου να σχεδιάσει µια
σωστή λύση στο πρόβληµα δεδοµένων όλων των προβληµατικών λύσεων που
παρουσιάστηκαν στην προηγούµενη ενότητα. Για να απαντηθεί το ερώτηµα αυτό, ο
αναγνώστης θα πρέπει να επιστρέψει στις προτεινόµενες λύσεις της προηγούµενης
ενότητας και να σκεφτεί για κάθε µια ξεχωριστά ποια είναι τα προβλήµατα που
παρουσιάζει και πως θα µπορούσαν ενδεχόµενα να επιλυθούν. Η πρώτη λύση δεν
επιτυγχάνει αµοιβαίο αποκλεισµό αλλά δεν παρουσιάζει προβλήµατα προόδου. Οι δύο
τελευταίες λύσεις επιτυγχάνουν αµοιβαίο αποκλεισµό αλλά παρουσιάζουν προβλήµατα
προόδου. Μήπως θα µπορούσαν οι τεχνικές που χρησιµοποιούνται στις διαφορετικές
προτεινόµενες λύσεις να συνδυαστούν για να δώσουν µια σωστή λύση; Με µια δεύτερη
προσεκτική µελέτη της λύσης του Peterson γίνεται σαφές πως αυτή χρησιµοποιεί
πράγµατι ένα συνδυασµό των τεχνικών που παρουσιάστηκαν στην προηγούµενη
ενότητα. Η λύση του Peterson χρησιµοποιεί και την κοινή µεταβλητή turn αλλά και
µεταβλητές flag που επιτρέπουν στις διεργασίες να δηλώνουν την πρόθεση τους να
εκτελέσουν το κρίσιµο τµήµα τους. Παρατηρήστε ότι οι τιµές των µεταβλητών flag
παίζουν ένα σηµαντικό ρόλο στο να µην παρουσιάζει η λύση του Peterson προβλήµατα
προόδου. Χρησιµοποιείται όµως έξυπνα και η τεχνική του να υπάρχει κάποια µεταβλητή
που σχετίζεται µε τη σειρά µε την οποία θα εκτελεστούν οι δύο διεργασίες.
Ερωτήµατα όπως τα παραπάνω φαίνεται να απασχόλησαν τους επιστήµονες αρκετό
καιρό πριν τελικά σχεδιαστεί µια σωστή λύση. Φυσικά το να βρεθεί το πως οι
διαφορετικές τεχνικές θα συνδυαστούν σωστά δεν είναι εύκολο έργο. Αναµένουµε ότι οι

2η έκδοση 84
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

ασκήσεις αυτοαξιολόγησης της ενότητας αυτής θα πείσουν τον αναγνώστη για το


γεγονός αυτό.
Για ιστορικούς λόγους αξίζει να αναφέρουµε, ότι η πρώτη σωστή λύση στο πρόβληµα
του αµοιβαίου αποκλεισµού δόθηκε το 1965 από τον Ολλανδό µαθηµατικό T. Dekker. Ο
αλγόριθµός του δουλεύει σωστά για περισσότερες από δύο διεργασίες αλλά είναι αρκετά
πιο πολύπλοκος από τον αλγόριθµο του Peterson που ανακαλύφθηκε αργότερα και
δουλεύει σωστά µόνο για δύο διεργασίες. Ο αλγόριθµος του Dekker θα συζητηθεί στην
ενότητα «Λίγο πιο ∆ύσκολα».

Άσκηση Αυτοαξιολόγησης 18 (Μέρος Θέµατος 2, 4η Εργασία, Ακ. Έτος 2001-2002)


Ας µην ξεχνιόµαστε! Τώρα που έχουµε ακόµη µια λύση του προβλήµατος του αµοιβαίου
αποκλεισµού στα χέρια µας, περιγράψτε και πάλι τις δύο ρουτίνες boolean deposit(int
amount), και boolean withdraw(int amount), που περιγράφτηκαν στην Άσκηση
Αυτοαξιολόγησης 8. Η λύση σας θα πρέπει να επιτυγχάνει αµοιβαίο αποκλεισµό για δύο
διεργασίες χρησιµοποιώντας την λύση του Peterson.

Άσκηση Αυτοαξιολόγησης 19 (Μέρος Θέµατος 3, 4η Εργασία, Ακ. Έτος 2001-2002)


Μια ακόµη λύση στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες
προτείνεται στο Σχήµα 37.
Κοινές µεταβλητές
shared int turn; /* αρχικά 0 ή 1 */
shared boolean flag[2]; /* αρχικά TRUE ή FALSE */
Κώδικας για τη διεργασία i:
repeat
begin
flag[i] = TRUE;
turn = i;
while (turn==1-i AND flag[1-i]==true) noop;
<κρίσιµο τµήµα>;
flag[i] = FALSE;
turn = 1-i;
<µη-κρίσιµο τµήµα>;
end
forever;

Σχήµα 37: Πέµπτη προτεινόµενη λύση µε χρήση λογισµικού στο πρόβληµα του αµοιβαίου αποκλεισµού
για δύο διεργασίες.

Βρείτε σενάριο κατά το οποίο και οι δύο διεργασίες βρίσκονται στο κρίσιµο τµήµα τους.
Εξηγήστε τις οµοιότητες και τις διαφορές µε τη λύση του Peterson. Τι είναι αυτό που
εγγυάται ορθότητα στη λύση του Peterson, αλλά όχι στην παραπάνω λύση;

Άσκηση Αυτοαξιολόγησης 20 (Άσκηση Αυτοαξιολόγησης 3.7 Τόµου Γ & Θέµα 3, Β’


Τελική Εξέταση, Ιούνιος 2001)

2η έκδοση 85
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Σχολιάστε την ορθότητα της λύσης του προβλήµατος του αµοιβαίου αποκλεισµού για
δύο διεργασίες που περιγράφεται στο Σχήµα 38.
∆ιεργασία 0 ∆ιεργασία 1
shared in turn; /* αρχικά 0 ή 1 */
shared boolean flag[2]; /* αρχικά FALSE */
repeat repeat
begin begin
flag[0] = TRUE; flag[1] = TRUE;
while (turn != 0) do while (turn != 1) do
begin begin
while (flag[1] == TRUE) do noop; while (flag[0] == TRUE) do noop;
turn = 0; turn = 1;
end end
<κρίσιµο τµήµα>; <κρίσιµο τµήµα>;
flag[0] = FALSE; flag[1] = FALSE;
<µη-κρίσιµο τµήµα>; <µη-κρίσιµο τµήµα>;
end end
forever; forever;

Σχήµα 38: Έκτη προτεινόµενη λύση µε χρήση λογισµικού στο πρόβληµα του αµοιβαίου αποκλεισµού.

Λύση: Αν η πρώτη απάντηση που σκέφτεται ο αναγνώστης, όταν έχει να αντιµετωπίσει


τέτοιου είδους προβλήµατα, είναι «∆εν έχω ιδέα! Τι να ισχύει άραγε;» δεν πρέπει να
νιώθει άβολα, γιατί αυτή είναι µάλλον η φυσιολογική και πιο συνηθισµένη αντίδραση.
∆υστυχώς, δεν υπάρχουν σαφείς κανόνες (ή κάποια αλγοριθµική µέθοδος) που αν
ακολουθηθούν θα οδηγήσουν στη σωστή απάντηση. Η παρακάτω συζήτηση αποσκοπεί
στο να βοηθήσει τον αναγνώστη να µάθει να δουλεύει µε τέτοιου είδους ασκήσεις.
Ωστόσο, θα πρέπει να τονιστεί ότι η εύκολη επίλυση τέτοιου είδους ασκήσεων είναι
αποτέλεσµα εµπειρίας που αποκτάται µόνο µε τον χρόνο που επενδύεται στο πρόβληµα.
Όσος περισσότερος χρόνος ξοδεύεται για την κατανόηση των δύσκολων εννοιών του
κεφαλαίου αυτού και τον πειραµατισµό µε τους κώδικες που παρουσιάζονται, τόσο πιο
εύκολη θα γίνεται η επίλυση τέτοιου είδους ασκήσεων.
Όταν πρέπει να επιλύθει µια άσκηση αυτού του τύπου, η πρώτη αντιµετώπιση του
αναγνώστη θα πρέπει να είναι να γίνει καχύποπτος. Ξεκινάµε πάντα µε την υποψία ότι
αυτό που παρουσιάζεται ως λύση δεν ικανοποιεί κάποια από τις δύο συνθήκες και
προσπαθούµε να βρούµε ένα κακό σενάριο που θα καταστρατηγεί τουλάχιστον µία από
αυτές. Η εύρεση κακού σεναρίου είναι σχετικά απλή εργασία, όταν ο κώδικας εισόδου
δεν περιέχει περισσότερες από µια εντολές ανακύκλωσης (ας θυµηθούµε τα κακά
σενάρια για τις πρώτες προσπάθειες επίλυσης, καθώς και την επιχειρηµατολογία
ορθότητας του αλγορίθµου του Peterson). Τα πράγµατα ωστόσο δυσκολεύουν σε
ασκήσεις όπως αυτές, όπου ο κώδικας εισόδου είναι σχετικά πολύπλοκος, περιέχει
περισσότερες από µια εντολές ανακύκλωσης και κάποιες από αυτές είναι φωλιασµένες.
Προκειµένου να σχεδιάσουµε ένα κακό σενάριο θα πρέπει να µελετήσουµε προσεκτικά
ποιες συνθήκες θα πρέπει να µην επαληθεύονται για κάθε διεργασία, προκειµένου αυτή
να µην µπλοκάρει σε κάποια εντολή του κώδικα εισόδου και άρα να εισέλθει στο κρίσιµο
τµήµα της. Για παράδειγµα, προκειµένου η διεργασία 0 να εισέλθει στο κρίσιµο τµήµα

2η έκδοση 86
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

της θα πρέπει είτε να ισχύει (turn = 0), οπότε η διεργασία δεν εκτελεί καθόλου το µπλοκ
εντολών της εξωτερικής while του κώδικα εισόδου, ή να ισχύει (turn ==1) αλλά να ισχύει
επίσης (flag[1] != TRUE), ώστε η διεργασία να µην µπλοκάρει στην εσωτερική while.
Στη συνέχεια, θα πρέπει η εντολή “turn = 0;” να εκτελεστεί αµέσως πριν γίνει ο έλεγχος
της συνθήκης της εξωτερικής while, ώστε να γίνει αποτίµηση της συνθήκης αυτής σε
FALSE και η 0 να εισέλθει στο κρίσιµο τµήµα της.
Εντελώς αντίστοιχα, προκειµένου η διεργασία 1 να εισέλθει στο κρίσιµο τµήµα της θα
πρέπει είτε να ισχύει (turn = 1), οπότε η 1 δεν εκτελεί καθόλου το µπλοκ εντολών της
εξωτερικής while του κώδικα εισόδου, ή να ισχύει (turn ==0) αλλά να ισχύει επίσης
(flag[0] != TRUE), ώστε η 1 να µην µπλοκάρει στην εσωτερική while. Στη συνέχεια, θα
πρέπει η εντολή “turn = 1;” να εκτελεστεί αµέσως πριν γίνει ο έλεγχος της εξωτερικής
while, ώστε να γίνει αποτίµηση της συνθήκης της εξωτερικής while σε FALSE και η 1 να
εισέλθει στο κρίσιµο τµήµα της.
Τώρα που γνωρίζουµε τις συνθήκες εισόδου κάθε διεργασίας στο κρίσιµο τµήµα, ας
κάνουµε µια πρώτη προσπάθεια (όχι απαραίτητα επιτυχηµένη) να βρούµε σενάριο στο
οποίο καταστρατηγείται η συνθήκη του αµοιβαίου αποκλεισµού. Θεωρούµε ότι η turn
έχει αρχικά την τιµή 0 (στην αντίθετη περίπτωση το σενάριο είναι συµµετρικό). Ας
υποθέσουµε ότι ξεκινά πρώτα η 0. Για την 0, η συνθήκη της εξωτερικής while είναι
FALSE και άρα θα µπει απευθείας στο κρίσιµο τµήµα της. Ενόσω είναι τώρα στο
κρίσιµο τµήµα της, ξεκινά να εκτελείται η 1. Η συνθήκη της εξωτερικής while για την 1
είναι TRUE, οπότε εκτελείται το µπλοκ εντολών της. Η συνθήκη της εσωτερικής while
είναι επίσης TRUE (δυστυχώς, γιατί αν ήταν FALSE θα είχαµε βρει το κακό µας
σενάριο) και άρα η 1 θα µπλοκάρει στην εσωτερική while περιµένοντας την 0 να εξέλθει
από το κρίσιµο τµήµα της.
Η προσπάθεια µας, δυστυχώς, δεν οδήγησε σε κακό σενάριο. Οδήγησε όµως σε µια
χρήσιµη παρατήρηση, ότι αν ξεκινήσει πρώτη η διεργασία i για την οποία ισχύει turn ==
i, αυτή θα µπει απευθείας στο κρίσιµο τµήµα της αλλά η άλλη διεργασία δεν θα µπορέσει
να ξεφύγει από τους εξωτερικούς και τους εσωτερικούς ελέγχους των εντολών
ανακύκλωσης του κώδικα εισόδου της. Ας δοκιµάσουµε λοιπόν να ξεκινήσουµε πρώτη
τη διεργασία 1 για την οποία δεν ισχύει “turn == 1” και ας παρατηρήσουµε τι θα συµβεί
σε αυτή την περίπτωση. Η εξωτερική while αποτιµάται σε TRUE, αλλά η εσωτερική
while αποτιµάται σε FALSE. Από εδώ και στο εξής, η 1 είναι έτοιµη να αλλάξει το turn
σε 1, να αποτιµήσει την συνθήκη της εξωτερικής while σε FALSE και να εισέλθει στο
κρίσιµο τµήµα της. Αν της επιτρέψουµε να το κάνει αυτό και µετά ξεκινήσουµε την 0, η
0 θα έχει την ίδια τύχη που είχε η 1 στην αποτυχηµένη προσπάθεια της προηγούµενης
παραγράφου. Για αυτό ας υποθέσουµε ότι ο χρονοδροµολογητής διακόπτει την 1 πριν
προλάβει να αλλάξει την τιµή της turn σε 1. ∆ροµολογείται τώρα η 0. Η 0 αποτιµά την
συνθήκη της εξωτερικής while σε FALSE και εισέρχεται στο κρίσιµο τµήµα της. Αυτό
ήταν ακριβώς αυτό που επιζητούσαµε! Αν ο χρονοδροµολογητής διακόψει τώρα την 0
όσο εκτελεί το κρίσιµο τµήµα της και αφήσει να εκτελεστεί η 1, η 1 θα αλλάξει την τιµή
της turn σε 1, θα αποτιµήσει την συνθήκη της εξωτερικής while σε FALSE και θα µπει
και αυτή στο κρίσιµο τµήµα της! Ο σκοπός µας επιτεύχθηκε! Μόλις τελειώσαµε το
«µαγείρεµα» του σεναρίου που ψάχναµε, το οποίο παρουσιάζεται πιο αναλυτικά στο
Σχήµα 39.

2η έκδοση 87
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

∆ιεργασία 0 ∆ιεργασία 1
flag[1] = TRUE;
Έλεγχος συνθήκης εξωτερικής while;
/* είναι TRUE */
Έλεγχος συνθήκης εσωτερικής while;
/* είναι FALSE */
flag[0] = TRUE;
Έλεγχος συνθήκης εξωτερικής while;
/* είναι FALSE */
εκτέλεση µέρους του κρίσιµου τµήµατος;
turn = 1;
Έλεγχος συνθήκης εξωτερικής while;
/* είναι FALSE */
<κρίσιµο τµήµα>;

Σχήµα 39: Σενάριο που καταστρατηγεί τη συνθήκη του αµοιβαίου αποκλεισµού για την 5η προτεινόµενη
λύση.

Θα ήταν καλό αν ο αναγνώστης συγκρατήσει ότι οι διεργασίες συνήθως διακόπτονται


αφού έχουν κάνει τις αποτιµήσεις (σε FALSE) των συνθηκών που τις αποτρέπουν από το
να εισέλθουν στο κρίσιµο τµήµα τους, αλλά πριν αλλάξουν οποιεσδήποτε κοινές
µεταβλητές που θα επηρεάσουν το αν άλλες διεργασίες θα εισέλθουν στο κρίσιµο τµήµα
τους. Αυτό ακριβώς συνέβη και στο κακό σενάριο του Σχήµατος 39.
Επίσης, όταν ο κώδικας εισόδου περιέχει φωλιασµένες εντολές ανακυκλώσεις, πολλές
φορές το κακό σενάριο προκύπτει αν η µια διεργασία αποτιµήσει τη συνθήκη της
εξωτερικής ανακύκλωσης σε FALSE, ενώ η άλλη αποτιµήσει τη συνθήκη της
εσωτερικής ανακύκλωσης σε FALSE. Αυτό ωστόσο δεν είναι ο κανόνας, και άρα
κάποιες φορές µπορεί και να µην ισχύει. Συνήθως ωστόσο, όταν υπάρχει κακό σενάριο
αλλά δεν εφαρµόζεται η παραπάνω παρατήρηση για την εύρεση του, το σενάριο είναι
απλό και είναι σχετικά εύκολο να βρεθεί. □

Άσκηση Αυτοαξιολόγησης 21
Εξετάστε αν η προτεινόµενη λύση του Σχήµατος 40 είναι σωστή λύση στο πρόβληµα του
αµοιβαίου αποκλεισµού για δύο διεργασίες.
Κοινές Μεταβλητές
shared in turn; /* αρχικά 0 ή 1 */
shared boolean flag[2]; /* αρχικά FALSE */
∆ιεργασία 0 ∆ιεργασία 1
repeat repeat
begin begin
1. flag[0] = TRUE; 1. flag[1] = TRUE;
2. turn = 1; 2. turn = 0;
3. while (turn == 0 && flag[1] == TRUE) do 3. while (turn == 1 && flag[0] == TRUE) do
noop; noop;
4. <κρίσιµο τµήµα>; 4. <κρίσιµο τµήµα>;
5. flag[0] = FALSE; 5. flag[1] = FALSE;

2η έκδοση 88
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

6. <µη-κρίσιµο τµήµα>; 6. <µη-κρίσιµο τµήµα>;


end end
forever; forever;

Σχήµα 40: Ελαφρώς τροποποιηµένη έκδοση της λύσης του Peterson.

Σκιαγράφηση Λύσης: Εύκολα µπορεί να βρεθεί σενάριο που να αποδεικνύει ότι η λύση
του Σχήµατος 40 δεν είναι σωστή. Συνίσταται στον αναγνώστη να το επαληθεύσει.
Εδώ θα συζητήσουµε ωστόσο και το γιατί η λύση αυτή δεν είναι δίκαιη. Θα εξετάσουµε
πρώτα αν η λύση µπορεί να οδηγήσει σε παρατεταµένη στέρηση. Για να αποδείξουµε ότι
µια λύση µπορεί να οδηγήσει σε παρατεταµένη στέρηση θα πρέπει να βρούµε σενάριο
στο οποίο η µια διεργασία εισέρχεται συνεχώς στο κρίσιµο τµήµα της ενώ η άλλη,
παρότι θέλει να εισέλθει στο κρίσιµο τµήµα της, παραµένει για πάντα µπλοκαρισµένη
στον κώδικα εισόδου. (Ας θυµηθούµε τη σχετική συζήτηση της Άσκησης
Αυτοαξιολόγησης 9.) Το σενάριο περιγράφεται στο Σχήµα 41.
∆ιεργασία 0 ∆ιεργασία 1
flag[0] = TRUE;
turn = 1;
flag[1] = TRUE;
turn = 0;
Έλεγχος συνθήκης while /* είναι FALSE */
<κρίσιµο τµήµα>;
Έλεγχος συνθήκης while /* είναι TRUE */
flag[1] = FALSE;
<µη-κρίσιµο τµήµα>;
flag[1] = TRUE;
turn = 0;
Έλεγχος συνθήκης while /* είναι TRUE */
Έλεγχος συνθήκης while /* είναι TRUE */
Έλεγχος συνθήκης while /* είναι FALSE */
<κρίσιµο τµήµα>;
flag[1] = FALSE;
<µη-κρίσιµο τµήµα>;
flag[1] = TRUE;
turn = 0;

Έλεγχος συνθήκης while /* είναι TRUE */


Έλεγχος συνθήκης while /* είναι TRUE */
Έλεγχος συνθήκης while /* είναι TRUE */ Έλεγχος συνθήκης while /* είναι FALSE */
<κρίσιµο τµήµα>;
flag[1] = TRUE;
turn = 0;
.
. .
. .
.

Σχήµα 41: Ένα σενάριο που οδηγεί σε παρατεταµένη στέρηση την ελαφρώς τροποποιηµένη έκδοση της
λύσης του Peterson.

2η έκδοση 89
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Στο Σχήµα 41, η διεργασία 0 ξεκινά πρώτη, εκτελεί τις δύο πρώτες εντολές της και
διακόπτεται. Η διεργασία 1 δροµολογείται στη συνέχεια και εισέρχεται στο κρίσιµο
τµήµα της. Όταν η διεργασία 1 εξέρχεται από το κρίσιµο τµήµα της, προλαβαίνει πριν
δροµολογηθεί η 0 να ξαναεκτελέσει τις δύο πρώτες εντολές του κώδικα εισόδου της (θα
µπορούσαµε εναλλακτικά να είχαµε αφήσει την 1 να εκτελέσει όλο τον κώδικα εισόδου
της και να την διακόπταµε όταν θα ήταν και πάλι στο κρίσιµο τµήµα της). Έτσι, η 0,
παρότι περιοδικά της δίνεται η δυνατότητα να εκτελεστεί, δεν παρουσιάζει πρόοδο αφού
(είναι κακότυχη και) η συνθήκη της while που εκτελεί είναι πάντα αληθής όταν ο
χρονοδροµολογητής αποφασίζει να τη δροµολογήσει.
Εξετάστε αν η λύση του Peterson είναι δίκαιη λύση. Στην περίπτωση που η λύση του
Peterson είναι δίκαιη λύση, τι είναι το διαφορετικό στις δύο λύσεις που κάνει τη µια
δίκαιη και την άλλη να οδηγεί σε παρατεταµένη στέρηση; □

Άσκηση Αυτοαξιολόγησης 22
Εξετάστε αν η προτεινόµενη λύση του Σχήµατος 42 είναι σωστή λύση στο πρόβληµα του
αµοιβαίου αποκλεισµού για δύο διεργασίες.
Κοινές µεταβλητές
shared boolean flag[2]; /* αρχικά, FALSE */
∆ιεργασία 0 ∆ιεργασία 1
flag[0] = 1; start: flag[1] = 0;
while (flag[1] != 0) do noop; while (flag[0] != 0) do noop;
<κρίσιµο τµήµα>; flag[1] = 1;
flag[0] = 0; if (flag[0] == 1) then goto start;
<µη-κρίσιµο τµήµα>; <κρίσιµο τµήµα>;
flag[1] = 0;
<µη-κρίσιµο τµήµα>;

Σχήµα 42: Έβδοµη προτεινόµενη λύση µε χρήση λογισµικού στο πρόβληµα του αµοιβαίου αποκλεισµού.

Είναι η λύση του Σχήµατος 42 δίκαιη λύση;

Άσκηση Αυτοαξιολόγησης 23 (Μέρος Θέµατος 3, 4η Εργασία, Ακ. Έτος 2001-2002)


Εξετάστε αν η λύση που προτείνεται στο Σχήµα 43 είναι σωστή λύση στο πρόβληµα του
αµοιβαίου αποκλεισµού για δύο διεργασίες.
Κοινές Μεταβλητές
shared int busy = 0;
shared int trying = -1;
Κώδικας που εκτελεί κάθε διεργασία
start: while (busy == 1) noop;
trying = GetPid();
if (busy == 1) goto start;
busy = 1;
if (trying != GetPid()) goto start;

2η έκδοση 90
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

<κρίσιµο τµήµα>;
busy = 0;
<µη-κρίσιµο τµήµα>;

Σχήµα 43: Όγδοη προτεινόµενη λύση µε χρήση λογισµικού στο πρόβληµα του αµοιβαίου αποκλεισµού.

Η ρουτίνα GetPid() επιστρέφει τον ίδιο πάντα ακέραιο όταν καλείται από τη διεργασία 0
(π.χ., ας υποθέσουµε ότι η GetPid() επιστρέφει το 0 στη διεργασία 0) και τον ίδιο πάντα
ακέραιο, διαφορετικό από αυτόν που επιστρέφει στη διεργασία 0, όταν καλείται από τη
διεργασία 1 (π.χ., ας υποθέσουµε ότι η GetPid() επιστρέφει το 1 στη διεργασία 1).
Εποµένως, ο κώδικας που εκτελεί η κάθε διεργασία είναι αυτός που φαίνεται στο Σχήµα
44.
∆ιεργασία 0 ∆ιεργασία 1
1. start: while (busy == 1) noop; 1. start: while (busy == 1) noop;
2. trying = 0; 2. trying = 1;
3. if (busy == 1) goto start; 3. if (busy == 1) goto start;
4. busy = 1; 4. busy = 1;
5. if (trying != 0) goto start; 5. if (trying != 1) goto start;
<κρίσιµο τµήµα>; <κρίσιµο τµήµα>;
busy = 0; busy = 0;
<µη-κρίσιµο τµήµα>; <µη-κρίσιµο τµήµα>;

Σχήµα 44: Πιο αναλυτική περιγραφή του κώδικα των διεργασιών της όγδοης προτεινόµενη λύσης.

Υπόδειξη: Βεβαιωθείτε ότι δεν µπορεί να βρίσκονται δύο διεργασίες ταυτόχρονα στο κρίσιµο τµήµα τους
(δηλαδή η συνθήκη του αµοιβαίου αποκλεισµού ισχύει). Βρείτε σενάριο στο οποίο και οι δύο διεργασίες
επιθυµούν να εισέλθουν στο κρίσιµο τµήµα τους, αλλά βρίσκονται και οι δύο µπλοκαρισµένες στη while
του κώδικα εισόδου. (Στο κακό σενάριο, η µία διεργασία θα οδηγηθεί στη while µέσω του goto της if της
γραµµής 3 του κώδικα και η άλλη µέσω του goto της if της γραµµής 5 του κώδικα).

Άσκηση Αυτοαξιολόγησης 24 (Μέρος Θέµατος 3, 4η Εργασία, Ακ. Έτος 2001-2002)


Εξετάστε αν η λύση που προτείνεται στο Σχήµα 45 είναι σωστή λύση στο πρόβληµα του
αµοιβαίου αποκλεισµού για δύο διεργασίες.
Κοινές µεταβλητές
shared int busy = 0;
shared int trying = -1;
Κώδικας για κάθε διεργασία
start: trying = GetPid();
if (busy == 1) goto start;
busy = 1;
if (trying != GetPid()) then
begin
busy = 0;
goto start;

2η έκδοση 91
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

end
<κρίσιµο τµήµα>;
busy = 0;
<µη-κρίσιµο τµήµα>;

Σχήµα 45: Ένατη προτεινόµενη λύση στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες.

Υπόδειξη: Η συνθήκη του αµοιβαίου αποκλεισµού δεν ισχύει. Προσπαθήστε να βρείτε κακό σενάριο που
οδηγεί και τις δύο διεργασίες στο κρίσιµο τµήµα τους ταυτόχρονα.
Η συνθήκη προόδου επίσης δεν ισχύει. Υπάρχει σενάριο στο οποίο και οι δύο διεργασίες επιθυµούν να
εισέλθουν στο κρίσιµο τµήµα τους αλλά καµία δεν το επιτυγχάνει. Αν προσπαθήσατε και δεν µπορέσατε
να το βρείτε µην το βάζετε κάτω. Το σενάριο αυτό είναι πραγµατικά δύσκολο. Μελετήστε το από την
ενδεικτική επίλυση του θέµατος 3 της 4ης εργασίας του Ακ. Έτους 2001-2002. Παρουσιάζει ενδιαφέρον.

3.6 Σηµαφόροι
Ένας σηµαφόρος είναι µια διαµοιραζόµενη ακέραια µεταβλητή την οποία οι διεργασίες
µπορούν να προσβαίνουν µόνο µέσω της εκτέλεσης δύο λειτουργιών που ονοµάζονται
down() (ή P()) και up() (ή V()). (Επειδή οι ονοµασίες P() και V() είναι αρχικά
ολλανδικών λέξεων και άρα είναι ελάχιστα µνηµονικές για όσους δεν µιλούν την
Ολλανδική γλώσσα, θα χρησιµοποιηθούν από εδώ και στο εξής τα ονόµατα down() και
up() για τις δύο λειτουργίες.)
Η λειτουργία up() αυξάνει την τιµή του σηµαφόρου κατά 1. Έτσι, κάθε φορά που µια
διεργασία εκτελεί την up(), η τιµή του σηµαφόρου αυξάνεται κατά 1 (ωστόσο, η
πραγµατική τιµή του σηµαφόρου δεν είναι γνωστή ούτε στην διεργασία που εκτελεί την
up() ούτε και σε καµία άλλη διεργασία χρήστη του συστήµατος).
Η λειτουργία down() ελέγχει την τιµή του σηµαφόρου και αν είναι µεγαλύτερη του 0 την
µειώνει κατά 1 και τερµατίζει. Αν η τιµή του σηµαφόρου είναι µικρότερη ή ίση του
µηδενός, η διεργασία που εκτελεί την down() απενεργοποιείται. Όλες οι διεργασίες που
εκτελούν την down() πάνω σε ένα σηµαφόρο που έχει τιµή µικρότερη ή ίση του µηδενός
απενεργοποιούνται. Αν αργότερα ο σηµαφόρος αποκτήσει τιµή µεγαλύτερη του µηδενός
(λόγω της εκτέλεσης µιας η περισσότερων up() λειτουργιών στο σηµαφόρο), όλες οι
διεργασίες που έχουν απενεργοποιηθεί πάνω στο σηµαφόρο επανενεργοποιούνται και
προσπαθούν να εκτελέσουν την down() εξ αρχής. Ανάλογα µε την τιµή του σηµαφόρου
µία ή περισσότερες από αυτές θα τα καταφέρουν ενώ οι υπόλοιπες και πάλι θα
απενεργοποιηθούν, κ.ο.κ.
Σε έναν σηµαφόρο µπορεί επίσης να αποδοθεί αρχική τιµή. Είναι πολύ σηµαντικό να
θυµάται ο αναγνώστης, ότι αυτές είναι οι µοναδικές λειτουργίες που υποστηρίζονται από
σηµαφόρους. Για παράδειγµα, δεν είναι δυνατό να διαβαστεί η τιµή ενός σηµαφόρου.
Επίσης, οι µόνοι τρόποι να αλλαχθεί η τιµή ενός σηµαφόρου είναι µέσω της εκτέλεσης
µιας ή περισσότερων up() και down() λειτουργιών (δηλαδή, εκτός από την αρχική
ανάθεση τιµής, δεν επιτρέπεται να γίνει άλλη απευθείας ανάθεση τιµής σε έναν
σηµαφόρο). Οι λειτουργίες up() και down() εκτελούνται ατοµικά.
Μια απλοϊκή υλοποίηση των λειτουργιών up() και down() ενός σηµαφόρου µε χρήση
λογισµικού φαίνεται στο Σχήµα 46. Στην υλοποίηση αυτή δεν παρουσιάζεται ο τρόπος µε
τον οποίο επιτυγχάνεται η ατοµική εκτέλεση των λειτουργιών αυτών.

2η έκδοση 92
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Λειτουργία up(semaphore S) Λειτουργία down(semaphore S)


S = S + 1; while (S <= 0) do noop;
S = S -1;

Σχήµα 46: Υλοποίηση λειτουργιών up() και down() µε χρήση λογισµικού.

Η λειτουργία up() αυξάνει την τιµή του σηµαφόρου κατά 1. Η λειτουργία down() ελέγχει
επαναληπτικά την τιµή του S µέχρι να ανακαλύψει ότι αυτή είναι µεγαλύτερη του
µηδενός. Σε αυτή την περίπτωση την µειώνει κατά 1 και τερµατίζει. Τροποποιήσεις του S
θα πρέπει να είναι εγγυηµένο πως εκτελούνται ατοµικά. Επίσης, ο έλεγχος της while
στην down() και η ενδεχόµενη µείωση της τιµής του S που ακολουθεί θα πρέπει να
εκτελούνται επίσης ατοµικά.
Στην πραγµατικότητα, τα ΛΣ δεν υλοποιούν τις λειτουργίες up() και down() µε τον τρόπο
που παρουσιάζεται στο Σχήµα 46. Ο λόγος είναι πως η επαναληπτική εκτέλεση της
συνθήκης της while της down() από µια ή περισσότερες διεργασίες επιβαρύνει το
σύστηµα, ενώ δεν εκτελείται κάποιος χρήσιµος υπολογισµός. Όταν µια διεργασία ελέγχει
επαναληπτικά µια συνθήκη χωρίς να εκτελεί οποιοδήποτε άλλο χρήσιµο υπολογισµό,
λέµε ότι βρίσκεται σε ενεργό αναµονή. Για λόγους καλής απόδοσης, τα ΛΣ φροντίζουν
να αποφεύγουν την ενεργό αναµονή.
Οι λειτουργίες up() και down() υλοποιούνται ως κλήσεις συστήµατος. Συνήθως, το ΛΣ
χρησιµοποιεί µια λίστα για κάθε σηµαφόρο στην οποία τοποθετεί όλες τις διεργασίες που
πρέπει να απενεργοποιηθούν πάνω στο σηµαφόρο. Αν αργότερα το ΛΣ αναλάβει την
εκτέλεση µιας ή περισσότερων up() λειτουργιών και µετά το πέρας τους διαπιστώσει ότι
ο σηµαφόρος έχει πλέον τιµή µεγαλύτερη του µηδενός, διαλέγει αυθαίρετα µια από τις
διεργασίες της λίστας, µειώνει την τιµή του σηµαφόρου κατά 1 και επιστρέφει
επιβεβαίωση περάτωσης της λειτουργίας down() στη διεργασία που επιλέχθηκε για να
συνεχίσει την εκτέλεσή της. Αυτό γίνεται επαναληπτικά µέχρι η τιµή του σηµαφόρου να
ξαναγίνει 0 ή η λίστα απενεργοποιηµένων διεργασιών να αδειάσει.
Για να γίνει κατανοητή η χρησιµότητα των σηµαφόρων ως εργαλεία συγχρονισµού, ας
δούµε πως θα µπορούσαµε να επιλύσουµε το πρόβληµα του αµοιβαίου αποκλεισµού µε
χρήση σηµαφόρων. Η λύση είναι πολύ απλή. Κάθε διεργασία εκτελεί τον κώδικα του
Σχήµατος 47.
Σηµαφόροι
semaphore mutex; /* αρχική τιµή 1 */
Κώδικας που εκτελεί κάθε διεργασία
repeat
begin
down(mutex);
<κρίσιµο τµήµα>;
up(mutex);
<µη-κρίσιµο τµήµα>;
end
forever;

Σχήµα 47: Λύση Προβλήµατος Αµοιβαίου Αποκλεισµού µε Σηµαφόρους.

2η έκδοση 93
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Εξηγούµε στη συνέχεια γιατί η λύση του Σχήµατος 47 είναι σωστή. Η λύση χρησιµοποιεί
έναν σηµαφόρο mutex που έχει αρχική τιµή 1. Έστω ότι δύο ή περισσότερες διεργασίες
επιθυµούν να εισέλθουν στο κρίσιµο τµήµα τους. Ας θυµηθούµε ότι η down() εκτελείται
ατοµικά. Η πρώτη διεργασία, έστω Α, που θα την καλέσει, θα µειώσει την τιµή του
σηµαφόρου σε 0 και θα εισέλθει στο κρίσιµο τµήµα της. Όλες οι υπόλοιπες διεργασίες
που θα εκτελέσουν την down() όσο η Α βρίσκεται στο κρίσιµο τµήµα της, θα βρουν την
τιµή του mutex ίση µε 0 και θα απενεργοποιηθούν. Μόνο όταν αργότερα η Α εξέλθει από
το κρίσιµο τµήµα της και εκτελέσει την up(), η τιµή του σηµαφόρου θα αλλάξει σε 1 και
έτσι θα επιτραπεί σε µια ακόµη διεργασία να εισέλθει στο κρίσιµο τµήµα της (οι
υπόλοιπες θα βρουν και πάλι την τιµή του σηµαφόρου ίση µε 0 και θα απενεργοποιηθούν
για άλλη µια φορά). Έτσι, µόνο µια διεργασία µπορεί να βρίσκεται κάθε χρονική στιγµή
στο κρίσιµο τµήµα της. ∆εν είναι δύσκολο να συµπεράνουµε ότι η λύση του Σχήµατος
47 ικανοποιεί και τη συνθήκη προόδου. Εποµένως, η λύση του Σχήµατος 47 είναι σωστή
λύση στο πρόβληµα του αµοιβαίου αποκλεισµού. Η απλότητα της λύσης αυτής οφείλεται
στην υπόθεση ότι το σύστηµα µας παρέχει ένα ισχυρό εργαλείο συγχρονισµού, όπως οι
σηµαφόροι.
Σηµαφόροι όπως ο mutex στο Σχήµα 47, που χρησιµοποιούνται µε τον τρόπο που
περιγράφτηκε πιο πάνω για την επίτευξη αµοιβαίου αποκλεισµού, ονοµάζονται δυαδικοί
σηµαφόροι. Ένας δυαδικός σηµαφόρος έχει αρχική τιµή 1 (προκειµένου να επιτραπεί
στην 1η διεργασία που θα εκτελέσει την down() πάνω στο σηµαφόρο να µην
απενεργοποιηθεί και εποµένως να εισέλθει στο κρίσιµο τµήµα της), ενώ οι µοναδικές
τιµές που µπορεί να πάρει είναι 0 και 1. Επαφίεται στον αναγνώστη να επαληθεύσει ότι
οι ισχυρισµοί αυτοί είναι αληθείς για το σηµαφόρο mutex του Σχήµατος 47.

Άσκηση Αυτοαξιολόγησης 25 (Θέµα 1, Ερώτηµα 4, Εργασία 4, Ακ. Έτος 2001-2002 &


Θέµα 2, Ερώτηµα 1, Εργασία 4, Ακ. Έτος 2003-2004)
Υποθέστε ότι στην υλοποίηση της λειτουργίας down() του Σχήµατος 46, ο έλεγχος της
while και η ενδεχόµενη µείωση της τιµής του S που ακολουθεί δεν εκτελούνται ατοµικά.
Είναι σε αυτή την περίπτωση σωστή η λύση του προβλήµατος του αµοιβαίου
αποκλεισµού που περιγράφεται στο Σχήµα 47;

Άσκηση Αυτοαξιολόγησης 26 (Μέρος Άσκησης Αυτοαξιολόγησης 3.12 Τόµου Γ)


Αποτελεί ο κώδικας του Σχήµατος 48 σωστή λύση στο πρόβληµα του αµοιβαίου
αποκλεισµού;
Σηµαφόροι
semaphore mutex; /* µε αρχική τιµή 1 */
Κώδικας που εκτελεί κάθε διεργασία
down(mutex);
<κρίσιµο τµήµα>;
down(mutex);
<µη-κρίσιµο τµήµα>;

Σχήµα 48: Ένα κοινό λάθος στη χρήση σηµαφόρων.

Λύση

2η έκδοση 94
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Είναι σχετικά εύκολο να επαληθεύσετε ότι ο κώδικας του Σχήµατος 48 ικανοποιεί τη


συνθήκη του αµοιβαίου αποκλεισµού. Μια µόνο διεργασία, έστω η Α, θα καταφέρει να
µειώσει την τιµή του σηµαφόρου και να εισέλθει στο κρίσιµο τµήµα της. Οι υπόλοιπες
θα βρουν τη τιµή του σηµαφόρου ίση µε 0 και θα µπλοκάρουν στην down(). Άρα, δεν
είναι δυνατό δύο διεργασίες να εκτελούν ταυτόχρονα το κρίσιµο τµήµα τους. Ωστόσο, η
λύση του Σχήµατος 48 δεν ικανοποιεί τη συνθήκη προόδου. Όταν η Α τελειώνει την
εκτέλεση του κρίσιµου τµήµατός της, εκτελεί down() στο σηµαφόρο mutex. Όµως, η
τιµή του σηµαφόρου είναι 0 και άρα η Α απενεργοποιείται (όπως έχουν απενεργοποιηθεί
και όλες οι άλλες διεργασίες που επιθυµούν να εισέλθουν στο κρίσιµο τµήµα τους και
όπως θα απενεργοποιηθούν, λόγω του πρώτου down() στον κώδικα και όλες οι
διεργασίες που θα επιχειρήσουν να εισέλθουν στο κρίσιµο τµήµα τους στο µέλλον). Όλες
αυτές οι διεργασίες δεν θα επανενεργοποιηθούν ποτέ, αφού ποτέ καµία διεργασία δεν θα
εκτελέσει κάποια λειτουργία up() στον mutex για να αυξήσει την τιµή του. Με άλλα
λόγια το σύστηµα βρίσκεται σε αδιέξοδο. Παρότι καµία διεργασία δεν εκτελεί το κρίσιµο
τµήµα της, και παρότι υπάρχουν διεργασίες που επιθυµούν να εισέλθουν, καµία από
αυτές δεν τα καταφέρνει. Εποµένως, η λύση του Σχήµατος 48 δεν ικανοποιεί τη συνθήκη
προόδου και άρα δεν είναι σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού. □

Άσκηση Αυτοαξιολόγησης 27 (Μέρος Άσκησης Αυτοαξιολόγησης 3.12 Τόµου Γ)


Αποτελεί ο κώδικας του Σχήµατος 49 σωστή λύση στο πρόβληµα του αµοιβαίου
αποκλεισµού;
Σηµαφόροι
semaphore mutex; /* µε αρχική τιµή 1 */
Κώδικας που εκτελεί κάθε διεργασία
up(mutex);
<κρίσιµο τµήµα>;
down(mutex);
<µη-κρίσιµο τµήµα>;

Σχήµα 49: Ένα ακόµη κοινό λάθος στη χρήση σηµαφόρων.

Λύση
Η λύση του Σχήµατος 49 δεν ικανοποιεί τη συνθήκη αµοιβαίου αποκλεισµού. Ας
υποθέσουµε ότι δύο διεργασίες επιχειρούν να εισέλθουν στο κρίσιµο τµήµα τους
ταυτόχρονα. Η πρώτη που θα εκτελέσει την εντολή up(mutex) θα αλλάξει την τιµή του
mutex από 1 σε 2 και θα εισέλθει στο κρίσιµο τµήµα της. Αν τώρα µια δεύτερη διεργασία
δροµολογηθεί, θα αυξήσει και αυτή την τιµή του mutex κατά 1 και θα εισέλθει και αυτή
στο κρίσιµο τµήµα της. Όσες διεργασίες και να επιχειρήσουν να εισέλθουν στο κρίσιµο
τµήµα τους θα τα καταφέρουν ανεξάρτητα µε το πόσες άλλες διεργασίες βρίσκονται
ταυτόχρονα στο κρίσιµο τµήµα τους. Εποµένως, η λύση του Σχήµατος 49 δεν είναι
σωστή λύση του προβλήµατος του αµοιβαίου αποκλεισµού. □
Παρατήρηση
Είναι σηµαντικό να κατανοήσει ο αναγνώστης γιατί η λύση του Σχήµατος 47 είναι
σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού, ενώ οι λύσεις που προτείνονται

2η έκδοση 95
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

στα Σχήµατα 48 και 49 δεν είναι σωστές λύσεις. ∆εν θα πρέπει να συνεχίσει τη µελέτη
του αν δεν έχει βεβαιωθεί πως έχει κατανοήσει σε βάθος που οφείλεται η ορθότητα της
πρώτης λύσης και ποια είναι τα προβλήµατα των δύο τελευταίων.

Άσκηση Αυτοαξιολόγησης 28 (Μέρος Θέµατος 2, Εργασία 4, Ακ. Έτος 2001-2002 )


Περιγράψτε τις ρουτίνες boolean deposit(int amount) και boolean withdraw(int amount),
που περιγράφτηκαν στην Άσκηση Αυτοαξιολόγησης 8. Η λύση σας θα πρέπει να
επιτυγχάνει αµοιβαίο αποκλεισµό χρησιµοποιώντας σηµαφόρους.

Άσκηση Αυτοαξιολόγησης 29
Είναι η λύση που παρουσιάζεται στο Σχήµα 47 δίκαιη λύση του προβλήµατος του
αµοιβαίου αποκλεισµού; Περιγράψτε σενάριο στο οποίο µια διεργασία εισέρχεται µη-
πεπερασµένο αριθµό φορών στο κρίσιµο τµήµα της, ενώ άλλες διεργασίες παραµένουν
µπλοκαρισµένες επ’ άπειρο εκτελώντας τον κώδικα εισόδου.

Άσκηση Αυτοαξιολόγησης 30 (Παραλλαγή Άσκησης Αυτοαξιολόγησης 3.11 Τόµου Γ)


Έχει γίνει σαφές πως αν έχουµε σηµαφόρους µπορούµε εύκολα να επιλύσουµε το
πρόβληµα του αµοιβαίου αποκλεισµού. Ωστόσο, οι λειτουργίες των σηµαφόρων δεν
παρέχονται εξ ουρανού. Οι λειτουργίες αυτές είναι σχετικά σύνθετες και η ατοµική τους
εκτέλεση πρέπει µε κάποιο τρόπο να υλοποιηθεί.
Θεωρείστε ότι το ΛΣ ενός συστήµατος δεν υποστηρίζει σηµαφόρους, αλλά το υλικό του
συστήµατος παρέχει την λειτουργία Test&Set(). Υποθέστε ότι η εταιρεία στην οποία
εργάζεστε σας αναθέτει να υλοποιήσετε τις ρουτίνες down() και up() που υποστηρίζει
µια µεταβλητή τύπου σηµαφόρος. Παρουσιάστε τη λύση που θα προτείνατε.
Λύση
Η άσκηση αυτή δεν είναι απλή. Ο αναγνώστης δεν θα πρέπει να απογοητεύεται αν νιώθει
πως δεν µπορεί να σκιαγραφήσει κάποια πιθανή λύση. Το νέο στοιχείο στην άσκηση
αυτή είναι πως ζητείται να υλοποιηθεί (ή να προσοµοιωθεί όπως λέγεται) ένα
αντικείµενο, στην περίπτωση µας οι σηµαφόροι, χρησιµοποιώντας άλλου είδους
αντικείµενα, στην περίπτωση µας κοινές µεταβλητές που υποστηρίζουν την ατοµική
εκτέλεση της Test&Set() λειτουργίας.
Θα εργαστούµε ως εξής. Πρέπει να παρέχουµε κώδικα για τις λειτουργίες down() και
up() που υποστηρίζονται από τους σηµαφόρους χρησιµοποιώντας κοινές µεταβλητές και
την Test&Set() που µας παρέχει το υλικό. Ο κώδικας πρέπει να σχεδιαστεί προσεκτικά,
ώστε όταν καλείται οποιαδήποτε από τις λειτουργίες up() και down() που υποστηρίζονται
από τους σηµαφόρους, οι ενέργειες που πραγµατοποιούνται να είναι οι αναµενόµενες µε
βάση τον ορισµό των down() και up() που δόθηκε παραπάνω.
Εποµένως, όταν καλείται η λειτουργία down() θα πρέπει ο κώδικας που θα φτιάξουµε να
εγγυάται τα ακόλουθα:
1. Ελέγχει την τιµή του σηµαφόρου και αν είναι µεγαλύτερη του 0 την µειώνει κατά 1.
Οι δύο αυτές λειτουργίες πρέπει να εκτελούνται ατοµικά (δηλαδή η εκτέλεση των
δύο αυτών λειτουργιών αποτελεί τώρα το κρίσιµο τµήµα µας).
2. Αν η τιµή του σηµαφόρου είναι 0, η διεργασία θα πρέπει να µπλοκάρεται.

2η έκδοση 96
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Όταν καλείται η λειτουργία up(), η τιµή του σηµαφόρου πρέπει να αυξάνει κατά 1. Η
λειτουργία αυτή θα πρέπει να εκτελείται ατοµικά.
Η λύση παρουσιάζεται στο Σχήµα 50.
Σηµαφόροι και Κοινές Μεταβλητές
typedef shared int semaphore;
/* ο τύπος semaphore προσοµοιώνεται από τον τύπο shared int */
semaphore sem;
shared int lock = 1;
/* χρησιµοποιούµε επίσης µια διαµοιραζόµενη ακέραια µεταβλητή lock µε αρχική τιµή 1 */

void down(semaphore sem)


int tmp; /* τοπική µεταβλητή */
begin
start: tmp = Test&Set(lock);
while (tmp == TRUE) do tmp = Test&Set(lock);
if (sem > 0) then
begin
sem = sem – 1;
lock = 0;
end
else
begin
lock = 0;
goto start;
end
end

void up(semaphore sem)


int tmp; /* τοπική µεταβλητή */
begin
tmp = Test&Set(lock);
while (tmp == TRUE) do tmp = Test&Set(lock);
sem = sem + 1;
lock = 0;
end

Σχήµα 50: Υλοποίηση σηµαφόρων χρησιµοποιώντας ακέραιες διαµοιραζόµενες µεταβλητές και την
ατοµική λειτουργία Test&Set() που παρέχεται από το υλικό.

Κάθε µεταβλητή σηµαφόρος (π.χ., η sem) προσοµοιώνεται από µια ακέραια


διαµοιραζόµενη µεταβλητή (µε το ίδιο όνοµα). Κάθε φορά που καλείται η down(), η τιµή
της sem ελέγχεται και αν είναι µεγαλύτερη από το 0 µειώνεται κατά 1 και η down()
τερµατίζει. Στην αντίθετη περίπτωση, ο κώδικας της down() εκτελείται επαναληπτικά
ξανά και ξανά. Ο έλεγχος και η µείωση της sem πρέπει να γίνονται ατοµικά. Οι δύο
αυτές ενέργειες αποτελούν εποµένως το κρίσιµο τµήµα µας, το οποίο πρέπει να
προστατεύσουµε µε κώδικα εισόδου και κώδικα εξόδου. Χρησιµοποιούµε εποµένως µια

2η έκδοση 97
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

ακόµη διαµοιραζόµενη µεταβλητή lock που µας επιτρέπει να χρησιµοποιήσουµε τον


κώδικα εισόδου και τον κώδικα εξόδου του Σχήµατος 20, ο οποίος χρησιµοποιεί την
Test&Set() για να επιτύχει τον αµοιβαίο αποκλεισµό.
Η αύξηση της sem που εκτελεί η up() θα πρέπει να πραγµατοποιείται επίσης ατοµικά.
Έτσι, ο κώδικας εισόδου και ο κώδικας εξόδου του Σχήµατος 20 χρησιµοποιούνται για
άλλη µια φορά. Φυσικά η ίδια κοινή µεταβλητή lock χρησιµοποιείται τόσο στον κώδικα
της down() όσο και στον κώδικα της up(), αφού δεν επιτρέπεται οι δύο λειτουργίες να
εκτελούνται ταυτόχρονα.
Ο αναγνώστης θα πρέπει να επενδύσει λίγο χρόνο για να βεβαιωθεί πως οι ρουτίνες
down() και up() που παρουσιάζονται στο Σχήµα 50 λειτουργούν µε σωστό τρόπο
(δηλαδή συµβατό ως προς τον ορισµό τους). □

Άσκηση Αυτοαξιολόγησης 31 (Παραλλαγή Άσκησης Αυτοαξιολόγησης 3.11 Τόµου Γ)


Υποθέστε τώρα ότι ούτε το ΛΣ ενός συστήµατος υποστηρίζει σηµαφόρους, ούτε και το
υλικό υποστηρίζει την ατοµική εκτέλεση σύνθετων εντολών, όπως η Test&Set() κ.α.
Παρουσιάστε µια υλοποίηση σηµαφόρων που θα µπορούν να χρησιµοποιούνται µόνο
από δύο διεργασίες, χρησιµοποιώντας τη λύση του Peterson για να επιτύχετε την ατοµική
εκτέλεση των λειτουργιών up() και down().

Άσκηση Αυτοαξιολόγησης 32 (Παραλλαγή Άσκησης Αυτοαξιολόγησης 3.11 Τόµου Γ)


Επαναλάβετε την προηγούµενη άσκηση αυτοαξιολόγησης χρησιµοποιώντας τώρα, αντί
για τη λύση του Peterson, τη λύση που περιγράφεται στο Σχήµα 40 για να επιτύχετε την
ατοµική εκτέλεση των λειτουργιών up() και down().

3.7 Προβλήµατα ∆ιαδιεργασιακής Επικοινωνίας


Στην ενότητα αυτή θα χρησιµοποιήσουµε σηµαφόρους για να επιλύσουµε ένα σύνολο
από ενδιαφέροντα προβλήµατα συγχρονισµού.

3.7.1 Για Προθέρµανση


Ξεκινάµε την µελέτη µας µε ένα σχετικά απλό πρόβληµα διαδιεργασιακής επικοινωνίας
(Θέµα 2α, Β’ Τελική Εξέταση, Ιούλιος 2002). Έστω ένα σύστηµα στο οποίο ένα σύνολο
από διεργασίες-πελάτες (clients) επικοινωνούν µε µία διεργασία-εξυπηρέτη (server)
µέσω µίας κοινής µεταβλητής X. Κάθε πελάτης µπορεί να παράγει επαναληπτικά
αιτήσεις τις οποίες γράφει στην X προκειµένου να τις διαβάσει και να τις επεξεργαστεί
αργότερα ο εξυπηρέτης. Κάθε φορά που ένας πελάτης παράγει µία αίτηση πρέπει να
ενεργοποιηθεί ο εξυπηρέτης ώστε να εξυπηρετήσει την αίτηση, ενώ ενδιάµεσα κανένας
άλλος πελάτης δεν θα πρέπει να µπορεί να παράγει αιτήσεις. Ο εξυπηρέτης µε τη σειρά
του, αφού εξυπηρετήσει την αίτηση, θα πρέπει να ενεργοποιήσει κάποιον άλλον πελάτη
(ή ίσως και τον ίδιο) για την παραγωγή µίας νέας αίτησης. Ζητείται να επιλυθεί το
παραπάνω πρόβληµα συγχρονισµού χρησιµοποιώντας σηµαφόρους
Σε γενικές γραµµές, η επίλυση προβληµάτων συγχρονισµού είναι µια αρκετά επίπονη και
δύσκολη εργασία που απαιτεί µεγάλη εµπειρία. Στη συνέχεια, επιχειρούµε να παρέχουµε

2η έκδοση 98
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

µερικές συµβουλές για τον τρόπο που πρέπει κάποιος να εργάζεται, οι οποίες ίσως
βοηθούν στην επίλυση τέτοιων προβληµάτων.
Το πρώτο ερώτηµα που θα πρέπει να απαντηθεί, όταν ζητείται λύση σε ένα πρόβληµα
συγχρονισµού, είναι τι είδους διεργασίες αναµιγνύονται; Η απάντηση στο ερώτηµα αυτό
είναι εύκολη και συνήθως προκύπτει άµεσα από την εκφώνηση. Αν ωστόσο δεν είναι
σαφής, ο αναγνώστης θα πρέπει να απαντήσει την εξής ερώτηση: «Πόσα διαφορετικά
προγράµµατα µπορούν να εκτελούνται ταυτόχρονα;» ή «Πόσες διαφορετικές οντότητες
µπορούν να είναι ενεργές ταυτόχρονα;» ή «Πόσες διαφορετικές ενέργειες µπορούν να
συµβαίνουν ταυτόχρονα;». Για κάθε ένα τέτοιο πρόγραµµα/οντότητα/ενέργεια, υπάρχει
και ένα είδος διεργασιών. Για παράδειγµα, στο πρόβληµα που προαναφέρθηκε, υπάρχουν
δύο είδη διεργασιών, η διεργασία εξυπηρέτη και οι διεργασίες πελατών. Άρα, θα πρέπει
να γραφεί κώδικας για δύο ρουτίνες, µία για τη διεργασία εξυπηρέτη και µία για τις
διεργασίες πελατών.
Το δεύτερο ερώτηµα που χρειάζεται να απαντηθεί είναι αν απαιτείται η χρήση κοινών
µεταβλητών (ή κοινών πόρων) που θα πρέπει να προσπελάζονται ατοµικά από τις
διεργασίες. Για κάθε κοινό πόρο που απαιτείται, συνήθως χρειαζόµαστε έναν δυαδικό
σηµαφόρο για να επιτύχουµε αµοιβαίο αποκλεισµό. Στο παράδειγµα µας, έχουµε µόνο
µία κοινή µεταβλητή και άρα ενδεχόµενα χρειαζόµαστε έναν δυαδικό σηµαφόρο για να
επιτύχουµε αµοιβαίο αποκλεισµό. Ας ονοµάσουµε τον σηµαφόρο αυτό mutex.
Στη συνέχεια θα πρέπει να αναρωτηθούµε µήπως υπάρχουν καταστάσεις (ή
περιπτώσεις), επιπρόσθετα εκείνων που προκύπτουν λόγω του αµοιβαίου αποκλεισµού,
στις οποίες κάποιο είδος διεργασιών πρέπει να απενεργοποιείται. Προφανώς, κάθε
διεργασία πρέπει να απενεργοποιείται όταν κάποιο άλλη διεργασία προσβαίνει τον κοινό
πόρο, αλλά µήπως υπάρχουν και άλλες περιπτώσεις που απαιτείται απενεργοποίηση
διεργασιών στο πρόβληµά µας;
Παρατηρούµε ότι η διεργασία εξυπηρέτη θα πρέπει να εκτελείται αυστηρά εναλλάξ µε
µια κάθε φορά από τις διεργασίες πελατών. Παρατηρήστε ότι, ενώ η χρήση του δυαδικού
σηµαφόρου mutex εγγυάται ότι µια µόνο διεργασία εκτελεί κάθε φορά το κρίσιµο τµήµα,
δεν παρέχει καµία εγγύηση για τη σειρά µε την οποία θα εκτελεστούν οι διεργασίες.
Βάσει των παραπάνω, προκύπτει ότι υπάρχουν δύο ακόµη περιπτώσεις στο πρόβληµα
που µελετάµε, στις οποίες κάποιο είδος διεργασίας θα πρέπει να απενεργοποιείται:
1. Η διεργασία εξυπηρέτη πρέπει να απενεργοποιείται όσο δεν έχει εκτελεστεί κάποια
από τις διεργασίες πελατών από την τελευταία φορά εκτέλεσής της.
2. Οι διεργασίες πελατών θα πρέπει να απενεργοποιούνται όσο η διεργασία εξυπηρέτη
δεν έχει εκτελεστεί από την τελευταία φορά εκτέλεσης κάποιας διεργασίας πελάτη.
Ο µοναδικός τρόπος απενεργοποίησης των διεργασιών είναι µε την εκτέλεση µιας
λειτουργίας down() πάνω σε κάποιο σηµαφόρο του οποίου η τιµή είναι 0. Άρα, η λύση
χρειάζεται τουλάχιστον τόσους σηµαφόρους όσες και οι περιπτώσεις που προ-
αναφέρθηκαν. Ας ονοµάσουµε ServerSem το σηµαφόρο πάνω στον οποίο θα
απενεργοποιούνται οι διεργασίες-πελάτες και ας ονοµάσουµε ClientSem το σηµαφόρο
πάνω στον οποίο θα απενεργοποιείται η διεργασία εξυπηρέτης (η ανάθεση ονοµάτων
είναι προφανώς αυθαίρετη, αλλά καλό είναι να διαλέγουµε ονόµατα που να επιτρέπουν
στον κώδικα να είναι ευανάγνωστος).

2η έκδοση 99
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Τώρα που έχουµε αποφασίσει πόσους σηµαφόρους θα χρησιµοποιήσουµε για να


λύσουµε το πρόβληµα, το επόµενο βήµα είναι να περιγράψουµε µε λόγια τις ενέργειες
που πρέπει να κάνει κάθε διεργασία.
Καταρχήν, θα πρέπει να αποφασιστεί αν κάθε είδος διεργασιών εκτελεί επαναληπτικά τις
ενέργειες που πρέπει να εκτελέσει ή όχι. Στην περίπτωση που οι ενέργειες εκτελούνται
επαναληπτικά, θα πρέπει να αποτελούν το µπλοκ εντολών µιας εντολής ανακύκλωσης
της µορφής repeat forever.
Για το παράδειγµα µας, τόσο η διεργασία εξυπηρέτη όσο και οι διεργασίες πελατών
εκτελούν επαναληπτικά κάποιες ενέργειες. Οι ενέργειες που εκτελεί κάθε διεργασία
παρουσιάζονται στο Σχήµα 51.
∆ιεργασία Εξυπηρέτης
repeat
begin
1. Αν η αίτηση δεν είναι έτοιµη απενεργοποιήσου;
2. ∆ιάβασε την αίτηση από τη κοινή µεταβλητή Χ; /* κρίσιµο τµήµα */
3. Ενηµέρωσε πελάτες για να ξεκινήσουν τη δηµιουργία νέας αίτησης;
4. Επεξεργάσου την αίτηση; /* µη-κρίσιµο τµήµα */
end
forever;
∆ιεργασία Πελάτης
repeat
begin
1. ∆ηµιούργησε νέα αίτηση /* µη-κρίσιµο τµήµα */
2. Όσο ο εξυπηρέτης διαβάζει µια αίτηση απενεργοποιήσου;
3. Τοποθέτησε την νέα αίτηση στη κοινή µεταβλητή Χ; /* κρίσιµο τµήµα */
4. Ενηµέρωσε εξυπηρέτη;
end
forever;

Σχήµα 51: Περιγραφή ενεργειών που εκτελούν οι διεργασίες πελατών και εξυπηρέτη.

Στο σηµείο αυτό είµαστε πολύ κοντά στη λύση. Όταν έχουµε καταφέρει να γράψουµε
ψευδοκώδικα στη µορφή που παρουσιάζεται στο Σχήµα 51, είναι εύκολο να
αποφασίσουµε ποια είναι τα κρίσιµα τµήµατα κάθε διεργασίας. Κρίσιµα τµήµατα είναι
τα µέρη του κώδικα που προσβαίνουν διαµοιραζόµενες µεταβλητές (και γενικότερα
διαµοιραζόµενους πόρους). Αυτά θα πρέπει να προστατευθούν από ταυτόχρονη
προσπέλαση, περικλείοντάς τα σε κώδικα εισόδου και κώδικα εξόδου, ώστε να
επιτευχθεί αµοιβαίος αποκλεισµός.
Το µόνο που αποµένει να γίνει είναι να καταλάβουµε ποιες down() και up() λειτουργίες
πρέπει να εκτελεστούν για κάθε µια από τις ενέργειες που περιγράφτηκαν πιο πάνω. Ας
δούµε πρώτα τις ενέργειες της διεργασίας εξυπηρέτη. Η 1η ενέργεια θα υλοποιηθεί µε µια
κλήση της down() στο σηµαφόρο ServerSem (ο οποίος αποφασίσαµε πως θα είναι ο
σηµαφόρος πάνω στον οποίο θα απενεργοποιείται η διεργασία εξυπηρέτη). Η 2η ενέργεια
είναι το κρίσιµο τµήµα της διεργασίας εξυπηρέτη και άρα θα πρέπει να γίνει κλήση της
down(mutex) πριν εκτελεστεί η ενέργεια αυτή, καθώς και κλήση της up(mutex) αµέσως

2η έκδοση 100
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

µετά. Η 3η ενέργεια αποσκοπεί στο να επιτρέψει σε κάποια από τις διεργασίες πελατών
(που µπορεί να είναι απενεργοποιηµένη περιµένοντας την διεργασία εξυπηρέτη να
διαβάσει την αίτηση που της στάλθηκε να επανενεργοποιηθεί και) να δηµιουργήσει νέα
αίτηση. Η ενέργεια αυτή υλοποιείται µε ένα up() στο σηµαφόρο ClientSem στον οποίο
αποφασίσαµε πως θα απενεργοποιούνται οι διεργασίες πελατών. Τέλος, η 4η ενέργεια
είναι το µη-κρίσιµο τµήµα της διεργασίας εξυπηρέτη και άρα καµία down() ή up()
λειτουργία δεν σχετίζεται µε την εκτέλεσή του.
Κάθε µια από τις διεργασίες πελατών ξεκινά µε το µη-κρίσιµο τµήµα της (1η ενέργεια)
και άρα δεν εµπλέκονται σηµαφόροι στην ενέργεια αυτή. Η 2η ενέργεια υλοποιείται µε
την κλήση µιας down() στο σηµαφόρο ClientSem πάνω στον οποίο αποφασίσαµε πως θα
απενεργοποιούνται οι διεργασίες πελατών. Η 3η ενέργεια αποτελεί το κρίσιµο τµήµα των
διεργασιών πελατών και άρα θα πρέπει να είναι εγγυηµένο (µε κατάλληλη χρήση του
σηµαφόρου mutex) ότι θα εκτελεστεί ατοµικά. Τέλος, κάθε διεργασία πελάτη πρέπει να
ενηµερώσει τη διεργασία εξυπηρέτη ότι µια νέα αίτηση δηµιουργήθηκε. Αυτό γίνεται
εκτελώντας µια λειτουργία up() πάνω στο σηµαφόρο ServerSem (ο οποίος αποφασίσαµε
πως θα είναι ο σηµαφόρος πάνω στον οποίο θα απενεργοποιείται η διεργασία
εξυπηρέτη).
Πρέπει να είναι σαφές πως στο σηµείο αυτό βρισκόµαστε πολύ κοντά σε µια πρώτη
λύση. Το µόνο που αποµένει να αποφασιστεί είναι ποιες θα είναι οι αρχικές τιµές των
σηµαφόρων. Προφανώς, κάποια διεργασία πελάτη θα πρέπει να είναι εκείνη που θα
ξεκινήσει µε την παραγωγή µιας αίτησης. Άρα, ο σηµαφόρος ServerSem πάνω στον
οποίο απενεργοποιείται η διεργασία εξυπηρέτη θα πρέπει να έχει αρχική τιµή 0 (έτσι
ώστε αν δροµολογηθεί πρώτη η διεργασία αυτή να απενεργοποιηθεί αµέσως). Αντίθετα,
η αρχική τιµή του σηµαφόρου ClientSem θα πρέπει να είναι 1 για να δοθεί η δυνατότητα
σε κάποιον από τους πελάτες να δηµιουργήσει µια πρώτη αίτηση. Η αρχική τιµή του
δυαδικού σηµαφόρου mutex είναι 1.
Μια πρώτη λύση του προβλήµατος που µελετάµε απεικονίζεται στο Σχήµα 52. Η ρουτίνα
process_request() αναλαµβάνει να επεξεργαστεί την αίτηση (µη-κρίσιµο τµήµα
διεργασίας εξυπηρέτη), ενώ η ρουτίνα produce_request() αναλαµβάνει την δηµιουργία
µιας νέας αίτησης σε κάποια τοπική µεταβλητή της εκάστοτε διεργασίας πελάτη που την
καλεί (µη-κρίσιµο τµήµα διεργασίας πελάτη).
Κοινές µεταβλητές & Σηµαφόροι:
semaphore ClientSem = 1;
semaphore ServerSem = 0;
semaphore mutex = 1;
request X;
∆ιεργασία Εξυπηρέτης ∆ιεργασία Πελάτη
repeat repeat
begin begin
down(ServerSem); produce_request();
down(mutex); down(ClientSem);
read X; down(mutex);
up(mutex); write X;
up(ClientSem); up(mutex);

2η έκδοση 101
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

process_request(); up(ServerSem);
end end
forever; forever;

Σχήµα 52: Μια πρώτη λύση στο πρόβληµα πελατών-εξυπηρέτη.

Η λύση (αν και περισσότερο πολύπλοκη από ότι θα έπρεπε, όπως θα δούµε στη
συνέχεια) είναι σωστή. Αν η διεργασία εξυπηρέτη ξεκινήσει να εκτελείται πρώτη θα
απενεργοποιηθεί εκτελώντας την λειτουργία down() στο σηµαφόρο ServerSem που έχει
αρχική τιµή 0. Αν µία ή περισσότερες διεργασίες πελατών ξεκινήσουν την εκτέλεση
τους, όλες εκτός από µία θα µπλοκάρουν εκτελώντας την λειτουργία down() στο
σηµαφόρο ClientSem. Η µοναδική διεργασία, έστω Α, που δεν θα µπλοκάρει στην
down(ClientSem), θα βρει το σηµαφόρο mutex να έχει τιµή 1 και δεν θα µπλοκάρει ούτε
στην down(mutex). Εποµένως, η Α θα συνεχίσει µε την καταγραφή της αίτησής της στη
µεταβλητή Χ. Στη συνέχεια, η Α θα εκτελέσει µια λειτουργία up() στο mutex για να
υποδηλώσει ότι τελείωσε την εκτέλεση του κρίσιµου τµήµατός της και µια up() στον
ServerSem για να αλλάξει την τιµή του από 0 σε 1 και να επιτρέψει στη διεργασία-
εξυπηρέτη να επεξεργαστεί την αίτηση. Προσέξτε ότι ο σηµαφόρος ClientSem
εξακολουθεί να έχει τιµή 0. Εποµένως, όλες οι διεργασίες-πελάτη που θέλουν να
παράγουν αίτηση συνεχίζουν να βρίσκονται µπλοκαρισµένες στην down(ClientSem).
Παρατηρήστε επίσης ότι, αν η Α θελήσει εκ νέου να παράγει µια αίτηση, θα µπλοκάρει
αν εκτελέσει την λειτουργία down(ClientSem) πριν η διεργασία εξυπηρέτη προλάβει να
επεξεργαστεί την αίτηση που είναι ήδη γραµµένη στην Χ.
Η διεργασία εξυπηρέτη εκτελείται όταν κάποια διεργασία πελάτη εκτελέσει µια up() στο
σηµαφόρο ServerSem. Η πρώτη ενέργεια που εκτελεί είναι η down(mutex) προκειµένου
να επιτευχθεί αµοιβαίος αποκλεισµός για την πρόσβαση στη µεταβλητή Χ. Προσέξτε
πως η τιµή του mutex θα είναι 1. Η διεργασία εξυπηρέτη διαβάζει την αίτηση στην Χ,
επιτελεί µια λειτουργία up(mutex) για να υποδηλώσει το τέλος του κρίσιµου τµήµατος
και µια up() στον σηµαφόρο ClientSem για να επιτρέψει σε έναν ακόµη πελάτη να
δηµιουργήσει µια νέα αίτηση.
Είναι σηµαντικό να κατανοήσουµε ότι στο παράδειγµα που µελετάµε, η διεργασία
εξυπηρέτης και µία κάθε φορά από τις διεργασίες πελατών θα πρέπει να εκτελούνται
αυστηρά εναλλάξ. Αυτό ακριβώς επιτυγχάνει ο κώδικας του Σχήµατος 52.
Πριν τελειώσουµε τη µελέτη του προβλήµατος θα πρέπει να µας απασχολήσει ένα
τελευταίο ερώτηµα. Χρειάζονται όλοι οι σηµαφόροι που χρησιµοποιήσαµε ή µήπως
κάποιοι από αυτούς είναι περιττοί; Μήπως ο αµοιβαίος αποκλεισµός που απαιτείται για
την ατοµική προσπέλαση στην διαµοιραζόµενη µεταβλητή X παρέχεται ήδη (έµµεσα)
λόγω της χρήσης των υπολοίπων σηµαφόρων που έχουν στρατευτεί; Με άλλα λόγια,
είναι ποτέ δυνατό δύο ή περισσότερες διεργασίες να προσπαθήσουν ταυτόχρονα να
προσβούν την µεταβλητή Χ, ή µήπως η διαδιεργασιακή επικοινωνία που επιτυγχάνεται
µε τη χρήση των σηµαφόρων ServerSem και ClientSem καθιστά κάτι τέτοιο αδύνατο;
∆εν είναι δύσκολο να γίνει κατανοητό ότι η χρήση του σηµαφόρου mutex είναι περιττή.
Όπως έχει ήδη αναφερθεί, η χρήση των σηµαφόρων ServerSem και ClientSem
συνεπάγεται ότι η διεργασία εξυπηρέτης και µία κάθε φορά από τις διεργασίες πελατών
θα εκτελούνται αυστηρά εναλλάξ. Εποµένως, η χρήση του mutex είναι περιττή. Με άλλα
λόγια, ο αµοιβαίος αποκλεισµός κατά την προσπέλαση στην Χ είναι εγγυηµένος ακόµη

2η έκδοση 102
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

και αν παραληφθούν οι εντολές “down(mutex)” και “up(mutex)” από τον κώδικα του
Σχήµατος 52.
Η απλούστερη λύση του προβλήµατος πελατών-εξυπηρέτη παρουσιάζεται στο Σχήµα 53.
Κοινές µεταβλητές & Σηµαφόροι:
semaphore ClientSem = 1;
semaphore ServerSem = 0;
request X;
∆ιεργασία-Εξυπηρέτης ∆ιεργασία-Πελάτης
repeat repeat
begin begin
down(ServerSem); produce_request();
read X; down(ClientSem);
up(ClientSem); write X;
process_request(); up(ServerSem);
end end
forever; forever;

Σχήµα 53: Μια βελτιστοποιηµένη λύση στο πρόβληµα πελατών-εξυπηρέτη.

Στο σηµείο αυτό τελειώνει η µελέτη του πρώτου απλού προβλήµατος.

Άσκηση Αυτοαξιολόγησης 33 (Θέµα 2α, Β’ Τελική Εξέταση, Ιούλιος 2002)


Υποθέστε ότι για να συγχρονιστούν οι διεργασίες πελατών και εξυπηρετητή που
συζητήθηκαν πιο πάνω σας δίνεται η ακόλουθη λύση.
Κοινές µεταβλητές & Σηµαφόροι:
semaphore ClientSem = 1;
semaphore ServerSem = 0;
request X;
∆ιεργασία-Εξυπηρέτης ∆ιεργασία-Πελάτης
repeat repeat
begin begin
up(ClientActive); produce_request();
read X; down(ServerActive);
down(ServerActive); write X;
process_request(); up(ClientActive);
end end
forever; forever;

Σχήµα 54: Μια λάθος λύση στο πρόβληµα πελατών-εξυπηρέτη.

Εξηγείστε γιατί είναι λάθος η λύση του Σχήµατος 53.

Άσκηση Αυτοαξιολόγησης 34 (Θέµα 2α, Β’ Τελική Εξέταση, Ιούλιος 2002)


Σχολιάστε τη λύση του Σχήµατος 53 ως προς το αν είναι δίκαιη ή όχι.

2η έκδοση 103
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Άσκηση Αυτοαξιολόγησης 35 (Θέµα 4, 4η Εργασία, Ακ. Έτος 2001-2002)


Τρεις µανιώδεις καπνιστές βρίσκονται στο ίδιο δωµάτιο µαζί µε ένα πωλητή ειδών
καπνιστού. Για να φτιάξει και να χρησιµοποιήσει τσιγάρα, κάθε καπνιστής χρειάζεται
τρία συστατικά: καπνό, χαρτί και σπίρτα. Όλα αυτά τα παρέχει ο πωλητής σε αφθονία.
Ένας καπνιστής έχει το δικό του καπνό, ένας δεύτερος το δικό του χαρτί και ο τρίτος τα
δικά του σπίρτα.
Η δράση ξεκινά όταν ο πωλητής τοποθετεί στο τραπέζι δύο από τα απαραίτητα υλικά,
επιτρέποντας έτσι σε έναν από τους καπνιστές να καπνίσει. Όταν ο κατάλληλος
καπνιστής τελειώσει το κάπνισµα, ο πωλητής θα πρέπει να αφυπνίζεται, ώστε να
τοποθετεί στο τραπέζι δύο ακόµη από τα υλικά του – τυχαία – µε αποτέλεσµα να
επιτρέπει και σε άλλον καπνιστή να καπνίσει.
Γράψτε κατάλληλα προγράµµατα για τους καπνιστές και τους πωλητές, µε σκοπό την
επίλυση του συγκεκριµένου προβλήµατος. Η λύση σας θα πρέπει να βασίζεται στη χρήση
σηµαφόρων.

Σκιαγράφηση Λύσης: Θα πρέπει να δουλέψουµε µε παρόµοιο τρόπο όπως στο


πρόβληµα πελατών-εξυπηρέτη. Αρχικά, θα πρέπει να απαντήσουµε το ερώτηµα τι είδους
διεργασίες αναµειγνύονται; Υπάρχει µια διεργασία πωλητή και τρεις διεργασίες
καπνιστών.
Στο παράδειγµά µας, ο κοινός πόρος είναι το τραπέζι και η πρόσβαση σε αυτό θα πρέπει
να γίνεται ατοµικά. Το επόµενο ερώτηµα είναι πόσες ακόµη καταστάσεις (ή περιπτώσεις)
υπάρχουν στις οποίες κάποιο είδος διεργασιών πρέπει να απενεργοποιείται. Οι
περιπτώσεις αυτές είναι οι ακόλουθες:
• Η διεργασία πωλητή πρέπει να απενεργοποιείται µέχρι ο εκάστοτε καπνιστής να
χρησιµοποιήσει τα υλικά που τοποθετήθηκαν στο τραπέζι και το τραπέζι να γίνει
άδειο ξανά. Χρειαζόµαστε εποµένως έναν σηµαφόρο για την απενεργοποίηση της
διεργασίας πωλητή. Ας ονοµάσουµε τον σηµαφόρο αυτό SellerSem.
Η απάντηση του ερωτήµατος για τις διεργασίες καπνιστών είναι λίγο πιο δύσκολη. Έστω
ότι οι τρεις διεργασίες καπνιστών είναι η 0, η 1 και η 2. Με βάση την εκφώνηση
συµπεραίνουµε ότι:
• Η διεργασία καπνιστή 0 θα πρέπει να απενεργοποιείται όσο δεν υπάρχουν χαρτί και
σπίρτα στο τραπέζι.
• Η διεργασία Β θα πρέπει να απενεργοποιείται όσο δεν υπάρχουν καπνός και σπίρτα
στο τραπέζι.
• Η διεργασία Γ θα πρέπει να απενεργοποιείται όσο δεν υπάρχουν καπνός και χαρτί
στο τραπέζι.
Εποµένως, χρειαζόµαστε άλλους τρεις σηµαφόρους, έναν για κάθε µια από τις διεργασίες
καπνιστών. Παρατηρήστε ότι εδώ απαιτούνται τρεις σηµαφόροι, ένας για κάθε διεργασία
καπνιστή, και όχι ένας (που θα χρησιµοποιείται από όλες τις διεργασίες καπνιστών),
όπως συνέβη µε τις διεργασίες πελατών στο πρόβληµα πελατών-εξυπηρέτη. Ο λόγος για
αυτό είναι ότι η συνθήκη που πρέπει να ισχύει προκειµένου να έχει νόηµα να
επανενεργοποιηθεί ένας απενεργοποιηµένος καπνιστής είναι διαφορετική για κάθε

2η έκδοση 104
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

καπνιστή (κάτι που δεν ίσχυε για τις διεργασίες πελατών, όπου οποιαδήποτε από τις
διεργασίες πελατών θα µπορούσε να είναι αυτή που θα παράγει την επόµενη αίτηση).
Στη συνέχεια δουλεύουµε µε ίδιο ακριβώς τρόπο όπως στην Ενότητα 3.7.1, ώστε να
καταλήξουµε στη λύση που παρουσιάζεται στο Σχήµα 55. Στη λύση αυτή, θεωρούµε ότι
η ρουτίνα DecideWhichMaterialsToSell() αποφασίζει µε τυχαίο τρόπο ποια δύο από τα
υλικά θα τοποθετηθούν κάθε φορά στο τραπέζι (µη κρίσιµο τµήµα διεργασίας πωλητή),
ενώ µέσω της ρουτίνας TakeMaterialsFromTable() ένας καπνιστής παίρνει τα υλικά από
το τραπέζι και τα χρησιµοποιεί (κρίσιµο τµήµα διεργασιών καπνιστών).
Κοινές µεταβλητές & Σηµαφόροι:
semaphore SmokerSem[3] = {0, 0, 0}; /* αρχικές τιµές 0 και για τους 3 σηµαφόρους */
semaphore SellerSem = 1;
shared variable table;
∆ιεργασία-Πωλητής ∆ιεργασία-Καπνιστής i, 0 <= i <= 2
repeat repeat
begin begin
DecideWhichMaterialsToSell(); down(SmokerSem[i]);
down(SellerSem); TakeMaterialsFromTable();
update table; up(SellerSem);
if (υλικά που τοποθετήθηκαν στο end
τραπέζι είναι χαρτί και σπίρτα) forever;
then up(SmokerSem[0]);
else if (υλικά που τοποθετήθηκαν στο
τραπέζι είναι καπνός και
σπίρτα)
then up(SmokerSem[1]);
else up(SmokerSem[2]);
end
forever;

Σχήµα 55: Λύση στο πρόβληµα των καπνιστών-πωλητή.

Παρατηρήστε ότι στη λύση του Σχήµατος 55 δεν χρησιµοποιείται κανένας δυαδικός
σηµαφόρος για επίτευξη αµοιβαίου αποκλεισµού, αφού αυτός επιτυγχάνεται λόγω του
τρόπου χρήσης των υπολοίπων σηµαφόρων. □

Άσκηση Αυτοαξιολόγησης 36 (Θέµα 4, 4η Γραπτή Εργασία, Ακ. Έτος 2003-2004)


Σας ζητείται να εξηγήσετε τι θα συµβεί όταν δύο διεργασίες Α και Β εκτελέσουν τον
κώδικα του Σχήµατος 56, δεδοµένου ότι τα SA και SB είναι σηµαφόροι των οποίων
ωστόσο δεν γνωρίζετε την αρχική τιµή. Ποιες προτείνετε να είναι οι αρχικές τιµές των
σηµαφόρων αυτών και γιατί;
∆ιεργασία Α ∆ιεργασία Β
repeat repeat
begin begin
down(SA); down(SB);
print “ping”; print “pong”;

2η έκδοση 105
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

up(SB); up(SA);
end end
forever; forever;

Σχήµα 56: Ping-Pong.

3.7.2 Το Πρόβληµα του Ζωολογικού Κήπου (Θέµα 6, Α’ Τελική Εξέταση,


Ιούνιος 2003)
Ένας ζωολογικός κήπος αποτελείται από το χώρο του µουσείου και ένα πάρκο στο οποίο
οι επισκέπτες µπορούν να περιηγηθούν χρησιµοποιώντας µικρά αυτοκίνητα του ενός
επιβάτη. Υπάρχουν µ αυτοκίνητα. Οι επιβάτες τριγυρνούν στο µουσείο για λίγη ώρα µε
τα πόδια και στη συνέχεια περιµένουν σε ουρά για να ανέβουν σε ένα αυτοκίνητο ώστε
να κάνουν µια βόλτα στο πάρκο. Όταν ένα αυτοκίνητο είναι διαθέσιµο, φορτώνει έναν
επιβάτη (χωρά µόνο έναν) και τριγυρνά στο πάρκο για ένα τυχαίο χρονικό διάστηµα. Αν
όλα τα αυτοκίνητα είναι κατειληµµένα από επιβάτες, τότε ένας επιβάτης που θέλει να
χρησιµοποιήσει ένα αυτοκίνητο πρέπει να περιµένει. Αν ένα αυτοκίνητο είναι έτοιµο να
φορτώσει αλλά δεν υπάρχουν επιβάτες τότε το αυτοκίνητο περιµένει.
∆ουλεύοντας όπως περιγράφτηκε στις προηγούµενες ενότητες, οδηγούµαστε στα εξής
συµπεράσµατα:
• Υπάρχουν δύο είδη διεργασιών στο σύστηµα, διεργασίες επιβατών και διεργασίες
αυτοκινήτων.
• Υπάρχουν δύο περιπτώσεις στις οποίες κάποιου είδους διεργασίες πρέπει να
απενεργοποιούνται:
o Μια διεργασία επιβάτης πρέπει να απενεργοποιείται αν δεν υπάρχουν διαθέσιµα
αυτοκίνητα.
o Μια διεργασία αυτοκίνητο θα πρέπει να απενεργοποιείται αν δεν υπάρχει
διαθέσιµος επιβάτης.
Χρειαζόµαστε εποµένως δύο σηµαφόρους. Έστω ότι ο σηµαφόρος πάνω στον οποίο
απενεργοποιούνται οι διεργασίες επιβατών ονοµάζεται CarSem (αφού µετράει τον
αριθµό των ελεύθερων αυτοκινήτων), ενώ ο σηµαφόρος πάνω στον οποίο
απενεργοποιούνται οι διεργασίες αυτοκινήτων ονοµάζεται PassengerSem (αφού
µετράει τον αριθµό των επισκεπτών που θέλουν να κάνουν βόλτα στο πάρκο). Ποιες
πρέπει να είναι οι αρχικές τιµές των σηµαφόρων; Αφού υπάρχουν µ αυτοκίνητα
διαθέσιµα, ο CarSem πρέπει να έχει αρχικά τιµή µ (ώστε οι πρώτες µ διεργασίες
επιβατών που θα επιτελέσουν µια λειτουργία down() στο σηµαφόρο αυτό να µην
απενεργοποιηθούν). Ο σηµαφόρος PassengerSem πρέπει να έχει αρχική τιµή 0
(θεωρούµε ότι αρχικά δεν υπάρχουν επιβάτες στο πάρκο που να ενδιαφέρονται να
χρησιµοποιήσουν αυτοκίνητα).
Θα πρέπει τώρα να αποφασιστεί αν κάθε είδος διεργασιών εκτελεί επαναληπτικά τις
ενέργειες που πρέπει να εκτελέσει ή όχι. Στο παράδειγµα µας, κάθε αυτοκίνητο εκτελεί
προφανώς επαναληπτικά ταξίδια στο πάρκο. Κάθε επιβάτης ωστόσο, ταξιδεύει στο
πάρκο µόνο µια φορά. Μια περιγραφή των ενεργειών που εκτελεί κάθε διεργασία
παρουσιάζεται στο Σχήµα 57.

2η έκδοση 106
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

∆ιεργασία Αυτοκίνητο
repeat
begin
1. Αν δεν υπάρχουν πελάτες απενεργοποιήσου;
2. Κάνε ταξίδι;
3. Ενηµέρωσε πελάτες ότι υπάρχει ένα ακόµη ελεύθερο αυτοκίνητο;
/* αυτό γίνεται αφού τελειώσει το ταξίδι */
end
forever;
∆ιεργασία Πελάτης
1. Αν δεν υπάρχει ελεύθερο αυτοκίνητο απενεργοποιήσου;
2. Ενηµέρωσε τα αυτοκίνητα ότι υπάρχει άλλος ένας διαθέσιµος πελάτης;
3. Κάνε ταξίδι;

Σχήµα 57: Περιγραφή απλών ενεργειών που εκτελούν οι διεργασίες επιβατών και αυτοκινήτων.

Η λύση παρουσιάζεται στο Σχήµα 58.


Κοινές µεταβλητές & Σηµαφόροι:
semaphore PassengerSem = 0;
semaphore CarSem = µ;
∆ιεργασία-Αυτοκίνητο ∆ιεργασία-Επιβάτης
repeat
begin
down(PassengerSem); down(CarSem);
take_trip(); up(PassengerSem);
up(CarSem); take_trip();
end
forever;

Σχήµα 58: Απλή λύση στο πρόβληµα του ζωολογικού κήπου.

Βελτιώσεις: Στη λύση του Σχήµατος 58 δεν θεωρήσαµε ότι η επιβίβαση στα αυτοκίνητα
και η αποβίβαση από αυτά θα πρέπει να γίνεται ατοµικά. Μήπως όµως θα ήταν καλύτερο
αν επιτευχθεί κάτι τέτοιο; Θεωρείστε ότι πολλοί επιβάτες αποφασίζουν ταυτόχρονα ότι
θέλουν να χρησιµοποιήσουν ένα από τα αυτοκίνητα. Προφανώς, αν διεκδικήσουν όλοι
µαζί το ίδιο αυτοκίνητο θα υπάρξει πρόβληµα. Έτσι, θα πρέπει να επιτευχθεί αµοιβαίος
αποκλεισµός κατά την επιβίβαση στο αυτοκίνητο. Οµοίως, κατά την αποβίβαση θα
πρέπει να είναι εγγυηµένη η επίτευξη αµοιβαίου αποκλεισµού, ώστε να µην γίνει
προσπάθεια από κάποια άλλη διεργασία επιβάτη να καταλάβει το όχηµα πριν ο
προηγούµενος επιβάτης εξέλθει αυτού. Για να γίνει πιο κατανοητή η ανάγκη αµοιβαίου
αποκλεισµού στο σηµείο αυτό, θεωρείστε ότι ο αριθµός των ελεύθερων αυτοκινήτων
είναι αποθηκευµένος σε µια διαµοιραζόµενη µεταβλητή Χ. Κάθε φορά που γίνεται
επιβίβαση σε ένα αυτοκίνητο, η τιµή της Χ πρέπει να µειωθεί κατά 1, ενώ κάθε φορά που
γίνεται αποβίβαση από ένα αυτοκίνητο, η τιµή της Χ πρέπει να αυξηθεί κατά 1.

2η έκδοση 107
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Χρειαζόµαστε εποµένως έναν δυαδικό σηµαφόρο mutex, µε αρχική τιµή 1, για την
επίτευξη του αµοιβαίου αποκλεισµού.
Η βελτιωµένη λύση παρουσιάζεται στο Σχήµα 59, όπου οι ρουτίνες ReserveCar () και
ReleaseCar() εµπεριέχουν τις ενέργειες κατάληψης και ελευθέρωσης, αντίστοιχα, ενός
αυτοκινήτου.
Κοινές µεταβλητές & Σηµαφόροι:
semaphore PassengerSem = 0;
semaphore CarSem = µ;
semaphore mutex = 1;
∆ιεργασία-Αυτοκίνητο ∆ιεργασία-Επιβάτης
repeat down(CarSem);
begin down(mutex);
down(PassengerSem); up(PassengerSem);
take_trip(); car_embarkation();
up(CarSem); up(mutex);
end take_trip();
forever; down(mutex);
leave_car();
up(mutex);

Σχήµα 59: Πρώτη βελτιωµένη λύση στο πρόβληµα του Ζωολογικού Κήπου.

Η λύση του Σχήµατος 59 επιδέχεται και µια ακόµη βελτίωση. Θα µπορούσε να


χρησιµοποιηθεί ένας ακόµη σηµαφόρος προκειµένου να επαφίεται στις διεργασίες
πελάτη να αποφασίζουν πότε ένα αυτοκίνητο παύει να χρησιµοποιείται πλέον και άρα
µπορεί να θεωρηθεί και πάλι ελεύθερο (προσέξτε ότι στις λύσεις των Σχηµάτων 60 και
61, αυτό αποφασίζεται από το ίδιο το αυτοκίνητο ανεξάρτητα από το αν ο πελάτης
θεωρεί ότι έχει ή όχι τελειώσει το ταξίδι του). Έστω EndOfTrip ο νέος σηµαφόρος. Η νέα
βελτιστοποιηµένη λύση παρουσιάζεται στο Σχήµα 60. Στη λύση αυτή ένα αυτοκίνητο
δεν θα επιτελέσει την εντολή up(CarSem) που σηµατοδοτεί ότι ένα ακόµη αυτοκίνητο
είναι διαθέσιµο αν κάποιος πελάτης δεν έχει τελειώσει τη βόλτα του (εκτελώντας την
εντολή up(EndOfTrip)).
Κοινές µεταβλητές & Σηµαφόροι:
semaphore PassengerSem = 0;
semaphore CarSem = µ;
semaphore mutex = 1;
semaphore EndOfTrip = 0;
∆ιεργασία-Αυτοκίνητο ∆ιεργασία-Επιβάτης
repeat down(CarSem);
begin down(mutex);
down(PassengerSem); up(PassengerSem);
take_trip(); car_embarkation();
down(EndOfTrip); up(mutex);
up(CarSem); take_trip();
end down(mutex);

2η έκδοση 108
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

forever; leave_car();
up(EndOfTrip);
up(mutex);

Σχήµα 60: ∆εύτερη βελτιωµένη λύση στο πρόβληµα του ζωολογικού κήπου.

Άσκηση Αυτοαξιολόγησης 37 – Το πρόβληµα του Παραγωγού - Καταναλωτή


Μια ενδιάµεση µνήµη µ θέσεων µπορεί να προσπελασθεί από δύο διεργασίες
ταυτόχρονα, µια διεργασία παραγωγό και µια διεργασία καταναλωτή. Η διεργασία
παραγωγός εκτελεί επαναληπτικά τα εξής. Παράγει ένα νέο δεδοµένο και το τοποθετεί
στην επόµενη διαθέσιµη θέση της κοινής ενδιάµεσης µνήµης. Όταν η µνήµη είναι γεµάτη
(περιέχει ήδη µ δεδοµένα), η διεργασία παραγωγός πρέπει να απενεργοποιείται µέχρι η
διεργασία καταναλωτής να αφαιρέσει από αυτή ένα ή περισσότερα δεδοµένα.
Αντίστοιχα, η διεργασία καταναλωτής εκτελεί επαναληπτικά τα εξής. Βγάζει από την
ενδιάµεση µνήµη ένα δεδοµένο και το επεξεργάζεται. Αν η µνήµη είναι άδεια
απενεργοποιείται µέχρι η διεργασία παραγωγός να τοποθετήσει σε αυτή ένα ή
περισσότερα δεδοµένα.
Θεωρείστε ότι η ρουτίνα insert_data() χρησιµοποιείται από τη διεργασία παραγωγό για
την τοποθέτηση ενός δεδοµένου στην κοινή µνήµη, ενώ η ρουτίνα delete_data()
χρησιµοποιείται από τη διεργασία καταναλωτή για την εξαγωγή ενός δεδοµένου από την
κοινή µνήµη. Προσέξτε ότι η κοινή µνήµη είναι ο διαµοιραζόµενος πόρος στο πρόβληµα
αυτό και άρα η εκτέλεση των ρουτινών insert_data() και delete_data() θα πρέπει να είναι
εγγυηµένο ότι γίνεται ατοµικά.
Παρουσιάστε λύση για το πρόβληµα του παραγωγού-καταναλωτή χρησιµοποιώντας
σηµαφόρους.

Άσκηση Αυτοαξιολόγησης 38 (Παραλλαγή Θέµατος 3, Εργασία 4, Ακ. Έτος 2003-2004)


Για την έκδοση εισιτηρίων σ’ ένα θέατρο υπάρχουν δύο ή περισσότερα ταµεία που
εξυπηρετούν τους θεατρόφιλους. Τα ταµεία µπορούν να εκτελούν παράλληλα, για
λογαριασµό του κάθε πελάτη, µια από τις ακόλουθες ρουτίνες:
• τη ρουτίνα reserve() για κράτηση εισιτηρίου και
• τη ρουτίνα cancel() για ακύρωση εισιτηρίου.
Στο σύστηµα µας µπορεί να έχουµε εποµένως πολλούς πελάτες που καθένας µπορεί να
εκτελεί µία από τις δύο αυτές ρουτίνες.
Κάθε διεργασία έχει πρόσβαση σε δύο κοινές µεταβλητές: free_positions και
available_positions. Η µεταβλητή free_positions αντιστοιχεί σε έναν µετρητή που δείχνει
το πλήθος των ελεύθερων θέσεων, ενώ η µεταβλητή available_positions, αναπαριστά
έναν πίνακα που δείχνει εάν µια θέση είναι κατειληµµένη ή όχι.
shared unsigned int free_positions;
shared boolean free_positions[N];
Οι διεργασίες κράτησης εισιτηρίου απενεργοποιούνται (περιµένοντας για κάποια
ακύρωση) όταν όλες οι θέσεις του θεάτρου είναι δεσµευµένες.

2η έκδοση 109
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Χρησιµοποιώντας σηµαφόρους επιλύστε το παραπάνω πρόβληµα συγχρονισµού.

3.7.3 Το Πρόβληµα του Κουρέα (Θέµα 3Β, Α’ Τελική Εξέταση, Μάιος 2001)
Ένα κουρείο αποτελείται από την "αίθουσα αναµονής" µε µ καρέκλες και το δωµάτιο
στο οποίο εργάζεται ο κουρέας που περιέχει την καρέκλα του κουρέα. Θεωρήστε πως
ένας πελάτης που εισέρχεται στο κουρείο περιµένει να εξυπηρετηθεί µόνο αν βρει κενή
καρέκλα στην αίθουσα αναµονής, αλλιώς φεύγει από το κουρείο. Αν δεν υπάρχουν
πελάτες να εξυπηρετηθούν, ο κουρέας πηγαίνει για ύπνο. Αν ένας πελάτης µπει στο
κουρείο και βρει τον κουρέα να κοιµάται τον ξυπνάει. Χρησιµοποιώντας σηµαφόρους,
ζητείται αλγόριθµος περιγραφής της λειτουργίας του κουρείου, ο οποίος θα συγχρονίζει
τον κουρέα µε τους πελάτες.
Ας µελετήσουµε πρώτα το εξής απλούστερο πρόβληµα. Ένα κουρείο απασχολεί ένα
κουρέα και έχει άπλετο χώρο για να περιµένουν οι πελάτες του. Αν κάποιος πελάτης βρει
τον κουρέα ελεύθερο εξυπηρετείται διαφορετικά περιµένει για να εξυπηρετηθεί. Αν δεν
υπάρχει πελάτης για εξυπηρέτηση ο κουρέας απενεργοποιείται. Αν ένας πελάτης βρει
τον κουρέα να κοιµάται, τον ξυπνάει.
Το πρόβληµα µπορεί εύκολα να µελετηθεί µε τον γνωστό τρόπο που παρουσιάστηκε στις
προηγούµενες ενότητες. Συνίσταται ισχυρά στον αναγνώστη να δοκιµάσει να δώσει λύση
στο απλό αυτό πρόβληµα χρησιµοποιώντας σηµαφόρους (πριν διαβάσει τη λύση που
περιγράφεται στη συνέχεια).
Η λύση στο απλό πρόβληµα φαίνεται στο Σχήµα 61.
Σηµαφόροι
semaphore CustomersSem = 0;
semaphore BarbersSem = 1;
∆ιεργασία-Κουρέας ∆ιεργασία-Πελάτης
repeat
begin
down(CustomersSem); up(CustomersSem);
cut_hair(); down(BarbersSem);
up(BarbersSem); get_haircut();
end
forever;

Σχήµα 61: Λύση στην απλή έκδοση του προβλήµατος του κουρέα.

Πριν συνεχίσει, ο αναγνώστης θα πρέπει να βεβαιωθεί πως έχει πειστεί ότι µια λύση
παρόµοια αυτής του Σχήµατος 61 θα είχε προκύψει, αν είχε ακολουθηθεί η γνωστή
διαδικασία επίλυσης του προβλήµατος (που παρουσιάζεται αναλυτικά στην Ενότητα
3.7.1). Αξίζει να τονιστεί πως η λύση του Σχήµατος 61 δεν είναι η µόνη σωστή λύση.
Στο Σχήµα 62 παρουσιάζεται µία ακόµη σωστή λύση.
Σηµαφόροι
semaphore CustomersSem = 0;
semaphore BarbersSem = 0;

2η έκδοση 110
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

∆ιεργασία-Κουρέας ∆ιεργασία-Πελάτης
repeat
begin
down(CustomersSem); up(CustomersSem);
up(BarbersSem); down(BarbersSem);
cut_hair(); get_haircut();
end
forever;

Σχήµα 62: Μία ακόµη σωστή λύση στην απλή έκδοση του προβλήµατος του κουρέα.

Θα προσπαθήσουµε τώρα να λύσουµε την πιο πολύπλοκη έκδοση του προβλήµατος που
περιγράφτηκε αρχικά. Κάθε πελάτης θα πρέπει τώρα να ελέγχει πόσοι πελάτες
περιµένουν ήδη στο κουρείο και αν αυτοί είναι περισσότεροι από µ θα πρέπει να
αποχωρεί. Παρατηρήστε ότι ο σηµαφόρος CustomersSem έχει τιµή ίση µε τον αριθµό
των πελατών σε αναµονή, αλλά αφού δεν επιτρέπεται να διαβάσουµε την τιµή ενός
σηµαφόρου, η πληροφορία αυτή δεν είναι δυνατόν να ληφθεί µέσω του σηµαφόρου.
Χρειαζόµαστε εποµένως µια διαµοιραζόµενη µεταβλητή που θα µετρά τον αριθµό των
πελατών σε αναµονή. Ας ονοµάσουµε την µεταβλητή αυτή cc. Κάθε πελάτης θα ελέγχει
αρχικά την cc και αν η τιµή της είναι µ θα αποχωρεί από το κουρείο. Στην αντίθετη
περίπτωση, θα αυξάνει την cc κατά ένα και θα απενεργοποιείται περιµένοντας σε κάποια
από τις διαθέσιµες καρέκλες για να κουρευτεί.
Η διεργασία κουρέας µειώνει την τιµή της cc κατά ένα κάθε φορά που παραλαµβάνει
έναν πελάτη για κούρεµα.
Η προσπέλαση στην cc (είτε για ανάγνωση ή για εγγραφή) θα πρέπει να γίνεται ατοµικά.
Χρησιµοποιούµε εποµένως έναν δυαδικό σηµαφόρο mutex προκειµένου να επιτευχθεί
αµοιβαίος αποκλεισµός.
Ο κώδικας φαίνεται στο Σχήµα 63.
Σηµαφόροι και Κοινές µεταβλητές
semaphore CustomersSem = 0;
semaphore BarbersSem = 1;
semaphore mutex = 1;
shared int cc; /* µε αρχική τιµή 0 */
∆ιεργασία-Κουρέας ∆ιεργασία-Πελάτης
repeat down(mutex);
begin if (cc < µ) then
down(CustomersSem); begin
down(mutex); up(CustomersSem);
cc = cc – 1; cc = cc + 1;
up(mutex); up(mutex);
cut_hair(); down(BarbersSem);
up(BarbersSem); get_haircut();
end end
forever; else up(mutex);

2η έκδοση 111
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Σχήµα 63: Λύση στο πρόβληµα του κουρέα.

3.7.4 Το Πρόβληµα των Αναγνωστών-Εγγραφέων


Θεωρείστε βάση δεδοµένων της οποίας τα δεδοµένα µπορούν να αναγνωσθούν ή να
τροποποιηθούν (εγγραφούν). Η ανάγνωση µπορεί να γίνεται ταυτόχρονα από πολλούς
αναγνώστες αλλά προκειµένου να γίνει εγγραφή δεν επιτρέπεται κανένας άλλος
αναγνώστης ή εγγραφέας να χρησιµοποιεί τη βάση (που είναι ο κοινός πόρος).
Παρουσιάστε λύση στο πρόβληµα του αµοιβαίου αποκλεισµού χρησιµοποιώντας
σηµαφόρους.
Προφανώς υπάρχουν δύο είδη διεργασιών στο σύστηµα, οι διεργασίες αναγνωστών και
οι διεργασίες εγγραφέων. Το πρόβληµα είναι αρκετά απλό. Αναγνώστες και εγγραφείς
πρέπει να προσβαίνουν ατοµικά τη βάση δεδοµένων. Απαιτείται εποµένως ένας
σηµαφόρος DataBase που θα χρησιµοποιηθεί για αµοιβαίο αποκλεισµό.
∆εν υπάρχουν άλλες καταστάσεις στις οποίες κάποιο είδος διεργασιών θα πρέπει να
απενεργοποιείται. Μια πρώτη προσπάθεια επίλυσης του προβλήµατος φαίνεται στο
Σχήµα 64.
Σηµαφόροι
semaphore DataBase = 1;
∆ιεργασία-Αναγνώστης ∆ιεργασία-Εγγραφέας
down(DataBase); down(DataBase);
read_data(); write_data();
up(DataBase); up(DataBase);

Σχήµα 64: Πρώτη προσπάθεια επίλυσης του προβλήµατος αναγνωστών-εγγραφέων.

Η λύση του Σχήµατος 64 επιτυγχάνει αµοιβαίο αποκλεισµό µεταξύ αναγνωστών και


εγγραφέων. Το πρόβληµα της λύσης αυτής είναι πως δεν επιτρέπει σε περισσότερους από
έναν αναγνώστες να διαβάζουν ταυτόχρονα. Προκειµένου να επιλύσουµε το πρόβληµα,
θα πρέπει να τροποποιήσουµε κατάλληλα τον κώδικα του αναγνώστη, ώστε µόνο ο
πρώτος αναγνώστης να εκτελεί την down(DataBase) προκειµένου να αποκλείσει
εγγραφείς αλλά όχι άλλους αναγνώστες από την ταυτόχρονη πρόσβαση στη βάση.
Επίσης, ο τελευταίος αναγνώστης που εγκαταλείπει τη βάση θα πρέπει να είναι αυτός
που θα εκτελέσει την up(DataBase) προκειµένου να επιτρέψει σε ενδεχόµενους
εγγραφείς (ή και σε νέους αναγνώστες) να προσπελάσουν τη βάση. Χρησιµοποιούµε
εποµένως µια κοινή µεταβλητή rc που µετρά τον αριθµό των αναγνωστών που διαβάζουν
την τρέχουσα χρονική στιγµή τη βάση. Αφού η rc είναι κοινή µεταβλητή, η προσπέλασή
της θα πρέπει να γίνεται ατοµικά. Απαιτείται εποµένως ένας δυαδικός σηµαφόρος rcSem.
Μια σωστή λύση στο πρόβληµα των αναγνωστών-εγγραφέων φαίνεται στο Σχήµα 65.
∆ιαµοιραζόµενες µεταβλητές και Σηµαφόροι
shared int rc; /* µε αρχική τιµή 0 */
semaphore rcSem; /* δυαδικός σηµαφόρος για αποκλειστική πρόσβαση στην rc */
semaphore DataBase; /* δυαδικός σηµαφόρος για αποκλειστική πρόσβαση στη βάση */

2η έκδοση 112
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

∆ιεργασία-Αναγνώστης ∆ιεργασία-Εγγραφέας
repeat
begin repeat
down(rcSem); begin
rc = rc + 1; down(DataBase);
if (rc == 1) down(DataBase); write_data();
up(rcSem); up(DataBase);
read_data(); end
down(rcSem); forever;
rc = rc – 1;
if (rc == 0) up(DataBase);
up(rcSem);
end
forever;

Σχήµα 65: Λύση στο πρόβληµα αναγνωστών-εγγραφέων.

Άσκηση Αυτοαξιολόγησης 39
Είναι η λύση του Σχήµατος 65 δίκαιη ή οδηγεί σε παρατεταµένη στέρηση; Τι θα συµβεί
αν καταφθάνουν διαρκώς αναγνώστες στο σύστηµα ενώ υπάρχουν εγγραφείς που
ενδιαφέρονται να ενηµερώσουν τη βάση;

Άσκηση Αυτοαξιολόγησης 40
Τροποποιήστε τον κώδικα του εγγραφέα, ώστε αν στο σύστηµα καταφθάσουν και άλλοι
εγγραφείς όσο κάποιος τροποποιεί τη βάση να είναι εγγυηµένο ότι θα τροποποιήσουν και
αυτοί τη βάση πριν κάποιος αναγνώστης ξεκινήσει ανάγνωση. Ο κώδικας του αναγνώστη
δεν θα πρέπει να αλλάξει µορφή, ενώ ο νέος κώδικας του εγγραφέα θα πρέπει να µοιάζει
περισσότερο στον κώδικα του αναγνώστη (µε την επιπρόσθετη µικρή δυσκολία ότι δεν
επιτρέπεται πολλοί εγγραφείς να προσπελάζουν τη βάση δεδοµένων ταυτόχρονα).

Άσκηση Αυτοαξιολόγησης 41 – Το πρόβληµα της Στενής Γέφυρας


Σας ζητείται να επιλύσετε το πρόβληµα συγχρονισµού της κυκλοφορίας µιας στενής
γέφυρας, στην οποία τα αυτοκίνητα µπορούν να κινούνται µόνο προς τη µία κατεύθυνση.
Τα αυτοκίνητα µπορούν να φθάνουν σε οποιοδήποτε από τα δύο άκρα της γέφυρας. Ένα
αυτοκίνητο που φθάνει στο ένα άκρο της γέφυρας εξετάζει αν υπάρχουν αυτοκίνητα που
διασχίζουν τη γέφυρα προς την αντίθετη κατεύθυνση. Αν συµβαίνει αυτό, το αυτοκίνητο
περιµένει. ∆ιαφορετικά, ξεκινά τη διάσχιση της γέφυρας.
Στο παραπάνω σύστηµα θεωρείστε ότι κάθε αυτοκίνητο αποτελεί µια διεργασία που όταν
φθάνει στη γέφυρα εκτελεί τη ρουτίνα Vehicle() που περιγράφεται στο Σχήµα 66.
void Vehicle(int direction)
begin
ArriveBridge(direction);
CrossBridge(direction);
ExitBridge(direction);
end

2η έκδοση 113
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Σχήµα 66: Σκελετός κώδικα διεργασίας αυτοκίνητου.

Στην παραπάνω ρουτίνα, η παράµετρος direction µπορεί να είναι 0 ή 1, υποδηλώνοντας


την κατεύθυνση του αυτοκινήτου.
Σας ζητείται να δώσετε ψευδοκώδικα για τις υπορουτίνες ArriveBridge() και
ExitBridge(). Θα πρέπει να χρησιµοποιήσετε σηµαφόρους προκειµένου να επιτύχετε
συγχρονισµό. Η υπορουτίνα ArriveBridge() θα πρέπει να επιστρέφει µόνο αν είναι
εγγυηµένο πως είναι ασφαλές για το αυτοκίνητο να περάσει τη γέφυρα (δηλαδή δεν
υπάρχει αυτοκίνητο που διασχίζει την γέφυρα προς την αντίθετη κατεύθυνση). Η
ExitBridge() πρέπει να παρέχει τις κατάλληλες ενέργειες, ώστε να είναι δυνατή η
διάσχιση της γέφυρας από νέα αυτοκίνητα.
Η λύση που θα δώσετε δεν απαιτείται να είναι δίκαιη. Ωστόσο, θα πρέπει να εξηγήσετε
αν η λύση σας παρέχει κάποιου είδους δικαιοσύνη.

Άσκηση Αυτοαξιολόγησης 42 – Το πρόβληµα της ∆ιάσχισης της Χαράδρας (Άσκηση


Αυτοαξιολόγησης 3.1 Τόµου Γ )
Ένα σχοινί είναι δεµένο από τη µια άκρη µιας χαράδρας ως την άλλη, έτσι ώστε όποιος
θέλει να διασχίσει τη χαράδρα να το κάνει κρεµασµένος από το σχοινί. Αν
χρησιµοποιήσουν το σχοινί άτοµα που κινούνται και προς τις δύο κατευθύνσεις, θα
δηµιουργηθεί πρόβληµα (αδιέξοδο) στο σηµείο συνάντησής τους πάνω από τη χαράδρα.
Εποµένως, όταν κάποιος θελήσει να διασχίσει τη χαράδρα, πρέπει πρώτα να ελέγχει αν
έρχεται άλλος από την αντίθετη κατεύθυνση. Επιλύστε το παραπάνω πρόβληµα
συγχρονισµού.

3.7.5 Για περισσότερη εξάσκηση


Άσκηση Αυτοαξιολόγησης 43 (Θέµα 4,4η Γραπτή Εργασία, Ακ. Έτος 2002-2003)
Ένα κολυµβητήριο µπορεί να δεχθεί µέχρι έναν συγκεκριµένο αριθµό κολυµβητών που
περιορίζεται από τον αριθµό Ε των ατοµικών ερµαριών που είναι διαθέσιµα για τα ρούχα
τους. Επιπλέον οι κολυµβητές ανταγωνίζονται για τη χρήση των ατοµικών αποδυτηρίων
(καµπινών) στα οποία αλλάζουν τα ρούχα τους και τα οποία είναι στον αριθµό συνολικά
A (το Α είναι αρκετά µικρότερο του E). Σηµειώνεται πως τα ερµάρια βρίσκονται σε
διαφορετικό φυσικό χώρο από τα αποδυτήρια.
Μόλις εισέλθει στο χώρο του κολυµβητηρίου, ένας υποψήφιος κολυµβητής θα πρέπει
πρώτα να βρει ένα άδειο ερµάρι για τα ρούχα του (το οποίο δεσµεύει π.χ. κλειδώνοντάς
το) και µετά ένα ελεύθερο αποδυτήριο για να αλλάξει και να φορέσει το µαγιό του.
Κατόπιν αφού τοποθετήσει τα ρούχα του στο ερµάρι θα ελευθερώσει το αποδυτήριο (το
ερµάρι το δεσµεύει για όλη τη διάρκεια της επίσκεψής του στο κολυµβητήριο) και
µπορεί να εισέλθει στο χώρο της πισίνας για να κολυµπήσει.
Κατά την έξοδό του θα ακολουθήσει ένα αντίστοιχο πρωτόκολλο. Θα πρέπει να βρει ένα
άδειο αποδυτήριο για να φορέσει τα ρούχα του και τελικά να ελευθερώσει το αποδυτήριο
και το ερµάρι που είχε δεσµεύσει.

2η έκδοση 114
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Το µοναδικό είδος διεργασιών στο σύστηµα αυτό είναι οι κολυµβητές, ενώ τα ερµάρια
και τα αποδυτήρια είναι οι πόροι του συστήµατος για τους οποίους ανταγωνίζονται οι
διεργασίες.
Η ανάλυση του προβλήµατος δείχνει πως κάθε διεργασία (δηλ. κάθε κολυµβητής) πρέπει
να εκτελέσει τρεις διαφορετικές ρουτίνες από τη στιγµή που θα εισέλθει στο
κολυµβητήριο έως τη στιγµή που θα εξέλθει. Αυτές οι ρουτίνες είναι :
ΆλλαξεΤαΡούχα();
Κολύµπησε();
ΆλλαξεΤοΜαγιό();
Επιλύστε το παραπάνω πρόβληµα συγχρονισµού χρησιµοποιώντας σηµαφόρους.

Άσκηση Αυτοαξιολόγησης 44 (Θέµα 6, Α’ Τελική Εξέταση, Ιούνιος 2004)


Για το συγχρονισµό πέντε διεργασιών P1, P2, P3, P4 και P5 θέλουµε να
χρησιµοποιήσουµε σηµαφόρους, έτσι ώστε η διεργασία P3 να ξεκινήσει τη δουλειά της
αφού έχουν ολοκληρώσει οι διεργασίες P1 και P2 τη δική τους, ενώ οι διεργασίες P4 και
P5 µπορούν να ξεκινήσουν µόνο µετά την ολοκλήρωση της P3. Υποθέτουµε ότι στην
αρχή όλες οι διεργασίες ξεκινούν ταυτόχρονα και ότι µετά την ολοκλήρωση
οποιασδήποτε διεργασίας αυτή µπορεί να εκτελείται ξανά από το ΛΣ ανεξάρτητα από
την πρόοδο των υπολοίπων.
Σας ζητείται να δώσετε µια λύση στο παραπάνω πρόβληµα συγχρονισµού
χρησιµοποιώντας τις εντολές down και up και τον κατάλληλο αριθµό σηµαφόρων. Στην
απάντησή σας να συµπεριλάβετε και την αρχικοποίηση των σηµαφόρων.
Σκιαγράφηση Λύσης: Αρκεί να προσδιορίσετε τις περιπτώσεις που οι διάφορες
διεργασίες απενεργοποιούνται:
• Η διεργασία P3 πρέπει να είναι απενεργοποιηµένη όσο εκτελείται η P1.
• Η διεργασία P3 πρέπει να είναι απενεργοποιηµένη όσο εκτελείται η P2.
• Η διεργασία P4 πρέπει να είναι απενεργοποιηµένη µέχρι να τελειώσει η εκτέλεση της
P3.
• Η διεργασία P5 πρέπει να είναι απενεργοποιηµένη µέχρι να τελειώσει η εκτέλεση της
P3.
Απαιτούνται εποµένως 4 σηµαφόροι. Ας τους ονοµάσουµε S1, S2, S3 και S4. Οι
σηµαφόροι πρέπει να έχουν αρχική τιµή 0 (αφού πρέπει να απενεργοποιηθούν πάνω τους
κάποιες διεργασίες).
Μια περιγραφή των ενεργειών των 5 διεργασιών φαίνεται στο Σχήµα 67.
∆ιεργασία P1 ∆ιεργασία P2
Εκτέλεσε τον κώδικα σου; Εκτέλεση τον κώδικά σου;
Ενηµέρωσε την P3 ότι τελείωσε η εκτέλεση; Ενηµέρωσε την P3 ότι τελείωσε η εκτέλεση;

∆ιεργασία P3 ∆ιεργασία P4
Περίµενε τον τερµατισµό της εκτέλεσης της P1; Περίµενε τον τερµατισµό εκτέλεσης της P3;

2η έκδοση 115
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Περίµενε τον τερµατισµό της εκτέλεσης της P2; Εκτέλεσε τον κώδικά σου;
Εκτέλεσε τον κώδικά σου;
Ενηµέρωσε την P4 ότι τελείωσε η εκτέλεσή σου;
Ενηµέρωσε την P5 ότι τελείωσε η εκτέλεσή σου;

∆ιεργασία P5
Περίµενε τον τερµατισµό εκτέλεσης της P3;
Εκτέλεσε τον κώδικά σου;

Σχήµα 67: Περιγραφή των ενεργειών των διεργασιών της Άσκησης Αυτοαξιολόγησης 44.

Βάσει της παραπάνω περιγραφής παρουσιάστε ψευδοκώδικα που να συµπεριλαµβάνει


όλες τις λειτουργίες up() και down() που πραγµατοποιούνται. □

Άσκηση Αυτοαξιολόγησης 45 (Θέµα 1, 5η Εργασία, Ακ. Έτος 2000-2001)


Ένας χώρος στάθµευσης αυτοκινήτων σε ένα νησί έχει χωρητικότητα Ν. Ένα πλοίο
χωρητικότητας Μ < Ν αυτοκινήτων εκτελεί δροµολόγια µεταφέροντας αυτοκίνητα από
την ηπειρωτική χώρα στο νησί και αντίστροφα. Το πλοίο αποπλέει όταν δεν υπάρχουν
αυτοκίνητα που περιµένουν για επιβίβαση (ακόµη και αν δεν είναι γεµάτο) δεδοµένου
ωστόσο ότι υπάρχουν αυτοκίνητα στον προορισµό. Το πλοίο αποπλέει επίσης όταν είναι
γεµάτο ή (στην περίπτωση που αποπλέει προς το νησί) αν ο αριθµός των αυτοκινήτων
έχει γεµίσει το χώρο στάθµευσης στο νησί.
Ζητείται λύση στο παραπάνω πρόβληµα συγχρονισµού µε χρήση σηµαφόρων. Η λύση
σας πρέπει να αποφεύγει την δηµιουργία αδιεξόδου ή παρατεταµένης στέρησης.
Σκιαγράφηση Λύσης: Υπάρχουν τρία είδη διεργασιών, µια διεργασία πλοίο, διεργασίες
αυτοκινήτων στο νησί και διεργασίες αυτοκινήτων στην ηπειρωτική χώρα. Ο κώδικας
της διεργασίας πλοίο διαφοροποιείται ανάλογα µε το αν το πλοίο βρίσκεται στο νησί ή
στην ηπειρωτική χώρα. Μια κοινή µεταβλητή, ας την ονοµάσουµε BoatLocation,
αποθηκεύει το που βρίσκεται το πλοίο την τρέχουσα στιγµή. Έστω ότι όταν έχει την τιµή
1 το πλοίο είναι στο νησί, ενώ όταν έχει την τιµή 0 το πλοίο είναι στην ηπειρωτική χώρα.
Τροποποιήσεις στην µεταβλητή BoatLocation γίνονται µόνο από τη διεργασία πλοίο
αλλά όλες οι διεργασίες µπορούν να διαβάζουν την τιµή της BoatLocation. Η πρόσβαση
στην µεταβλητή BoatLocation θα πρέπει να γίνεται ατοµικά (έστω lmutex ο δυαδικός
σηµαφόρος που εγγυάται αµοιβαίο αποκλεισµό κατά την πρόσβαση στη συγκεκριµένη
µεταβλητή).
Για την επίλυση του προβλήµατος χρειαζόµαστε δύο ακόµη κοινές µεταβλητές, µια που
θα µετρά τον αριθµό των αυτοκινήτων στο νησί που αναµένουν για επιβίβαση και µια
που θα µετρά τον αριθµό των αυτοκινήτων στην ηπειρωτική χώρα που αναµένουν για
επιβίβαση. Ας ονοµάσουµε τις µεταβλητές αυτές CarsOnIsland και CarsOnLand,
αντίστοιχα. Ας ονοµάσουµε επίσης CarsOnIslandSem και CarsOnLandSem του δύο
δυαδικούς σηµαφόρους που εγγυώνται αµοιβαίο αποκλεισµό κατά την πρόσβαση στις
δύο αυτές µεταβλητές.
Ποιες είναι οι περιπτώσεις που θα πρέπει κάποιο είδος διεργασιών να απενεργοποιείται;

2η έκδοση 116
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

• Μια διεργασία αυτοκίνητο στο νησί απενεργοποιείται αν το πλοίο δεν είναι στο νησί
και υπάρχει χώρος στο parking.
• Μια διεργασία αυτοκίνητο στην ηπειρωτική χώρα απενεργοποιείται αν το πλοίο είναι
στο νησί.
• Η διεργασία πλοίο δεν απενεργοποιείται ποτέ.
Απαιτούνται εποµένως δύο ακόµη σηµαφόροι.
Υποθέτουµε ότι τα αυτοκίνητα που βρίσκουν το χώρο στάθµευσης γεµάτο στο νησί
αποχωρούν από το σύστηµα.
Οι τρεις ρουτίνες που πρέπει να δηµιουργηθούν θα έχουν τη µορφή που παρουσιάζεται
στο Σχήµα 68. Θεωρείστε ότι οι OnIsland, OnLand είναι σταθερές που έχουν τις τιµές 0
και 1, αντίστοιχα.
∆ιεργασία Πλοίο
repeat
begin
if (BoatLocation == OnIsland) then
begin
Όσο (υπάρχουν αυτοκίνητα για επιβίβαση στο νησί και υπάρχει χώρος στο πλοίο):
Ενηµέρωσε ένα αυτοκίνητο ότι επιβιβάζεται;
Μείωσε κατά ένα τον αριθµό των αυτοκινήτων προς επιβίβαση;
Άλλαξε την τιµή της µεταβλητής BoatLocation;
end
else
begin
Όσο (υπάρχουν αυτοκίνητα για επιβίβαση στην ηπειρωτική χώρα και
υπάρχει χώρος στο πλοίο):
Ενηµέρωσε ένα αυτοκίνητο ότι επιβιβάζεται;
Μείωσε κατά ένα τον αριθµό των αυτοκινήτων προς επιβίβαση;
Αν (CarsOnIsland == Ν) (και άρα ο χώρος στάθµευσης έχει γεµίσει)
τερµάτισε την εκτέλεση της while
Άλλαξε την τιµή της µεταβλητής BoatLocation;
end
end
forever;

∆ιεργασία Αυτοκίνητο στην Ηπειρωτική Χώρα


Αύξησε την µεταβλητή CarsOnLand;
Αν το πλοίο δεν είναι στην ηπειρωτική χώρα απενεργοποιήσου;

∆ιεργασία Αυτοκίνητο στο Νησί


Αν η τιµή της CarsOnIsland είναι < Ν τότε:
Αύξησε την τιµή της κατά 1;
Αν το πλοίο δεν είναι στο νησί απενεργοποιήσου;
∆ιαφορετικά:
Αποχώρησε από το σύστηµα;

2η έκδοση 117
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Σχήµα 68: Σκελετός κώδικα διεργασιών για το πρόβληµα που περιγράφεται στην Άσκηση
Αυτοαξιολογόγησης 45.

Βάσει της παραπάνω λύσης παρουσιάστε ψευδοκώδικα που να συµπεριλαµβάνει όλες τις
λειτουργίες up() και down() που πραγµατοποιούνται. □

3.7.6 Γενικές Παρατηρήσεις


Συνοψίζουµε στη συνέχεια τα βήµατα που πρέπει να ακολουθηθούν προκειµένου να
επιλύσουµε ένα πρόβληµα συγχρονισµού.
1. Προσπαθούµε καταρχήν να συσχετίσουµε το πρόβληµα µε κάποιο από τα
προβλήµατα που µελετήσαµε ήδη. Τα περισσότερα προβλήµατα συγχρονισµού έχουν
παρόµοια λύση µε κάποιο από τα προβλήµατα που µελετήσαµε στις προηγούµενες
ενότητες.
2. Αν το πρόβληµα που µας δίνεται δεν µας θυµίζει κανένα από τα προβλήµατα που
έχουµε ήδη µελετήσει ακολουθούµε τη µεθοδολογία που περιγράφτηκε στην Ενότητα
3.7.1 (την οποία συνοψίζουµε στη συνέχεια).
3. Βρίσκουµε τα είδη των διεργασιών του προβλήµατος απαντώντας στο ερώτηµα τι
είδους ενέργειες µπορούν να συµβαίνουν ταυτόχρονα (ή τι είδους οντότητες µπορούν
να εκτελούνται ταυτόχρονα;)
4. Αποφασίζουµε ποιοι είναι οι κοινοί πόροι (π.χ., κοινές µεταβλητές) που θα
χρησιµοποιηθούν και χρησιµοποιούµε από έναν δυαδικό σηµαφόρο για την επίτευξη
αµοιβαίου αποκλεισµού στην πρόσβαση κάθε κοινού πόρου.
5. Αποφασίζουµε αν υπάρχουν και άλλες καταστάσεις (εκτός από αυτές που
καθορίζονται λόγω της ανάγκης επίτευξης αµοιβαίου αποκλεισµού) στις οποίες
κάποιο είδος διεργασιών πρέπει να απενεργοποιείται. Χρησιµοποιούµε έναν ακόµη
σηµαφόρο (όχι απαραίτητα δυαδικό) για κάθε µια από τις καταστάσεις αυτές.
6. Αποφασίζουµε ποιες θα είναι οι αρχικές τιµές των σηµαφόρων που θα
χρησιµοποιηθούν. Κάθε δυαδικός σηµαφόρος έχει αρχική τιµή 1.
7. Περιγράφουµε µε απλά λόγια τις ενέργειες που πρέπει να επιτελούν οι διάφορες
διεργασίες
8. Παρουσιάζουµε ψευδοκώδικα υλοποιώντας τις ενέργειες που περιγράφτηκαν στο
βήµα 7 χρησιµοποιώντας τους σηµαφόρους που αποφασίσαµε πως θα
χρησιµοποιήσουµε για την επίλυση του προβλήµατος.
9. Ελέγχουµε την ορθότητα του τελικού κώδικα και εξετάζουµε αν κάποιοι από τους
σηµαφόρους είναι περιττοί.

3.8 Κρίσιµες Περιοχές και Κρίσιµες Περιοχές Υπό Συνθήκη


Η έννοια της κρίσιµης περιοχής έχει συζητηθεί λεπτοµερώς και ο αναγνώστης θα πρέπει
να νιώθει πολύ εξοικειωµένος µε αυτήν. Έστω ένας κοινός πόρος u (π.χ., µια
διαµοιραζόµενη µεταβλητή). Ο πόρος µπορεί να χρησιµοποιείται ταυτόχρονα από πολλές

2η έκδοση 118
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

διεργασίες και άρα απαιτείται συγχρονισµός των διεργασιών στην προσπέλασή του. Η
γλωσσική έκφραση που περιγράφεται στο Σχήµα 69 ορίζει ότι το µπλοκ εντολών S
αποτελεί την κρίσιµη περιοχή του u. Η έκφραση χρησιµοποιήθηκε για πρώτη φορά από
τον Hansen (το 1972).
shared T u; /* διαµοιραζόµενη µεταβλητή u τύπου T */
region u do
begin
<µπλοκ εντολών S>;
end

Σχήµα 69: Γλωσσική Έκφραση του Hansen.

Όπως έχουµε ήδη δει, η εκτέλεση της κρίσιµης περιοχής µιας διεργασίας απαιτεί
αµοιβαίο αποκλεισµό. Το ερώτηµα πως θα υλοποιήσουµε κρίσιµες περιοχές (δηλαδή πως
θα επιλύσουµε το πρόβληµα του αµοιβαίου αποκλεισµού) έχει ήδη αναλυθεί αρκετά. Για
παράδειγµα, µια τέτοια υλοποίηση µπορεί να χρησιµοποιεί σηµαφόρους, ή την εντολή
Test&Set(), ή τη λύση του Peterson, κλπ. Στην ενότητα αυτή θα χρησιµοποιούµε την
γλωσσική έκφραση που περιγράφτηκε πιο πάνω, αφαιρετικά, για να καθορίσουµε ότι το
µπλοκ εντολών S πρέπει να εκτελεστεί ατοµικά. Το πως αυτό θα επιτευχθεί εξαρτάται
από το εκάστοτε σύστηµα και τα εργαλεία συγχρονισµού που παρέχει.
Παράδειγµα (Μέρος Θέµατος 2, Εργασία 4, Ακ. Έτος 2001-2002)
Περιγράψτε και πάλι τις ρουτίνες boolean deposit(int amount) και boolean withdraw(int
amount), που περιγράφονται στην Άσκηση Αυτοαξιολόγησης 8, χρησιµοποιώντας τη
γλωσσική έκφραση του Hansen για αµοιβαίο αποκλεισµό.
Οι µη-ατοµικές εκδόσεις των ρουτινών deposit() και withdraw() έχουν συζητηθεί στην
Ενότητα 3.2. ∆εδοµένου ότι τα κρίσιµα τµήµατα αυτών είναι γνωστά, ο ψευδοκώδικάς
παρουσιάζεται στο Σχήµα 70.
Κοινές µεταβλητές
shared int balance;
boolean deposit(int amount) boolean withdraw(int amount)
int tmp; /* τοπική µεταβλητή */ begin
region balance do
begin
begin
region balance do
tmp = balance;
begin
end
balance = balance + amount;
if (amount > tmp) then
print “Το νέο σας ποσό είναι”;
begin
print balance;
print “∆εν επιτρέπεται ανάληψη
end
τόσο µεγάλου ποσού!”;
return TRUE;
return FALSE;
end
end
else
begin
region balance do
begin

2η έκδοση 119
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

balance = balance – amount;


print “Το νέο σας ποσό είναι”;
print balance;
end
return TRUE;
end
end

Σχήµα 70: Ατοµική έκδοση των deposit() και withdraw() χρησιµοποιώντας τη γλωσσική έκφραση του
Hansen. □

Άσκηση Αυτοαξιολόγησης 46
Εξηγήστε πως υλοποιείται η γλωσσική έκφραση του Hansen χρησιµοποιώντας όλους
τους δυνατούς τρόπους συγχρονισµού που µελετήθηκαν σε προηγούµενες ενότητες. □

Θα δούµε στη συνέχεια µια ισχυρότερη γλωσσική έκφραση από εκείνη του Hansen, η
οποία επιτρέπει σε µια διεργασία να περιµένει έως µια αυθαίρετη συνθήκη πάνω στο
κοινό πόρο να ικανοποιηθεί. Η γλωσσική έκφραση περιγράφεται στη συνέχεια:
shared T u; /* η διαµοιραζόµενη µεταβλητή u είναι τύπου Τ */
region u do
begin
S0;
await B(u);
S1;
end

Σχήµα 71: Γλωσσική Έκφραση Κρίσιµης Περιοχής υπό Συνθήκη.

Η συνθήκη B(u) είναι µια οποιαδήποτε boolean έκφραση που αναφέρεται στον κοινό
πόρο u. Ο κώδικας µεταξύ των begin end εκτελείται ατοµικά. Αν η συνθήκη B(u) είναι
αληθής, η διεργασία εκτελεί το µπλοκ εντολών S1 και τερµατίζει την εκτέλεση του
κρίσιµου τµήµατός της. Ωστόσο, αν η συνθήκη B(u) δεν είναι αληθής, η διεργασία που
εκτελεί την await αναστέλλεται και µια άλλη διεργασία µπορεί να ξεκινήσει την
εκτέλεση της κρίσιµης περιοχής της (δηλαδή του κώδικα µεταξύ των begin end). Όταν
µια διεργασία τερµατίζει την εκτέλεση της κρίσιµης περιοχής, όλες οι διεργασίες που
είχαν ανασταλεί στην await επανενεργοποιούνται, έτσι ώστε η κάθε µια ατοµικά να
εξετάσει και πάλι τη συνθήκη B(u). Αξίζει να σηµειωθεί πως η συνθήκη µπορεί να είναι
τώρα αληθής αφού η διεργασία που µόλις τελείωσε το κρίσιµο τµήµα της µπορεί να
άλλαξε την τιµή του u. Θα πρέπει να τονιστεί πως ακόµη και αν όλες οι διεργασίες βρουν
τη συνθήκη αληθή, η εκτέλεση του S1 είναι εγγυηµένο πως θα γίνει ατοµικά. Το ίδιο
φυσικά ισχύει και για το µπλοκ εντολών S0.
Η γλωσσική έκφραση του Σχήµατος 71 είναι ισχυρότερη από εκείνη του Σχήµατος 69
αφού, επιπρόσθετα του αµοιβαίου αποκλεισµού, µπορεί να χρησιµοποιηθεί για την
υλοποίηση όλων εκείνων των καταστάσεων που µία ή περισσότερες διεργασίες πρέπει να
ανασταλούν περιµένοντας µέχρι κάποια συνθήκη να είναι αληθής. Η γλωσσική έκφραση

2η έκδοση 120
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

κρίσιµων περιοχών υπό συνθήκη µπορεί εποµένως να χρησιµοποιηθεί για την επίλυση
σύνθετων προβληµάτων συγχρονισµού, όπως αυτά που µελετήθηκαν στην Ενότητα 3.7.

Παράδειγµα 9 – Πρόβληµα Ζωολογικού Κήπου


Ας επανέλθουµε στο πρόβληµα του Ζωολογικού Κήπου που µελετήθηκε στην Ενότητα
3.7.2. Ζητείται λύση στο πρόβληµα χρησιµοποιώντας κρίσιµες περιοχές υπό συνθήκη αντί
για σηµαφόρους.
Ο τρόπος εργασίας µας είναι αντίστοιχος µε εκείνον όταν χρησιµοποιούνται σηµαφόροι.
Ωστόσο, για κάθε περίπτωση στην οποία κάποιου είδους διεργασίες πρέπει να
απενεργοποιούνται, θα υπάρχει τώρα µια διαµοιραζόµενη µεταβλητή, που θα ονοµάζεται
µεταβλητή συνθήκης, και όχι ένας σηµαφόρος. Επίσης, δεν θα χρησιµοποιούµε
σηµαφόρους για αµοιβαίο αποκλεισµό, αφού η ίδια η γλωσσική έκφραση τον εγγυάται.
Για το πρόβληµα του ζωολογικού κήπου θα χρησιµοποιήσουµε εποµένως δύο
διαµοιραζόµενες µεταβλητές, µία που θα µετράει τον αριθµό των διαθέσιµων
αυτοκινήτων (ας την ονοµάσουµε Cars) και µία που θα µετρά τον αριθµό των επιβατών
(ας την ονοµάσουµε Passengers) στο σύστηµα. Η Cars έχει αρχική τιµή µ, ενώ η
Passengers έχει αρχική τιµή 0 (παρατηρήστε την αντιστοιχία που υπάρχει µεταξύ των
µεταβλητών Cars και Passengers και των σηµαφόρων CarSem και PassengerSem,
αντίστοιχα).
Η ανάλυση των ενεργειών που εκτελεί κάθε είδος διεργασιών γίνεται µε τον ίδιο ακριβώς
τρόπο όπως και για τους σηµαφόρους .
Μια απλή λύση φαίνεται στο Σχήµα 72.
Κοινές µεταβλητές
shared record zoo
begin
int Passengers;
int Cars;
end;
shared record zoo b; /* µε αρχική τιµή 0 για το πεδίο b.Passengers και µ για το πεδίο b.Cars */
∆ιεργασία Αυτοκίνητο ∆ιεργασία Επιβάτης
repeat repeat
begin begin
region b do region b do
begin begin
await b.Passengers > 0; await b.Cars > 0;
b.Passengers = b.Passengers – 1; b.Cars = b.Cars - 1;
end b.Passengers = b.Passengers +1;
take_trip(); end
region b do take_trip();
begin end
b.Cars = b.Cars + 1; forever;
end
end
forever;

2η έκδοση 121
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Σχήµα 72: Λύση στο πρόβληµα του ζωολογικού κήπου µε χρήση κρίσιµων περιοχών υπό συνθήκη
(αντίστοιχη εκείνης του Σχήµατος 58).

∆ιαισθητικά, για να µετατρέψουµε µια λύση βασισµένη σε σηµαφόρους, σε µια λύση


βασισµένη σε κρίσιµες περιοχές υπό συνθήκη, κάνουµε τα εξής. Για κάθε σηµαφόρο S,
που δεν χρησιµοποιείται απλά για αποκλειστική πρόσβαση σε κάποιο κοινό πόρο,
χρησιµοποιούµε µια µεταβλητή συνθήκης V. Κάθε εντολή down(S) αντικαθίσταται µε
µια εντολή await σε κάποια boolean συνθήκη της V που ακολουθείται από µια µείωση
κατά ένα της τιµής της V. Κάθε εντολή up(S) αντικαθίσταται µε µια εντολή αύξησης
κατά ένα της τιµής της V. Όλες οι παραπάνω ενέργειες πρέπει να αποτελούν το µπλοκ
εντολών µίας (ή και περισσότερων) εκφράσεων region.
Η λύση του Σχήµατος 72 είναι αντίστοιχη (δηλαδή παρουσιάζει τα προτερήµατα και
µειονεκτήµατα) εκείνης του Σχήµατος 58. Ας προσπαθήσουµε να παρουσιάσουµε πιο
βελτιωµένες λύσεις, όπως αυτές που περιγράφονται στα Σχήµατα 59 και 60.
Η λύση του Σχήµατος 59 χρησιµοποιεί έναν ακόµη σηµαφόρο για επίτευξη αµοιβαίου
αποκλεισµού. Η έκφραση region παρέχει αυτόµατα αµοιβαίο αποκλεισµό, και άρα δεν
χρειαζόµαστε παραπάνω µεταβλητές συνθήκης. Η αντίστοιχη λύση παρουσιάζεται στο
Σχήµα 73.
Κοινές µεταβλητές
shared record zoo
begin
int Passengers;
int Cars;
end;
shared record zoo b; /* µε αρχική τιµή 0 για το πεδίο b.Passengers και µ για το πεδίο b.Cars */
∆ιεργασία Αυτοκίνητο ∆ιεργασία Επιβάτης
repeat repeat
begin begin
region b do region b do
begin begin
await b.Passengers > 0; await b.Cars > 0;
b.Passengers = b.Passengers – 1; b.Cars = b.Cars - 1;
end b.Passengers = b.Passengers +1;
take_trip(); car_embarkation();
region b do end
begin take_trip();
b.Cars = b.Cars + 1; region b do
end begin
end leave_car();
forever; end
end
forever;

Σχήµα 73: Πρώτη βελτιωµένη λύση στο πρόβληµα του ζωολογικού κήπου µε χρήση κρίσιµων περιοχών
υπό συνθήκη (αντίστοιχη εκείνης του Σχήµατος 59).

2η έκδοση 122
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Η λύση του Σχήµατος 60 χρησιµοποιεί έναν ακόµη σηµαφόρο, τον EndOfTrip.


Χρειαζόµαστε εποµένως µια ακόµη µεταβλητή συνθήκης (ας την ονοµάσουµε επίσης
EndOfTrip) µε αρχική τιµή 0 (όπως και η αρχική τιµή του σηµαφόρου). Ο κώδικας
φαίνεται στο Σχήµα 74.
Κοινές Μεταβλητές
shared record zoo
begin
int Passengers;
int Cars;
boolean EndOfTrip;
end;
shared record zoo b; /* µε αρχική τιµή 0 για τα πεδία b.Passengers και b.EndOfTrip
και αρχική τιµή µ για το πεδίο b.Cars */
∆ιεργασία Αυτοκίνητο ∆ιεργασία Επιβάτης
repeat repeat
begin begin
region b do region b do
begin begin
await b.Passengers > 0; await b.Cars > 0;
b.Passengers = b.Passengers – 1; b.Cars = b.Cars - 1;
end b.Passengers = b.Passengers +1;
take_trip(); car_embarkation();
region b do end
begin take_trip();
await b.EndOfTrip > 0; region b do
b.EndOfTrip = b.EndOfTrip – 1; begin
b.Cars = b.Cars + 1; leave_car();
end b.EndOfTrip = 1;
end end
forever; end
forever;

Σχήµα 74: ∆εύτερη βελτιωµένη λύση στο πρόβληµα του Ζωολογικού Κήπου µε χρήση κρίσιµων περιοχών
υπό συνθήκη (αντίστοιχη εκείνης του Σχήµατος 60).

Παρατήρηση: Στις λύσεις που παρουσιάζονται στα Σχήµατα 72, 73 και 74 δεν είναι
δυνατόν να προσπελάζονται δύο διαφορετικές κοινές µεταβλητές ταυτόχρονα. Αυτό
οφείλεται στο γεγονός ότι επιλέχθηκε οι κοινές µεταβλητές που χρησιµοποιούνται σε
κάθε µια από τις υλοποιήσεις αυτές να αποτελούν πεδία µιας µεταβλητής b τύπου struct
zoo στην οποία αναφέρονται όλες οι εντολές region που χρησιµοποιούνται. Εποµένως
στις λύσεις αυτές ο κοινός πόρος είναι η µεταβλητή b και η region δεν επιτρέπει την
προσπέλαση κανενός πεδίου της b από πολλές διεργασίες ταυτόχρονα.
Η λύση που παρουσιάζεται στο Σχήµα 75 είναι αντίστοιχη της λύσης του Σχήµατος 74
αλλά επιτυγχάνει µεγαλύτερο παραλληλισµό (βάσει όσων συζητηθήκαν παραπάνω).
Κοινές Μεταβλητές

2η έκδοση 123
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

shared int Passengers; /* µε αρχική τιµή 0 */


shared int Cars; /* µε αρχική τιµή µ */
shared boolean EndOfTrip; /* µε αρχική τιµή 0 */
∆ιεργασία Αυτοκίνητο ∆ιεργασία Επιβάτης
repeat repeat
begin begin
region Passengers do region Cars do
begin begin
await Passengers > 0; await Cars > 0;
Passengers = Passengers – 1; Cars = Cars - 1;
end end
take_trip(); region Passengers do
region EndOfTrip do begin
begin Passengers = Passengers +1;
await EndOfTrip > 0; car_embarkation();
EndOfTrip = EndOfTrip – 1; end
end take_trip();
region Cars do region EnfOfTrip do
begin begin
Cars = Cars + 1; leave_car();
end EndOfTrip = 1;
end end
forever; end
forever;

Σχήµα 75: Λύση αντίστοιχη εκείνης του Σχήµατος 74 αλλά που επιτυγχάνει µεγαλύτερο παραλληλισµό. □

Παράδειγµα 10 – Πρόβληµα Αναγνωστών - Εγγραφέων


Ας συζητήσουµε τώρα πως µπορούµε να επιλύσουµε ένα ακόµη πρόβληµα, εκείνο των
αναγνωστών-εγγραφέων, χρησιµοποιώντας κρίσιµες περιοχές υπό συνθήκη.
Μια πρώτη προσπάθεια επίλυσης του προβλήµατος, αντίστοιχη εκείνης του Σχήµατος 64
φαίνεται στο Σχήµα 76.
Κοινές Μεταβλητές
shared int b;
∆ιεργασία-Αναγνώστης ∆ιεργασία-Εγγραφέας
region b do region b do
begin begin
read_data(); write_data();
end end

Σχήµα 76: Πρώτη προσπάθεια επίλυσης του προβλήµατος αναγνωστών-εγγραφέων µε χρήση κρίσιµων
περιοχών υπό συνθήκη.

Η παραπάνω λύση, όπως και η λύση του Σχήµατος 64, παρουσιάζει το πρόβληµα ότι
µόνο ένας αναγνώστης µπορεί να διαβάζει κάθε φορά δεδοµένα από τη βάση. Για να
επιλύσουµε το πρόβληµα αυτό, θα εισαγάγουµε µια µεταβλητή συνθήκης, την DataBase

2η έκδοση 124
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

(που θα παίζει το ρόλο του σηµαφόρου DataBase). Η DataBase θα έχει αρχική τιµή 1. Θα
χρησιµοποιηθεί επίσης η κοινή µεταβλητή rc µε αρχική τιµή 0. Ο κώδικας φαίνεται στο
Σχήµα 77.
Κοινές Μεταβλητές
shared record RW
begin
shared int rc;
shared boolean DataBase;
end
shared record RW b; /* µε αρχική τιµή 0 για το πεδίο b.rc και αρχική τιµή 1 για το πεδίο
b.DataBase */
∆ιεργασία-Αναγνώστης ∆ιεργασία-Εγγραφέας
region b do region b do
begin begin
if (b.rc == 0) then await b.DataBase > 0;
begin b.DataBase = b.DataBase – 1;
await b.DataBase > 0; write_data();
b.DataBase = b.DataBase - 1; b.DataBase = b.DataBase + 1;
end end
b.rc = b.rc + 1;
end
read_data();
region b do
begin
b.rc = b.rc – 1;
if (b.rc == 0) then
b.DataBase = b.DataBase + 1;
end

Σχήµα 77: Σωστή λύση του προβλήµατος αναγνωστών-εγγραφέων µε χρήση κρίσιµων περιοχών υπό
συνθήκη.

Ο πρώτος αναγνώστης που θα εισέλθει στο σύστηµα θα εξετάσει την τιµή της
b.DataBase. Αν είναι 1, θα την αλλάξει σε 0. Επόµενοι αναγνώστες δεν θα εκτελούν την
if και άρα θα µπορούν να εκτελεστούν ταυτόχρονα. Όσοι εγγραφείς έρχονται στο
σύστηµα ενόσω κάποιοι αναγνώστες διαβάζουν δεδοµένα της βάσης, βρίσκουν την
b.DataBase να έχει τιµή 0 και µπλοκάρουν στην await. Ο τελευταίος αναγνώστης θα
αλλάξει την τιµή της b.DataBase σε 1 προκειµένου να δοθεί η δυνατότητα σε εγγραφείς
(ή και σε αναγνώστες που µπορεί να εισέλθουν αργότερα στο σύστηµα) να προσβούν τη
βάση.
Αν ένας αναγνώστης εισέλθει στο σύστηµα ενόσω ένας εγγραφέας τροποποιεί τη βάση, ο
αναγνώστης θα βρει την b.DataBase να έχει τιµή 0 και θα µπλοκάρει στην await. Το ίδιο
θα συµβεί µε όλους τους αναγνώστες που θα εισέλθουν στο σύστηµα πριν ο εγγραφέας
τελειώσει τις τροποποιήσεις του στη βάση. Παρατηρήστε ότι αυτό δεν θα συνέβαινε αν η
αύξηση της rc προηγείτο της await και για το λόγο αυτό η αύξηση της rc συµβαίνει τώρα
µετά την await. Θυµηθείτε ότι η αύξηση της rc προηγείται της down(DataBase) στον

2η έκδοση 125
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

κώδικα του Σχήµατος 65. Η διαφορά µεταξύ των δύο κωδίκων είναι πως αν µια
διεργασία µπλοκάρει σε κάποια await, η region επιτρέπει σε άλλη διεργασία να εκτελέσει
το µπλοκ εντολών της. Αυτό δεν ισχύει στην περίπτωση που χρησιµοποιούνται
σηµαφόροι, όπου όλοι οι αναγνώστες, εκτός από τον πρώτο, θα µπλοκάρουν στην
down(rcSem) και κανένας άλλος δεν θα εκτελέσει την συνθήκη της if πριν να
επανενεργοποιηθεί ο πρώτος και εκτελέσει την up(rcSem).

Άσκηση Αυτοαξιολόγησης 47
Επιλύστε όλα τα προβλήµατα που συζητήθηκαν στην Ενότητα 3.7 χρησιµοποιώντας
κρίσιµες περιοχές υπό συνθήκη αντί για σηµαφόρους.

3.9 Λίγο πιο ∆ύσκολα

3.9.1 Λύση του Lamport (Bakery Algorithm)


Στην ενότητα αυτή παρουσιάζεται µια δίκαιη λύση στο πρόβληµα του αµοιβαίου
αποκλεισµού για n διεργασίες 0, ... , n-1. Η λύση προτάθηκε από τον Lamport και
ονοµάζεται Αλγόριθµος του Αρτοπωλείου (Bakery Algorithm). Αν ο Lamport ήταν
Έλληνας, πιθανότατα θα τον είχε ονοµάσει αλγόριθµο της τράπεζας γιατί υιοθετεί το
σύστηµα εξυπηρέτησης πελατών σε µια τράπεζα (φαίνεται πως το ίδιο σύστηµα
υιοθετείται στο εξωτερικό σε αρτοπωλεία).
Μια διεργασία που θέλει να εισέλθει στο κρίσιµο τµήµα της, επιλέγει ένα εισιτήριο. Στο
κρίσιµο τµήµα εισέρχεται κάθε φορά η διεργασία µε το µικρότερο εισιτήριο ανάµεσα σε
εκείνες που επιθυµούν να εισέλθουν στο κρίσιµο τµήµα τους.
Το εισιτήριο µιας διεργασίας είναι ένα ζεύγος αριθµών. Ο πρώτος από τους αριθµούς
είναι ένας ακέραιος που επιλέγεται από τη διεργασία και ο δεύτερος είναι το
αναγνωριστικό της διεργασίας. Ορίζουµε τη σχέση < ανάµεσα σε δύο ζεύγη αριθµών ως
εξής:
(x,y) < (v,w) αν και µόνο αν( x < v) ή (x = v και y < w).
Ο αλγόριθµος του Αρτοπωλείου παρουσιάζεται στο Σχήµα 78.
Κοινές Μεταβλητές
boolean choosing[n];
int number[n];
Κώδικας για τη διεργασία i
choosing[i] = TRUE;
number[i] = max{number[1], ... , number[n-1]} + 1;
choosing[i] = FALSE;
for j = 0 to n-1 do
begin
while (choosing[j] == TRUE) do noop;
while ((number[j] != 0) AND ((number[i], i) > (number[j], j))) do noop;
end
<κρίσιµο τµήµα>;

2η έκδοση 126
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

number[i] = 0;
<µη-κρίσιµο τµήµα>;

Σχήµα 78: Ο αλγόριθµος του Αρτοπωλείου.

Κάθε διεργασία i, 0 ≤ i < n, επιλέγει αρχικά έναν αριθµό. Η διεργασία διαβάζει τους
αριθµούς όλων των άλλων διεργασιών και επιλέγει ο αριθµός της να είναι το µέγιστο των
αριθµών αυτών συν ένα. Προσέξτε ότι αυτό το κοµµάτι κώδικα µπορεί να εκτελεστεί
ταυτόχρονα από περισσότερες της µιας διεργασίες και άρα είναι πιθανό περισσότερες
από µια διεργασίες να πάρουν τον ίδιο αριθµό. Αφού τα εισιτήρια πρέπει να είναι
µοναδικά, οι διεργασίες χρησιµοποιούν ως εισιτήριο το ζεύγος που αποτελείται από τον
αριθµό που διάλεξαν (που δεν είναι µοναδικός) και το αναγνωριστικό τους (που είναι
µοναδικό). Έτσι, το εισιτήριο κάθε διεργασίας είναι τελικά µοναδικό. Όσο κάθε
διεργασία i επιλέγει τον αριθµό της, η µεταβλητή choosing[i] έχει την τιµή TRUE.
Όταν µια διεργασία i επιλέξει εισιτήριο εκτελεί τα εξής. Για κάθε άλλη διεργασία j, αν η
j έχει δηλώσει την πρόθεση της να επιλέξει αριθµό (έχοντας θέσει τη µεταβλητή
choosing[j] στην τιµή TRUE), η i περιµένει την j να τελειώσει µε την επιλογή του
αριθµού της. Στη συνέχεια, ελέγχει αν το εισιτήριο της j είναι µεγαλύτερο από εκείνο της
i. Αν δεν ισχύει αυτό, η i θα πρέπει να περιµένει την j να εισέλθει πριν από αυτήν στο
κρίσιµο τµήµα. Αν το εισιτήριο της i βρεθεί να είναι το µικρότερο από τα εισιτήρια των
υπολοίπων διεργασιών που ενδιαφέρονται να εισέλθουν στο κρίσιµο τµήµα τους, η i
αποφασίζει πως είναι η σειρά της να εισέλθει στο κρίσιµο τµήµα.
Όταν η i εξέλθει από το κρίσιµο τµήµα µεταβάλλει την τιµή της number[i] σε 0 για να
υποδηλώσει πως δεν εκτελεί πλέον το κρίσιµο τµήµα της.
Για να γίνει κατανοητό ότι ο παραπάνω αλγόριθµος επιτυγχάνει αµοιβαίο αποκλεισµό, ο
αναγνώστης θα πρέπει να παρατηρήσει τα εξής:
• Αν µια διεργασία i βρίσκεται στο κρίσιµο τµήµα της, ισχύει ότι (number[i] != 0).
• Αν µια διεργασία i βρίσκεται στο κρίσιµο τµήµα της ισχύει ότι (number[i],i) <
(number[j],j), για κάθε 0 ≤ j < n, i != j, τέτοιο ώστε Number[j] != 0.
Ο αλγόριθµος ικανοποιεί επίσης τη συνθήκη της προόδου. Η διεργασία µε το µικρότερο
κάθε φορά εισιτήριο δεν µπορεί να αποτραπεί από το να εισέλθει στο κρίσιµο τµήµα της.
Επίσης, διεργασίες που εκτελούν το µη-κρίσιµο τµήµα τους δεν αποτρέπουν άλλες από
το να εισέλθουν στο κρίσιµο τµήµα τους (αφού ισχύει ότι η µεταβλητή number για αυτές
είναι 0).
Τέλος, ο αλγόριθµος ικανοποιεί τη συνθήκη της αποφυγής παρατεταµένης στέρησης.
Όταν µια διεργασία i επιλέξει αριθµό, οι άλλες διεργασίες δεν µπορούν να εισέλθουν στο
κρίσιµο τµήµα τους παραπάνω από µια φορά πριν η i εισέλθει στο κρίσιµο τµήµα της.
Συνίσταται ισχυρά στον αναγνώστη να προσπαθήσει να κατανοήσει γιατί συµβαίνει αυτό
(η σηµαντική παρατήρηση είναι ότι αν µια διεργασία j πάρει εισιτήριο ενόσω µια άλλη
διεργασία i έχει number[i] != 0 τότε ισχύει number[j] > number[i]).

2η έκδοση 127
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

3.9.2 Λύση του Dekker


Στο Σχήµα 79 παρουσιάζεται µια ακόµη δίκαιη λύση στο πρόβληµα του αµοιβαίου
αποκλεισµού που παρουσιάστηκε από τον Dekker. Η λύση είναι σωστή για δύο
διεργασίες, αλλά µπορεί να γενικευτεί και για n > 2 διεργασίες.
Κοινές Μεταβλητές
shared int flag[2]; /* µε αρχική τιµή TRUE ή FALSE, δεν παίζει ρόλο */
shared int priority; /* µε αρχική τιµή 0 ή 1, δεν παίζει ρόλο */
∆ιεργασία 0 ∆ιεργασία 1
repeat repeat
begin begin
flag[0] = TRUE; flag[1] = TRUE;
while (flag[1] == TRUE) do while (flag[0] == TRUE) do
begin begin
if (priority == 1) then if (priority == 0) then
begin begin
flag[0] = FALSE; flag[1] = FALSE;
while (priority == 1) do while (priority == 0) do
noop; noop;
flag[0] = TRUE; flag[1] = TRUE;
end end
else noop; else noop;
end end

<κρίσιµο τµήµα>; <κρίσιµο τµήµα>;

priority = 1; priority = 0;
flag[0] = FALSE; flag[1] = FALSE;

<µη-κρίσιµο τµήµα>; <µη-κρίσιµο τµήµα>;


end end
forever; forever;

Σχήµα 79: Η λύση του Dekker.

Ο αλγόριθµος χρησιµοποιεί τη διαµοιραζόµενη µεταβλητή priority που σηµατοδοτεί ποια


από τις δύο διεργασίες έχει προτεραιότητα πρόσβασης στο κρίσιµο τµήµα. Η τιµή της
priority εναλλάσσεται αυστηρά από 0 σε 1 και πάλι σε 0, κ.ο.κ. Χρησιµοποιείται επίσης
µια µεταβλητή flag για κάθε µια από τις διεργασίες. Κάθε διεργασία δηλώνει την
πρόθεση της να εισέλθει στο κρίσιµο τµήµα θέτοντας την τιµή της µεταβλητής flag που
της αντιστοιχεί σε TRUE. Αν και οι δύο διεργασίες επιθυµούν να εισέλθουν στο κρίσιµο
τµήµα, θα εισέλθει αυτή που έχει την προτεραιότητα µε το µέρος της (δηλαδή θα
εισέλθει η διεργασία i για την οποία ισχύει priority == i).
Ο αλγόριθµος επιτυγχάνει αµοιβαίο αποκλεισµό. Αν η διεργασία 0 δει στο flag[1] την
τιµή FALSE, η διεργασία 1 θα δει στο flag[0] την τιµή TRUE και δεν θα εισέλθει στο
κρίσιµο τµήµα της, και αντίστροφα. Αν η διεργασία 0 και η διεργασία 1 αλλάξουν τη
µεταβλητή flag τους σε 1 ταυτόχρονα, η µία από τις δύο (εκείνη που δεν έχει την

2η έκδοση 128
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

προτεραιότητα) θα εκτελέσει το µπλοκ εντολών της if που περιέχεται στην while.


Εποµένως θα αλλάξει το flag της σε FALSE, επιτρέποντας στην άλλη να εισέλθει στο
κρίσιµο τµήµα της, ενώ στη συνέχεια θα την περιµένει µέχρι να εξέλθει από αυτό.
Ο αλγόριθµος ικανοποιεί τη συνθήκη της προόδου. Ας υποθέσουµε (για να καταλήξουµε
σε άτοπο) ότι κάποια χρονική στιγµή και οι δύο διεργασίες είναι µπλοκαρισµένες στον
κώδικα εισόδου και καµία δεν µπορεί να εισέλθει στο κρίσιµο τµήµα της. Ας
υποθέσουµε επίσης ότι την χρονική στιγµή αυτή ισχύει ότι priority == 0 (η περίπτωση
στην οποία ισχύει priority == 1 είναι συµµετρική). Η διεργασία 0 δεν µπορεί να είναι
µπλοκαρισµένη στην εσωτερική while και άρα θα πρέπει να είναι µπλοκαρισµένη στην
εξωτερική while (βλέποντας διαρκώς flag[1] == 1). Ωστόσο, η διεργασία 1 θα εκτελέσει
το µπλοκ εντολών της if, θα αλλάξει το flag της σε 0 και θα µπλοκάρει στην εσωτερική
while. Όταν συµβεί αυτό, η συνθήκη της εξωτερικής while δεν θα είναι TRUE για την
διεργασία 0, η οποία τότε θα εισέλθει στο κρίσιµο τµήµα της. Η υπόθεση µας ότι και οι
δύο διεργασίες είναι µπλοκαρισµένες στον κώδικα εισόδου δεν µπορεί εποµένως να
είναι σωστή. Άρα, ο αλγόριθµος ικανοποιεί τη συνθήκη της προόδου.
Αφήνεται στον αναγνώστη να αποδείξει ότι ο αλγόριθµος του Dekker ικανοποιεί και την
ισχυρότερη συνθήκη δικαιοσύνης της αποφυγής παρατεταµένης στέρησης.

Άσκηση Αυτοαξιολόγησης 48 (Άσκηση Αυτοαξιολόγησης 3.5, Τόµος Γ, Λειτουργικά


Συστήµατα Ι)

Προσθέστε σε κάθε γραµµή ψευδοκώδικα του Σχήµατος 79 σχόλια του τύπου:


flag[0] = TRUE; /* Η διαδικασία 0 δηλώνει την πρόθεση της να χρησιµοποιήσει τον πόρο */
while (flag[1] == TRUE) do /* Όσο η διαδικασία 1 ενδιαφέρεται για τον πόρο */
...
Ποιες τιµές πρέπει να έχουν οι µεταβλητές flag[0], flag[1] και priority όταν η 0
χρησιµοποιεί τον πόρο και ποιες όταν τον χρησιµοποιεί η 1; Ποιες τιµές µπορούν να
έχουν οι µεταβλητές αυτές όταν δεν χρησιµοποιεί καµία διεργασία τον πόρο;
Περιγράψτε τον αλγόριθµο σε πρώτο πρόσωπο από την πλευρά της διεργασίας, π.χ.:
∆ηλώνω την πρόθεση µου για να πάρω τον πόρο;
Όσο η άλλη διεργασία ενδιαφέρεται να χρησιµοποιήσει τον πόρο:
Αν δεν έχω προτεραιότητα:
Αλλάζω τη πρόθεση µου για χρήση του πόρου προκειµένου να επιτρέψω
στην άλλη διεργασία να τον χρησιµοποιήσει;
Όσο δεν έχω προτεραιότητα περιµένω;
...

3.9.3 ∆ικαιοσύνη
Πιο ∆ίκαιες Λύσεις µε τη Βοήθεια του Υλικού (Παραλλαγή Μέρους Άσκησης
Αυτοαξιολόγησης 3.19)

2η έκδοση 129
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Όπως είδαµε στην Άσκηση Αυτοαξιολόγησης 9 της Ενότητας 3.4, η λύση του
προβλήµατος του αµοιβαίου αποκλεισµού µε χρήση της Test&Set() που παρουσιάστηκε
στο Σχήµα 20 µπορεί να οδηγήσει σε παρατεταµένη στέρηση. Στην ενότητα αυτή θα
χρησιµοποιήσουµε την Test&Set() για να σχεδιάσουµε λύση στο πρόβληµα του
αµοιβαίου αποκλεισµού που θα ικανοποιεί επιπρόσθετα και τη συνθήκη αποφυγής
παρατεταµένης στέρησης. Η λύση παρουσιάζεται στο Σχήµα 80.
Κοινές µεταβλητές
boolean waiting[n]; /* µε αρχική τιµή FALSE για όλα τα στοιχεία */
boolean lock; /* µε αρχική τιµή FALSE */
Κώδικας ∆ιεργασίας pi
int j; /* τοπική µεταβλητή */
boolean tmp; /* τοπική µεταβλητή */
repeat
begin
waiting[i] = TRUE;
tmp = TRUE;
while (waiting[i] == TRUE AND tmp == TRUE) do
tmp = Test&Set(lock);
waiting[i] = FALSE;
<κρίσιµο τµήµα>;
j = (i+1) mod n;
while (j != i AND (not waiting[j])) do j = (j+1) mod n;
if (j == i) then lock = FALSE;
else waiting[j] = FALSE;
<µη-κρίσιµο τµήµα>;
end
forever;

Σχήµα 80: ∆ίκαιη λύση στο πρόβληµα του αµοιβαίου αποκλεισµού µε χρήση Test&Set().

Σε κάθε διεργασία pi αντιστοιχεί µια διαµοιραζόµενη boolean µεταβλητή waiting[i]. Αν η


pi ενδιαφέρεται να εισέλθει στο κρίσιµο τµήµα της θέτει την waiting[i] σε TRUE. Στη
συνέχεια, η διεργασία καλεί την Test&Set() µε παράµετρο τη διαµοιραζόµενη µεταβλητή
lock που έχει αρχική τιµή FALSE. Αφού η Test&Set() εκτελείται ατοµικά, στην πρώτη
διεργασία, έστω k, που θα εκτελέσει την Test&Set() θα επιστραφεί η τιµή FALSE. Σε
όλες τις άλλες διεργασίες θα επιστραφεί TRUE. Έτσι, µόνο η κ θα εισέλθει στο κρίσιµο
τµήµα της και όλες οι άλλες θα µπλοκάρουν στην while του κώδικα εισόδου.
Παρατηρήστε ότι µια διεργασία i µπορεί να εισέλθει στο κρίσιµο τµήµα της είτε επειδή η
Test&Set() της επέστρεψε FALSE ή επειδή κάποια άλλη διεργασία άλλαξε την
µεταβλητή waiting[i] σε FALSE.
Όταν η διεργασία k εξέλθει από το κρίσιµο τµήµα της ψάχνει να βρει µια διεργασία που
επιθυµεί να εισέλθει στο κρίσιµο τµήµα. Αν καµία δεν επιθυµεί να εισέλθει, τότε αλλάζει
απλά το lock σε FALSE (για να δώσει τη δυνατότητα σε κάποια από τις διεργασίες που
θα επιθυµήσουν στο µέλλον να εισέλθουν να τα καταφέρει). Αν όµως υπάρχουν κάποιες

2η έκδοση 130
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

διεργασίες που βρίσκονται σε αναµονή (δηλαδή έχουν θέσει την µεταβλητή τους waiting
σε TRUE) τότε διαλέγει εκείνη τη διεργασία της οποίας το αναγνωριστικό είναι το πρώτο
που έπεται του k (ας ονοµάσουµε τη διεργασία αυτή j) και αλλάζει την τιµή της
waiting[j] σε FALSE. Αυτό έχει σαν αποτέλεσµα, την επόµενη φορά που η j θα
δροµολογηθεί, να αποτιµήσει την συνθήκη της while του κώδικα εισόδου σε FALSE και
θα εισέλθει στο κρίσιµο τµήµα της.

Άσκηση Αυτοαξιολόγησης 49

Προσθέστε σχόλια σε κάθε γραµµή ψευδοκώδικα του Σχήµατος 80. Ποιες τιµές έχουν οι
µεταβλητές που χρησιµοποιούνται από τον αλγόριθµο όταν κάποια διεργασία i βρίσκεται
στο κρίσιµο τµήµα της; Περιγράψτε τον αλγόριθµο σε πρώτο πρόσωπο από την πλευρά
της διεργασίας.

Άσκηση Αυτοαξιολόγησης 50
Περιγράψτε σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που να ικανοποιεί
επιπρόσθετα και τη συνθήκη αποφυγής παρατεταµένης στέρησης, χρησιµοποιώντας την
εντολή Fetch&Add() που περιγράφτηκε στο Σχήµα 26.

Άσκηση Αυτοαξιολόγησης 51
Περιγράψτε σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που να ικανοποιεί
επιπρόσθετα και τη συνθήκη αποφυγής παρατεταµένης στέρησης, χρησιµοποιώντας την
εντολή Swap() που περιγράφτηκε στο Σχήµα 27.

Άσκηση Αυτοαξιολόγησης 52
Περιγράψτε σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που να ικανοποιεί
επιπρόσθετα και τη συνθήκη αποφυγής παρατεταµένης στέρησης, χρησιµοποιώντας την
εντολή RMW() που περιγράφτηκε στο Σχήµα 28.

Πιο ∆ίκαιη Λύση στο Πρόβληµα των Αναγνωστών και Εγγραφέων


Οι λύσεις στο πρόβληµα των αναγνωστών και εγγραφέων που παρουσιάζονται στην
Ενότητα 3.7.4 οδηγούν σε παρατεταµένη στέρηση. Στην ενότητα αυτή θα περιγράψουµε
λύση η οποία θα αποφεύγει την παρατεταµένη στέρηση. Αν υπάρχει επιθυµία και από
εγγραφείς και από αναγνώστες να προσπελάσουν τη βάση ταυτόχρονα, θα την
προσπελάζουν εναλλάξ, ένας προς έναν, π.χ., πρώτα ένας αναγνώστης, στη συνέχεια ένας
εγγραφέας, στη συνέχεια και πάλι ένας αναγνώστης, κ.ο.κ. Η λύση φαίνεται στο Σχήµα
81.
Κοινές Μεταβλητές και Σηµαφόροι
shared int rc, wc; /* µε αρχική τιµή 0 */
shared boolean priority; /* µε αρχική τιµή 0 ή 1 */
semaphore cSem; /* δυαδικός σηµαφόρος για αποκλειστική πρόσβαση στις rc, wc */
semaphore DataBase; /* δυαδικός σηµαφόρος για αποκλειστική πρόσβαση στη βάση */
∆ιεργασία Αναγνώστης ∆ιεργασία Εγγραφέας
while (TRUE) do while (TRUE) do

2η έκδοση 131
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

begin begin
down(cSem); down(cSem);
rc = rc + 1; wc = wc + 1;
up(cSem); up(cSem);
down(DataBase); down(DataBase);
if (priority == 0) then if (priority == 1) then
begin begin
read_data(); wite_data();
down (cSem); down (cSem);
rc = rc - 1; wc = wc - 1;
if (wc != 0 ) priority = 1; if (rc != 0 ) priority = 0;
up(cSem); up(cSem);
end end
else else
begin begin
down(cSem); down(cSem);
if (wc == 0) priority = 0; if (rc == 0) priority = 1;
rc = rc -1; wc = wc -1;
up(cSem); up(cSem);
end end
up(DataBase); up(DataBase);
end end

Σχήµα 81: ∆ίκαιη λύση στο πρόβληµα των αναγνωστών-εγγραφέων.

Η µεταβλητή priority σηµατοδοτεί αν είναι η σειρά ενός αναγνώστη ή ενός εγγραφέα να


προσπελάσει τη βάση. Κάθε αναγνώστης (εγγραφέας) που εξέρχεται της βάσης, αλλάζει
το priority, ώστε να δώσει προτεραιότητα σε κάποιο εγγραφέα (αναγνώστη, αντίστοιχα)
να την προσπελάσει στη συνέχεια. Αν ένας αναγνώστης παρατηρήσει ότι η
προτεραιότητα είναι µε το µέρος των εγγραφέων αλλά δεν υπάρχουν εγγραφείς που να
ενδιαφέρονται να προσπελάσουν τη βάση, τότε ο αναγνώστης αλλάζει το priority ώστε
να είναι µε το µέρος του. Έτσι, στην αµέσως επόµενη ανακύκλωση κάποιος αναγνώστης
θα καταφέρει να προσπελάσει τη βάση. Αντίστοιχες ενέργειες εκτελούνται και από τους
εγγραφείς.

Άσκηση Αυτοαξιολόγησης 53
Παρουσιάστε λύση που να αποφεύγει το πρόβληµα της παρατεταµένης στέρησης για το
πρόβληµα της στενής γέφυρας.

Άσκηση Αυτοαξιολόγησης 54
Παρουσιάστε λύση που να αποφεύγει το πρόβληµα της παρατεταµένης στέρησης για το
πρόβληµα της διάσχισης της χαράδρας.

3.9.4 Προσοµοιώσεις
Σε µια προσοµοίωση δίνεται κάποιο εργαλείο συγχρονισµού και ζητείται να
χρησιµοποιηθεί για να υλοποιηθεί κάποιο άλλο εργαλείο συγχρονισµού. Η πρώτη επαφή
µε προσοµοιώσεις έγινε στις Ασκήσεις Αυτοαξιολόγησης 30 -32, όπου ζητήθηκε να

2η έκδοση 132
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

υλοποιηθούν σηµαφόροι χρησιµοποιώντας είτε την εντολή Test&Set() ή λύσεις µέσω


λογισµικού του προβλήµατος του αµοιβαίου αποκλεισµού.
Στην ενότητα αυτή θα επιλύσουµε ένα λίγο πιο δύσκολο πρόβληµα. Θεωρείστε ότι το ΛΣ
ενός συστήµατος υποστηρίζει δυαδικούς σηµαφόρους, αλλά δεν παρέχει γενικούς
σηµαφόρους. Έτσι, παρότι το σύστηµα παρέχει τα βασικά εργαλεία για επίτευξη
αµοιβαίου αποκλεισµού, η επίλυση κάποιων προβληµάτων συγχρονισµού δεν είναι
εύκολη υπόθεση.
Υποθέστε ότι η εταιρεία στην οποία εργάζεστε σας αναθέτει να υλοποιήσετε τις ρουτίνες
gsem_down() και gsem_up() που υποστηρίζει µια µεταβλητή τύπου γενικής σηµαφόρου.
Προκειµένου να το επιτευχθεί αυτό, οποιοσδήποτε αριθµός δυαδικών σηµαφόρων µπορεί
να χρησιµοποιηθεί.
Η εργασία που πρέπει να επιτελέσουµε είναι η εξής. Πρέπει να σχεδιάσουµε προσεκτικά
κώδικα για τις λειτουργίες gsem_down() και gsem_up(), έτσι ώστε όταν καλείται
οποιαδήποτε από τις λειτουργίες αυτές, οι ενέργειες που θα πραγµατοποιούνται να είναι
οι αναµενόµενες µε βάση τον ορισµό των λειτουργιών αυτών.
Ας δούµε µερικά βασικά χαρακτηριστικά των γενικών σηµαφόρων:
1. Ένας γενικός σηµαφόρος µπορεί να έχει οποιαδήποτε τιµή ≥ 0 (ενώ ένας δυαδικός
σηµαφόρος µπορεί να έχει µόνο τις τιµές 0 ή 1).
2. Μια διεργασία που εκτελεί µια λειτουργία gsem_down() σε κάποιο γενικό σηµαφόρο
θα πρέπει να µπλοκάρει αν ο σηµαφόρος έχει τιµή ≤ 0, ενώ θα πρέπει να µειώνει την
τιµή του κατά 1 αν αυτή είναι > 0.
3. Μια διεργασία που εκτελεί µια λειτουργία gsem_up() θα πρέπει να αυξάνει την τιµή
του σηµαφόρου κατά 1. Αν η τιµή του σηµαφόρου ήταν ίση µε 0 και υπήρχαν
µπλοκαρισµένες διεργασίες στον σηµαφόρο, µία από αυτές θα επανεργοποιηθεί µετά
την up() και θα καταφέρει να τερµατίσει την gsem_down() στην οποία είχε
µπλοκάρει.
Για να επιλύσουµε το πρόβληµα θα δουλέψουµε περίπου µε τον ίδιο τρόπο όπως όταν
επιλύουµε προβλήµατα συγχρονισµού.
Οι ενέργειες που µπορούν να εκτελούνται παράλληλα είναι στην περίπτωση µας οι
λειτουργίες gsem_down() και gsem_up(). Θα πρέπει να παρουσιάσουµε κώδικα για αυτές
τις δύο λειτουργίες.
Αφού ένας δυαδικός σηµαφόρος δεν µπορεί να έχει άλλες τιµές εκτός από τις τιµές 0 και
1, χρειαζόµαστε µια διαµοιραζόµενη µεταβλητή cnt που θα αποθηκεύει την τιµή του
γενικού σηµαφόρου. Πρόσβαση στην cnt πρέπει να γίνεται ατοµικά. Χρησιµοποιούµε
εποµένως έναν δυαδικό σηµαφόρο mutex για να το επιτύχουµε.
Θα πρέπει να αναρωτηθούµε τώρα αν υπάρχουν περιπτώσεις στις οποίες διεργασίες που
εκτελούν την µια ή την άλλη λειτουργία θα πρέπει να απενεργοποιούνται:
• Μια διεργασία που εκτελεί την gsem_down() θα πρέπει να απενεργοποιείται αν η
τιµή του σηµαφόρου είναι ≤ 0.

2η έκδοση 133
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Χρειαζόµαστε εποµένως και έναν ακόµη δυαδικό σηµαφόρο, ας τον ονοµάσουµε block,
πάνω στον οποίο θα µπλοκάρουν, όταν χρειάζεται, οι διεργασίες που εκτελούν την
gsem_down().
Ας προσπαθήσουµε τώρα να περιγράψουµε µε λόγια ποιες είναι οι ενέργειες που επιτελεί
κάθε µια από τις λειτουργίες αυτές. Η περιγραφή αυτή φαίνεται στο Σχήµα 82.
∆υαδικοί Σηµαφόροι και Κοινές Μεταβλητές
semaphore mutex;
semaphore block;
int cnt = s; /* όπου s είναι η αρχική τιµή που θα θέλαµε να έχει ο γενικός σηµαφόρος */
Λειτουργία gsem_down() Λειτουργία gsem_up()
Έλεγξε την τιµή της µεταβλητής cnt; Αύξησε την τιµή της cnt κατά 1;
Μείωσε την τιµή της cnt κατά 1; Αν η τιµή της cnt ήταν ≤ 0 ενηµέρωσε τις
Αν η τιµή της cnt είναι < 0 απενεργοποιήσου; διεργασίες που επιτελούν την gsem_down()
ώστε κάποια από αυτές να µπορέσει να
επανενεργοποιηθεί;

Σχήµα 82: Ενέργειες που επιτελούν οι λειτουργίες sem_down() και gsem_up().

Ο τελικός κώδικας φαίνεται στο Σχήµα 83.


∆υαδικοί Σηµαφόροι και Κοινές Μεταβλητές
semaphore mutex; /* µε αρχική τιµή 1 */
semaphore block; /* µε αρχική τιµή 0 */
int cnt = s; /* όπου s είναι η αρχική τιµή που θα θέλαµε να έχει ο γενικός σηµαφόρος */
Λειτουργία gsem_down() Λειτουργία gsem_up()
down(mutex);
cnt = cnt -1; down(mutex);
if (cnt < 0) then cnt = cnt + 1;
begin if (cnt ≤ 0) then
up(mutex); up(block);
down(block); up(mutex);
end
else up(mutex);

Σχήµα 83: Υλοποίηση γενικού σηµαφόρου από δυαδικό σηµαφόρο.

Είναι αξιοσηµείωτο ότι η cnt χρησιµοποιείται επιπρόσθετα για να µετρά τον αριθµό των
διεργασιών που έχουν απενεργοποιηθεί πάνω στο γενικό σηµαφόρο. Η πληροφορία αυτή
παρέχεται από την τιµή |cnt| όταν cnt < 0.

Άσκηση Αυτοαξιολόγησης (Άσκηση Αυτοαξιολόγησης 3.18, Τόµου Γ)


Εξηγήστε γιατί οι gsem_down() και gsem_up() λειτουργούν σωστά. Τι ρόλο παίζει κάθε
µια από τις δυαδικές σηµαφόρους;
Τι θα συµβεί αν αντιµεταθέσουµε τη σειρά των εντολών:

2η έκδοση 134
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

up(mutex);
down(block);
στον κώδικα του gsem_down();
Εξηγήστε πως εξασφαλίζεται ο αµοιβαίος αποκλεισµός όταν τα παρακάτω ζευγάρια
διεργασιών εκτελούνται παράλληλα:
α) ∆ύο διεργασίες που καλούν ταυτόχρονα την gsem_down();
β) ∆ύο διεργασίες που καλούν ταυτόχρονα την gsem_up();
γ) ∆ύο διεργασίες που η µία καλεί την gsem_down() και η άλλη καλεί την gsem_up();

Άσκηση Αυτοαξιολόγησης (Θέµα 2, Ερώτηµα 2, 4η Γραπτή Εργασία, Ακ. Έτος 2003-


2004)
Όπως αναφέρθηκε στην Ενότητα 3.8, µια κρίσιµη περιοχή υπό συνθήκη (σελ. 65 του
τόµου ΛΣ, ενότητα 3.7.4) ορίζεται µε την γλωσσική έκφραση:
shared T u;
region u do
begin
S0;
await B(u);
S1;
end
Σε ένα ΛΣ που υποστηρίζει σηµαφόρους θα θέλαµε να υλοποιήσουµε τo υψηλότερου
επιπέδου εργαλείο συγχρονισµού της κρίσιµης περιοχής υπό συνθήκη. Να
συµπληρώσετε το σχέδιο ψευδοκώδικα που παρέχεται στο Σχήµα 84 για µια τέτοια
υλοποίηση.
Σηµαφόροι και Κοινές Μεταβλητές
semaphore mutex;
semaphore delay;
integer process_count;
shared T u;
Αρχικοποίηση
mutex:=???, delay:=???, process_count:=???;
Κώδικας που εκτελεί κάθε διεργασία
begin
down(mutex);
process_count := process_count + 1;
S0;
while not B(u) do
begin

end
process_count := process_count – 1;

2η έκδοση 135
3ο Κεφάλαιο ∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

S1;

up(mutex);
end
Σχήµα 84: Σκελετός κώδικα για την υλοποίηση κρίσιµων περιοχών υπό συνθήκη χρησιµοποιώντας
σηµαφόρους.

3.10 Βιβλιογραφικές Αναφορές


Η επίτευξη αµοιβαίου αποκλεισµού είναι ένα από τα θεµελιώδη προβλήµατα του
κατανεµηµένου υπολογισµού. Μέχρι σήµερα, έχει δηµοσιευθεί πληθώρα ερευνητικών
εργασιών στο πρόβληµα αυτό (µια επισκόπηση των πιο πρόσφατων σηµαντικότερων
αποτελεσµάτων παρουσιάζεται στην εργασία [AKH03]), ενώ εξακολουθεί να γίνεται
έρευνα πάνω στο πρόβληµα και να γράφονται συγγράµµατα [T06] των οποίων µεγάλο
µέρος είναι αφιερωµένο στο πρόβληµα αυτό και σε παραλλαγές του. Παλαιότερα
ερευνητικά αποτελέσµατα παρουσιάζονται στο βιβλίο [R86].
Οι αλγόριθµοι και τα θέµατα που περιγράφονται στο παρόν εκπαιδευτικό υλικό είναι
κλασικά και πολλά από αυτά συζητούνται στα περισσότερα βιβλία των λειτουργικών
συστηµάτων [BH73, BH03, G00, SGG07, St03, TanI], του κατανεµηµένου υπολογισµού
[AW98, HS07, Lyn96, T07, T94], του παράλληλου και πολυνηµατικού
προγραµµατισµού [A00, BA90, S92] και των κατανεµηµένων συστηµάτων [CDK01,
TanS06].
Το πρόβληµα του αµοιβαίου αποκλεισµού συζητείται για πρώτη φορά στην κλασική
εργασία του Dijkstra [D65I]. Στην εργασία αυτή περιγράφονται και κάποιες από τις
λανθασµένες προτεινόµενες λύσεις που συζητήθηκαν στην Ενότητα 3.5. Ο αλγόριθµος
για δύο διεργασίες που περιγράφεται στην Ενότητα 3.9.2 παρουσιάστηκε από τον Dekker
[D65II]. O Dijkstra [D65I] επέκτεινε τον αλγόριθµο αυτό προτείνοντας τον πρώτο
αλγόριθµο αµοιβαίου αποκλεισµού για n διεργασίες. Ο αλγόριθµος του Peterson
περιγράφεται στην εργασία [P81], ενώ ο αλγόριθµος του αρτοπωλείου προτάθηκε από
τον Lamport [L74].
Κάποια από τα προβλήµατα διαδιεργασιακής επικοινωνίας που µελετήθηκαν στην
Ενότητα 3.7, όπως π.χ., το πρόβληµα των καπνιστών, το πρόβληµα του κουρέα και το
πρόβληµα των αναγνωστών-εγγραφέων παρουσιάζονται στις εργασίες [CHP97, D65II,
D71, Τα προβλήµατα αυτά αναφέρονται στα περισσότερα βιβλία των ΛΣ [SGG07,
ST03, TanI] και του κατανεµηµένου υπολογισµού [HS07, Lyn96,T07]. Το πρόβληµα
συγχρονισµού µε το σκύλο και τη γάτα που περιγράφεται στις ασκήσεις
αυτοαξιολόγησης 15, 16 και 17 συζητείται λεπτοµερώς στο [HS07].
Η έννοια της κρίσιµης περιοχής και του σηµαφόρου προτάθηκε από τον Dijkstra στην
εργασία [DS65II]. Η έννοια της κρίσιµης περιοχής και η γλωσσική έκφραση «region u do
S» έχουν προταθεί και συζητηθεί από τον Brinch Hansen [ΒΗ72Ι, BH72II].

2η έκδοση 136
Παράρτηµα Ι
Μορφή Ψευδοκώδικα Περιγραφής Προγράµµατος
∆ιεργασίας

Ο αναγνώστης θα πρέπει να βεβαιωθεί ότι είναι καλά εξοικειωµένος µε τις εντολές


προγραµµατισµού που περιγράφονται συνοπτικά στη συνέχεια. Η αναλυτική περιγραφή
των εντολών αυτών δεν είναι ένας από τους τρέχοντες στόχους αυτού του εκπαιδευτικού
υλικού.

3.10.1 Πρόταση ∆ήλωσης Μεταβλητών


Η δήλωση µεταβλητών γίνεται ως εξής:
int num;
Η παραπάνω πρόταση δήλωσης υποδηλώνει ότι θα χρησιµοποιήσουµε µια µεταβλητή µε
το όνοµα num και ότι η num είναι ακέραια µεταβλητή. Ένας άλλος απλός τύπος που θα
χρησιµοποιηθεί, είναι ο boolean. Η πρόταση «boolean flag;» µας λέει ότι η flag είναι µια
µεταβλητή τύπου boolean. Μια µεταβλητή τύπου boolean µπορεί να πάρει µόνο µία από
δύο τιµές, TRUE ή FALSE. Μια ακέραια µεταβλητή µπορεί να χρησιµοποιηθεί ως
boolean µεταβλητή, κάνοντας τη θεώρηση ότι η ακέραια τιµή 0 αντιστοιχεί σε FALSE
ενώ οποιαδήποτε µη µηδενική τιµή αντιστοιχεί σε TRUE.
Πολλές φορές αποδίδονται αρχικές τιµές στις µεταβλητές στην πρόταση δήλωσης. Π.χ.,
η πρόταση
int num = 5;
δεν είναι απλά µια πρόταση δήλωσης της ακέραιας µεταβλητής num, αλλά επιπρόσθετα
καθορίζει ότι η αρχική τιµή της num θα είναι το 5.

3.10.2 Πρόταση Καταχώρησης


Η πρόταση καταχώρησης δίνει µια τιµή σε µια µεταβλητή. Π.χ., η πρόταση
num = 1;
δίνει την τιµή 1 στην µεταβλητή num.
Η πρόταση
num = num +1;
αυξάνει την τιµή της num κατά 1. Το ίδιο ακριβώς συµβαίνει και µε τις προτάσεις
num++;
και
num += 1;
Παράρτηµα Ι Μορφή Ψευδοκώδικα Περιγραφής Προγράµµατος ∆ιεργασίας

Εκτός από τον τελεστή ‘+’ της πρόσθεσης, άλλοι γνωστοί τελεστές είναι ο ‘–‘ της
αφαίρεσης, ο ‘*’ του πολλαπλασιασµού και ο ‘/’ της διαίρεσης. Όλοι χρησιµοποιούνται
µε αντίστοιχο τρόπο όπως ο ‘+’.

3.10.3 Βρόγχοι
Μια πρόταση repeat έχει τρία µέρη. Τις λέξεις κλειδιά (repeat, begin, end, until), την
ελεγχόµενη συνθήκη µετά το until, και τις προτάσεις που εκτελούνται επαναληπτικά όσο
η συνθήκη δεν είναι αληθής:
repeat
begin
<προτάσεις>;
end
until <συνθήκη>;
Οι προτάσεις που περιέχονται µεταξύ των begin, end ονοµάζονται µπλοκ εντολών της
repeat. Με λίγα λόγια η repeat καθορίζει ότι οι προτάσεις του µπλοκ εντολών της
εκτελούνται ξανά και ξανά. Μετά από κάθε εκτέλεσή τους, η συνθήκη που ακολουθεί το
until εκτιµάται. Αν η συνθήκη είναι ψευδής, η εκτέλεση των προτάσεων
επαναλαµβάνεται. ∆ιαφορετικά, η repeat τερµατίζει και η εκτέλεση συνεχίζει µε την
επόµενη εντολή στο πρόγραµµα (δηλαδή την εντολή που ακολουθεί την γραµµή «until
<συνθήκη>»). Η συνθήκη µπορεί να είναι οποιαδήποτε boolean έκφραση, όπου το ‘==’
συµβολίζει την δυαδική πράξη της ισότητας, το ‘!=’ συµβολίζει τη δυαδική πράξη της
διαφοράς (όχι ισότητα), ενώ τα ‘<’, ‘>’, ‘≤’, ‘≥’ συµβολίζουν τις γνωστές δυαδικές
πράξεις ανισοτήτων.
Με αντίστοιχο τρόπο λειτουργεί και η πρόταση while.
while (<συνθήκη>) do
begin
<προτάσεις>;
end
Όσο η συνθήκη της while αποτιµάται σε TRUE, εκτελείται το µπλοκ εντολών. Η while
ξεκινά µε αποτίµηση της συνθήκης της. Αν η συνθήκη είναι αληθής, εκτελείται το µπλοκ
της. Στη συνέχεια η συνθήκη αποτιµάται ξανά, κ.ο.κ.
Θα πρέπει να γίνει κατανοητό πως τα µπλοκ εντολών των repeat και while µπορεί να
περιέχουν είτε απλές προτάσεις, όπως π.χ., προτάσεις καταχώρησης, ή δοµηµένες
προτάσεις, όπως π.χ., άλλες προτάσεις repeat ή while. Στη δεύτερη περίπτωση έχουµε τη
δηµιουργία φωλιασµένων βρόγχων (δηλαδή βρόγχων που περιέχουν άλλους βρόγχους).
Συχνά θα χρησιµοποιηθεί και η ακόλουθη µορφή της repeat:
repeat
begin
<προτάσεις>;
end
forever;

2η έκδοση 138
Παράρτηµα Ι Μορφή Ψευδοκώδικα Περιγραφής Προγράµµατος ∆ιεργασίας

που υποδηλώνει την επαναληπτική εκτέλεση των προτάσεων για πάντα (αφού δεν
υπάρχει συνθήκη τερµατισµού, η repeat δεν θα τερµατίσει ποτέ). Εντελώς αντίστοιχη
είναι και η ακόλουθη µορφή της repeat:
repeat
begin
<προτάσεις>;
end
until FALSE;
Εφόσον η συνθήκη FALSE είναι πάντα ψευδής (εξ ορισµού), η συνθήκη της repeat δεν
θα γίνει ποτέ αληθής και άρα η repeat δεν θα τερµατίσει ποτέ.
Αντίστοιχη είναι επίσης και η ακόλουθη µορφή της while:
while (TRUE) do
begin
<προτάσεις>;
end
H συνθήκη των repeat, while µπορεί να είναι απλά µια µεταβλητή. Αν η µεταβλητή έχει
µη µηδενική τιµή, η συνθήκη αποτιµάται σε TRUE. ∆ιαφορετικά, η συνθήκη αποτιµάται
σε FALSE. Επίσης, η συνθήκη µπορεί να είναι της µορφής “not <µεταβλητή>”. Σε αυτή
την περίπτωση, η συνθήκη αποτιµάται σε TRUE, αν η τιµή της µεταβλητής είναι 0 και σε
FALSE στην αντίθετη περίπτωση.
Όταν το σύνολο των προτάσεων που περιέχονται σε έναν βρόγχο αποτελείται από µια
µόνο πρόταση, τα begin, end µπορούν να παραληφθούν.
Τέλος, ενδιαφέρον παρουσιάζουν οι ακόλουθες µορφές των repeat, while:
repeat noop; until <συνθήκη>;
while (<συνθήκη>) do noop;
Η εντολή noop δεν προκαλεί καµία ενέργεια (δηλαδή λέει στο µεταγλωττιστή να µην
κάνει τίποτα). Έτσι, η εντολή αυτή είναι χρήσιµη, µόνο όταν χρησιµοποιείται µε τον
τρόπο που παρουσιάστηκε παραπάνω. Στις παραπάνω προτάσεις, απλά εκτελείται
επαναληπτικά ο έλεγχος που καθορίζεται από τη συνθήκη και δεν γίνεται καµία άλλη
χρήσιµη ενέργεια. Με άλλα λόγια, η παραπάνω µορφή της repeat ή της while χρησιµεύει
για να κάνουµε ένα πρόγραµµα να µπλοκάρει σε κάποιο σηµείο (δηλαδή να µην
προχωράει η εκτέλεση του πιο κάτω από µια συγκεκριµένη εντολή ανακύκλωσης µε
άδειο µπλοκ εντολών), µέχρι µια συνθήκη να αποτιµηθεί σε TRUE ή σε FALSE,
αντίστοιχα.

Παράδειγµα 11
Θεωρήστε το ακόλουθο απλό πρόγραµµα:
int num = 0;
while (num < 3)
num = num+1;
print num;

2η έκδοση 139
Παράρτηµα Ι Μορφή Ψευδοκώδικα Περιγραφής Προγράµµατος ∆ιεργασίας

Ας εξετάσουµε τι θα εκτελέσει το πρόγραµµα αυτό. Η num έχει αρχικά την τιµή 0. Άρα,
η συνθήκη (num < 3) αποτιµάται σε TRUE και εκτελείται η πρόταση της while, η οποία
αλλάζει την τιµή του num σε 1. Στη συνέχεια, η συνθήκη αποτιµάται ξανά. Αφού 1 < 3 η
συνθήκη είναι και πάλι TRUE. Έτσι, η τιµή του num αυξάνει και πάλι σε 2. Η συνθήκη
αποτιµάται ξανά και είναι και πάλι TRUE. Η τιµή του num γίνεται στη συνέχεια 3 και η
συνθήκη αποτιµάται για άλλη µια φορά. Αυτή τη φορά, η συνθήκη αποτιµάται σε
FALSE (αφού δεν ισχύει ότι 3 < 3). Έτσι, η while τερµατίζει και εκτελείται η επόµενη
εντολή, δηλαδή η πρόταση «print num;». Τυπώνεται το 3 και το πρόγραµµα τερµατίζει.
Πως θα µπορούσαµε να πετύχουµε να εκτελεστούν ακριβώς οι ίδιες ενέργειες, αλλά µε
χρήση της repeat αντί της while;
int num = 0;
repeat
num = num + 1
until (num >= 3);
print num;
Ο αναγνώστης θα πρέπει να πείσει τον εαυτό του πως το πρόγραµµα αυτό εκτελεί τις
ίδιες λειτουργίες µε το προηγούµενο.
Παρατηρήστε ότι τα δύο προγράµµατα είναι εντελώς αντίστοιχα εκτός από τη συνθήκη
της repeat, η οποία είναι η άρνηση της συνθήκης της while. Αυτό ισχύει γιατί η while
εκτελεί επαναληπτικά τις προτάσεις της όσο η συνθήκη της είναι TRUE, ενώ η repeat
εκτελεί επαναληπτικά τις προτάσεις της µέχρι η συνθήκη της να γίνει TRUE (δηλαδή
όσο είναι FALSE). Μια άλλη διαφορά των repeat, while είναι πως η πρώτη αποτιµά τη
συνθήκη της αφού εκτελέσει τις προτάσεις της, ενώ η δεύτερη πριν συµβεί αυτό. Έτσι, οι
προτάσεις της repeat θα εκτελεστούν τουλάχιστον µια φορά, αλλά αυτό µπορεί να µην
συµβεί στην περίπτωση της while (αφού, αν, την πρώτη φορά, η συνθήκη της while
αποτιµηθεί σε FALSE, οι προτάσεις της δεν θα εκτελεστούν καµία φορά). □
Άλλες χρήσιµες εντολές, που καλό θα ήταν να γνωρίζει ο αναγνώστης, είναι οι for,
break, continue, κ.α. (αλλά δεν θα επεκταθούµε εδώ).

3.10.4 Προτάσεις Ελέγχου


Η πρόταση if καλείται πρόταση διακλάδωσης γιατί δηµιουργεί ένα σηµείο διασταύρωσης
από το οποίο το πρόγραµµα µπορεί να ακολουθήσει δύο δυνατές κατευθύνσεις. Η γενική
µορφή είναι η ακόλουθη:
if (<συνθήκη>) then
begin
<προτάσεις>
end
Αν η συνθήκη είναι αληθής, εκτελείται το µπλοκ εντολών (οι προτάσεις) της if.
∆ιαφορετικά, αυτό αγνοείται. Για τη συνθήκη της if ισχύουν όλα όσα αναφέρθηκαν για
τις συνθήκες των repeat και while προηγουµένως.
Η απλή µορφή µιας πρότασης if δίνει τη δυνατότητα επιλογής εκτέλεσης ενός µπλοκ
εντολών ή αγνόησής του. Η δυνατότητα επιλογής µεταξύ δύο διαφορετικών µπλοκ

2η έκδοση 140
Παράρτηµα Ι Μορφή Ψευδοκώδικα Περιγραφής Προγράµµατος ∆ιεργασίας

εντολών παρέχεται από την if else της οποίας η γενική µορφή περιγράφεται στη
συνέχεια:
if (<συνθήκη>) then
begin
<προτάσεις 1>;
end
else
begin
<προτάσεις 2>;
end
Η συνθήκη αποτιµάται. Αν είναι TRUE εκτελείται το µπλοκ εντολών 1, ενώ διαφορετικά
εκτελείται το µπλοκ εντολών 2.

3.10.5 Πρόταση goto


Η πρόταση goto έχει δύο µέρη, το goto και ένα όνοµα ετικέτας:
goto part1;
Για να λειτουργήσει σωστά αυτή η πρόταση, θα πρέπει να υπάρχει άλλη µια πρόταση
που να ακούει στο όνοµα part1. Αυτή η πρόταση αρχίζει µε το όνοµα της ετικέτας part1,
ακολουθούµενο από ‘:’.
part1: num = num +1;
Η εντολή goto καθορίζει ότι η επόµενη προς εκτέλεση εντολή είναι η εντολή που
ακολουθεί την ετικέτα part1. Έτσι, η goto αλλάζει τη ροή εκτέλεσης του προγράµµατος.

3.10.6 Πίνακες και ∆οµές


Η πρόταση δήλωσης
int array[4];
ορίζει έναν πίνακα 4 ακεραίων µε όνοµα array. Το όνοµα array προσδιορίζει εποµένως 4
ακεραίους σε συνεχόµενες θέσεις στη µνήµη του υπολογιστή. Ο πρώτος ακέραιος
βρίσκεται στη θέση array[0], o δεύτερος στην array[1], ο τρίτος στην array[2], και ο 4ος
στη θέση array[3]. Πιο γενικά, αν ένας πίνακας περιέχει n στοιχεία, το k-οστό στοιχείο, 0
< k ≤ n, βρίσκεται στη θέση array[k-1].
Μια δοµή (structure) µπορεί να περιέχει ένα ή περισσότερα πεδία. Τα πεδία µιας δοµής
µπορεί να είναι οποιουδήποτε τύπου (και όχι απαραίτητα του ίδιου τύπου, όπως ισχύει
για τα στοιχεία ενός πίνακα). Η µορφοποίηση µιας δοµής καθορίζει τα διαφορετικά πεδία
που απαρτίζουν τη δοµή. Μια µορφοποίηση δοµής παρουσιάζεται στη συνέχεια:
struct 2fields
begin
int a;
boolean flag;
end

2η έκδοση 141
Παράρτηµα Ι Μορφή Ψευδοκώδικα Περιγραφής Προγράµµατος ∆ιεργασίας

Μεταβλητές δοµών τύπου 2fields αποτελούνται από δύο πεδία, µια ακέραια µεταβλητή a
και µια boolean µεταβλητή flag. Για να δηλώσουµε µια µεταβλητή τύπου δοµής 2fields
γράφουµε τα εξής:
struct 2fields b;
Η b είναι µια µεταβλητή τύπου δοµής 2fields. Η b αποτελείται από δύο πεδία, την
ακέραια µεταβλητή b.a και την boolean µεταβλητή b.flag.

3.10.7 Typedef
Η typedef επιτρέπει τη δηµιουργία ενός νέου ονόµατος για κάποιο τύπο. Ας επανέλθουµε
στον τύπο δοµής 2fields. Ο ορισµός:
typedef struct 2fields 2FIELDS;
υποδηλώνει ότι το όνοµα 2FIELDS µπορεί να χρησιµοποιηθεί αντί των δύο λέξεων
struct 2fields. Έτσι, η δήλωση:
2FIELDS b;
είναι ένας ακόµη τρόπος να δηλώσουµε µια µεταβλητή b τύπου δοµής 2fields.
Οµοίως, ο ορισµός:
typedef int boolean;
υποδηλώνει ότι η λέξη boolean µπορεί να χρησιµοποιείται αντί του int για τη δήλωση
ακεραίων.

3.10.8 Συναρτήσεις
Συνάρτηση είναι µια αυτοδύναµη µονάδα κώδικα προγράµµατος που έχει σχεδιαστεί να
εκτελεί µια συγκεκριµένη εργασία. Μια συνάρτηση µπορεί να παράγει ενέργειες και να
παρέχει τιµές. Το Κεφάλαιο 3 προϋποθέτει κάποια εξοικείωση του αναγνώστη µε τον
τρόπο λειτουργίας µιας συνάρτησης.

3.10.9 Άλλα Θέµατα


Το Κεφάλαιο 3 προϋποθέτει µικρή εξοικείωση του αναγνώστη µε θέµατα σχετικά µε τις
εµβέλειες των µεταβλητών (και ιδιαίτερα µε την έννοια της τοπικής µεταβλητής).
Τα σύµβολα /* και */ χρησιµοποιούνται για να συµπεριλάβουµε σχόλια στον κώδικα,
ώστε να γίνει πιο ευανάγνωστος.

2η έκδοση 142
Βιβλιογραφία
[A00] G. Andrews, Multithreaded, Parallel and Distributed Programming,
Addison-Wesley, 2000.
[AKH03] J. Anderson, Y. Kim, and T. Herman, “Shared-memory mutual
exclusion: Major research trends since 1986”, Distributed
Computing: 15(4): 221-253, 2002.
[AW98] H. Attiya and J. Welch, Distributed Computing, Fundamentals,
Simulations and Advanced Topics, Mc Graw Hill, England, 1998.
[B86] M. Bach, The design of the UNIX operating system, Prentice-Hall,
1986.
[BA90] M. Ben-Ari, Principles of Concurrent and Distributed
Programming, Prentice-Hall, 1990.
[BH03] J. Bacon, and T. Harris, Operating Systems, Concurrent and
Distributed Software Design, Harlow: Addison-Wesley, 2003.
[BH72] P. Brinch Hansen, “A Comparison of two synchronizing concepts”,
Acta Informatica, 1(3): 190-199, 1972.
[BH72] P. Brinch Hansen, Operating System Principles, Prentice-Hall, 1972.
[CDD62] F.J. Corbató, M. M. Daggett and R. C. Daley, "An Experimental
Time-Sharing System," Proceedings of the Spring Joint Computer
Conference, 21, Spartan Books, Baltimore, 1962, pp. 335-344.
[CDK01] G. Coulouris, J. Dollimore and T. Kindberg, Distributed Systems,
Concepts and Design, Addison-Wesley, 2001.
[CHP71] P. Courtois, F. Heyman, and D. Parnas, “Concurrent control with
Readers and Writers”, Communications of the ACM, 14(10): 667-
668, 1971.
[D65I] E.W. Dijkstra, “Solution of a problem in concurrent programming
control”, Communication of the ACM, 8(9): 569, 1965.
[D65II] E.W. Dijkstra, “Co-operating sequential processes”, In F.D. Genuys,
ed., Programming Languages, pp. 43-112, New York Academic
Press, 1968.
[D71] E. W. Dijkstra, “Hierarchical ordering of sequential processes”, Acta
Informatica, 1: 115-138, 1971.
[D90] H. Deitel, Operating Systems, Addison-Wesley, 1990.
[G00] D. Galli, Distributed Operating Systems, Prentice-Hall, Inc., 2000.
[GL96] L. Goldshlager and A. Lister, Εισαγωγή στη σύγχρονη Επιστήµη των
Υπολογιστών, Εκδόσεις ∆ίαυλος, 1996.
[HP90] J. Hennessy, and D. Patterson, Computer Architecture: A
quantitative approach, Morgan Kaufmann, 1990.
Βιβλιογραφία

[L74] L. Lamport, “A new solution of Dijksta’s concurrent programming


problem”, Communication of the ACM, 18(8): 453-455, 1974.
[Lyn96] N. A. Lynch, Distributed Algorithms, Morgan Kaufmann Publishers,
Inc., 1996.
[M93] S. Mullender, Distributed Systems, Addison-Wesley, 1993.
[MM01] J. Mauro and R. McDougall, Solaris Internal: Core Kernel
Architecture, Prentice Hall, 2001.
[N00] G. Nutt, Operating Systems: A modern Perspective, Addison-
Wesley, 2000.
[N00] P. Norton, Εισαγωγή στους Υπολογιστές, Εκδόσεις Τζιόλα, 2000
[P71] S. Patil, “Limitations of capabilities of Dijkstra’s semaphore
primitives for coordination among processes”, In Project MAC
computational structures group, MIT, memo 57, 1971.
[P81] G. Peterson, “Myths about the mutual exclusion problem”,
Information Processing Letters, 12(3): 115-116, 1981.
[R86] M. Raynal, Algorithms for Mutual Exclusion, MIT Press, 1986.
[S59] C. Strachey, “Time Sharing in Large Fast Computers”, Prooceedings
of the International Conference of Information Processing, Paris,
1959, pp. 336-341.
[S92] C. Snow, Concurrent Programming, Cambridge University Press,
1992.
[SGG07] Α. Silberschatz, P. Galvin and G. Gagne, Λειτουργικά Συστήµατα,
John Willey & Sons, 2005 και Εκδόσεις ΙΩΝ, 2007.
[SH07] M. Herlihy and N. Shavit, The Art of Multiprocessor programming,
Morgan-Kaufmann, 2007.
[SR00] D. Solomon and M. Russinovich, Inside Microsoft Windows 2000,
Microsoft Press, 2000.
[St00] W. Stallings, Computer Organization and Architecture, Prentice-
Hall, 2000.
[St03] W. Stallings, Λειτουργικά Συστήµατα Αρχές Σχεδίασης, Prentice Hall,
Inc., 2001 και Εκδόσεις Τζιόλα, 2003.
[T06] G. Taubenfeld, Synchronization Algorithms and Concurrent
Programming, Pearson/Prentice Hall, 2006.
[T94] G. Tell, Introduction to Distributed Algorithms, Cambridge
University Press, 1994.
[Tan90] A. Tanenbaum, Structured Computer Organization, Prentice-Hall,
1990
[TanI] A. S. Tanenbaum, Σύγχρονα Λειτουργικά Συστήµατα, Prentice-Hall
International, Κλειδάριθµος, Αθήνα.
[TanII] A. S. Tanenbaum, Σύγχρονα Λειτουργικά Συστήµατα, Τόµος Β,

2η έκδοση 144
Βιβλιογραφία

Prentice Hall International, Παπασωτηρίου, Αθήνα 1994.


[TanS06] A. S. Tanenbaum and Maarten van Steen, ΚΑΤΑΝΕΜΗΜΕΝΑ
ΣΥΣΤΗΜΑΤΑ: Αρχές και Υποδείγµατα, Εκδόσεις Κλειδάριθµος,
2005.
[TW96] A. Tanenbaum and A. Woodhull, Operating Systems: Design and
Implementation, Prentice Hall, 2hn Edition, 1996.

2η έκδοση 145