Ls Chapters 123 New

  • November 2019
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Ls Chapters 123 New as PDF for free.

More details

  • Words: 48,667
  • Pages: 143
ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ Παναγιώτα Φατούρου Τµήµα Πληροφορικής, Πανεπιστήµιο Ιωαννίνων

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

Σεπτέµβριος 2004

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

Πίνακας Περιεχοµένων 1

ΚΕΦΑΛΑΙΟ ΕΙΣΑΓΩΓΗ ............................................................................................................ 8 1.1 1.2 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.4 1.4.1 1.4.2 1.4.3 1.5 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9

2

ΚΕΦΑΛΑΙΟ ∆ΙΕΡΓΑΣΙΕΣ – ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΣΗ ∆ΙΕΡΓΑΣΙΩΝ ............................30 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.7.1 2.7.2 2.7.3 2.7.4

3

ΤΙ ΕΊΝΑΙ ΛΕΙΤΟΥΡΓΙΚΌ ΣΎΣΤΗΜΑ – ΣΚΟΠΌΣ & ΠΑΡΕΧΌΜΕΝΕΣ ΥΠΗΡΕΣΊΕΣ ............................... 9 ΣΎΝΤΟΜΗ ΑΝΑΣΚΌΠΗΣΗ ΥΛΙΚΟΎ ΥΠΟΛΟΓΙΣΤΏΝ .....................................................................10 ΘΈΜΑΤΑ ΑΠΌ∆ΟΣΗΣ ...................................................................................................................12 Ενδιάµεση Προσωρινή Αποθήκευση (Buffering) ....................................................................13 Παροχέτευση (Spooling) ........................................................................................................13 Πολυ-προγραµµατισµός (Multiprogramming)........................................................................14 ∆ιαµοιρασµός Χρόνου (Time Sharing)...................................................................................15 ΠΡΟΣΤΑΣΊΑ .................................................................................................................................16 Προστασία Ε/Ε.......................................................................................................................16 Προστασία ΚΜΕ ...................................................................................................................17 Προστασία ∆ιευθυνσιοδότησης Μνήµης .................................................................................17 ΤΑ ΣΥΣΤΑΤΙΚΆ ΕΝΌΣ ΛΕΙΤΟΥΡΓΙΚΟΎ ΣΥΣΤΉΜΑΤΟΣ ..................................................................18 ΚΑΤΑΝΕΜΗΜΈΝΑ ΣΥΣΤΉΜΑΤΑ & ΣΥΣΤΉΜΑΤΑ ΠΡΑΓΜΑΤΙΚΟΎ ΧΡΌΝΟΥ ..................................21 ΓΙΑ ΠΕΡΙΣΣΌΤΕΡΗ ΜΕΛΈΤΗ .........................................................................................................21 ΜΟΡΦΉ ΨΕΥ∆ΟΚΏ∆ΙΚΑ ΠΕΡΙΓΡΑΦΉΣ ΠΡΟΓΡΆΜΜΑΤΟΣ ∆ΙΕΡΓΑΣΊΑΣ ..........................................24 Πρόταση ∆ήλωσης Μεταβλητών ............................................................................................24 Πρόταση Καταχώρησης..........................................................................................................24 Βρόγχοι ..................................................................................................................................25 Προτάσεις Ελέγχου.................................................................................................................27 Πρόταση goto.........................................................................................................................28 Πίνακες και ∆οµές..................................................................................................................28 Typedef ..................................................................................................................................29 Συναρτήσεις ...........................................................................................................................29 Άλλα Θέµατα ..........................................................................................................................29

Η ΈΝΝΟΙΑ ΤΗΣ ∆ΙΕΡΓΑΣΊΑΣ .........................................................................................................31 ΚΑΤΑΣΤΆΣΕΙΣ ∆ΙΕΡΓΑΣΙΏΝ ..........................................................................................................31 ΤΟ ΜΠΛΟΚ ΕΛΈΓΧΟΥ ∆ΙΕΡΓΑΣΙΏΝ .............................................................................................32 ΛΕΙΤΟΥΡΓΊΕΣ ΕΠΊ ∆ΙΕΡΓΑΣΙΏΝ.....................................................................................................33 ∆ΙΑΚΟΠΈΣ ...................................................................................................................................34 ΧΡΟΝΟ∆ΡΟΜΌΛΟΓΗΣΗ ................................................................................................................39 ΑΛΓΌΡΙΘΜΟΙ ΧΡΟΝΟ∆ΡΟΜΟΛΌΓΗΣΗΣ .........................................................................................43 Πρώτη Εισερχόµενη – Πρώτη Εξυπηρετούµενη (First Come – First Served, FCFS).............43 Εκ Περιτροπής (Round Robin, RR) ........................................................................................44 Προτεραιοτήτων (Priority).....................................................................................................45 Θεωρητική Μελέτη Απόδοσης Χρονοδροµολογητών ..............................................................46

ΚΕΦΑΛΑΙΟ ∆ΙΑ∆ΙΕΡΓΑΣΙΑΚΗ ΕΠΙΚΟΙΝΩΝΙΑ & ΣΥΓΧΡΟΝΙΣΜΟΣ ..........................58 3.1 3.2 3.3 3.4 3.5 3.5.1 3.5.2 3.6 3.7 3.7.1 3.7.2 3.7.3 3.7.4

ΤΑΥΤΌΧΡΟΝΗ ΕΚΤΈΛΕΣΗ ∆ΙΕΡΓΑΣΙΏΝ ........................................................................................59 Η ΑΝΆΓΚΗ ΣΥΓΧΡΟΝΙΣΜΟΎ ........................................................................................................69 ΤΟ ΠΡΌΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΊΟΥ ΑΠΟΚΛΕΙΣΜΟΎ .......................................................................73 ΛΎΣΕΙΣ ΜΕ ΤΗ ΒΟΉΘΕΙΑ ΤΟΥ ΥΛΙΚΟΎ ........................................................................................75 ΛΎΣΕΙΣ ΜΕ ΧΡΉΣΗ ΛΟΓΙΣΜΙΚΟΎ ΜΌΝΟ ......................................................................................82 Πρώτες Προσπάθειες προς την λύση ......................................................................................83 Λύση του Peterson..................................................................................................................90 ΣΗΜΑΦΌΡΟΙ ................................................................................................................................99 ΠΡΟΒΛΉΜΑΤΑ ∆ΙΑ∆ΙΕΡΓΑΣΙΑΚΉΣ ΕΠΙΚΟΙΝΩΝΊΑΣ ....................................................................105 Για Προθέρµανση................................................................................................................105 Το Πρόβληµα του Ζωολογικού Κήπου (Θέµα 6, Α’ Τελική Εξέταση, Ιούνιος 2003).............113 Το Πρόβληµα του Κουρέα (Θέµα 3Β, Α’ Τελική Εξέταση, Μάιος 2001) ..............................117 Το Πρόβληµα των Αναγνωστών-Εγγραφέων........................................................................119

3.7.5 Για περισσότερη εξάσκηση ...................................................................................................121 3.7.6 Γενικές Παρατηρήσεις..........................................................................................................125 3.8 ΚΡΊΣΙΜΕΣ ΠΕΡΙΟΧΈΣ ΚΑΙ ΚΡΊΣΙΜΕΣ ΠΕΡΙΟΧΈΣ ΥΠΌ ΣΥΝΘΉΚΗ ................................................125 3.9 ΛΊΓΟ ΠΙΟ ∆ΎΣΚΟΛΑ ..................................................................................................................133 3.9.1 Λύση του Lamport (Bakery Algorithm) ................................................................................133 3.9.2 Λύση του Dekker ..................................................................................................................135 3.9.3 ∆ικαιοσύνη ...........................................................................................................................136 3.9.4 Προσοµοιώσεις ....................................................................................................................139

Πίνακας Σχηµάτων ΣΧΗΜΑ 1: ΣΧΕΣΗ ΤΟΥ ΛΣ ΜΕ ΤΟ ΥΛΙΚΟ ΤΟΥ ΥΠΟΛΟΓΙΣΤΗ ΚΑΙ ΤΟΥΣ ΧΡΗΣΤΕΣ............................................... 9 ΣΧΗΜΑ 2: ΙΕΡΑΡΧΙΑ ΜΟΝΑ∆ΩΝ ΑΠΟΘΗΚΕΥΣΗΣ ............................................................................................12 ΣΧΗΜΑ 3: ΠΡΟΣΤΑΣΙΑ ∆ΙΕΥΘΥΝΣΙΟ∆ΟΤΗΣΗΣ ΜΝΗΜΗΣ ΜΕ ΧΡΗΣΗ ΤΩΝ ΚΑΤΑΧΩΡΗΤΩΝ ΒΑΣΗΣ ΚΑΙ ΟΡΙΟΥ.....18 ΣΧΗΜΑ 4: ΓΡΑΦΗΜΑ ΚΑΤΑΣΤΑΣΕΩΝ ∆ΙΕΡΓΑΣΙΑΣ. ..........................................................................................32 ΣΧΗΜΑ 5: ΤΟ PCB ΜΙΑΣ ∆ΙΕΡΓΑΣΙΑΣ..............................................................................................................33 ΣΧΗΜΑ 6: ΕΝΑΛΛΑΓΗ ∆ΙΕΡΓΑΣΙΩΝ.................................................................................................................37 ΣΧΗΜΑ 7: Η ΟΥΡΑ ΕΤΟΙΜΩΝ ∆ΙΕΡΓΑΣΙΩΝ. ......................................................................................................40 ΣΧΗΜΑ 8: ΣΧΗΜΑΤΙΚΗ ΠΕΡΙΓΡΑΦΗ ΤΗΣ ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΣΗΣ ∆ΙΕΡΓΑΣΙΩΝ. ...............................................41 ΣΧΗΜΑ 9: ΕΙ∆ΟΣ ∆ΡΟΜΟΛΟΓΗΣΗΣ ΠΟΥ ΠΑΡΕΧΕΤΑΙ ΑΠΟ ΤΟΥΣ ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΤΕΣ ΚΜΕ ΚΑΙ ΜΑΚΡΑΣ ∆ΙΑΡΚΕΙΑΣ.............................................................................................................................................43 ΣΧΗΜΑ 10: ΠΙΝΑΚΑΣ ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΣΗΣ ΓΙΑ ΤΟΝ ΑΛΓΟΡΙΘΜΟ RR........................................................52 ΣΧΗΜΑ 11: ∆ΙΑΓΡΑΜΜΑΤΑ GANNT ΓΙΑ ΤΟΥΣ ΑΛΓΟΡΙΘΜΟΥΣ ΧΡΟΝΟ∆ΡΟΜΟΛΟΓΗΣΗΣ ΤΟΥ ΠΑΡΑ∆ΕΙΓΜΑΤΟΣ 6. .............................................................................................................................................................54 ΣΧΗΜΑ 12: 1Ο ΠΑΡΑ∆ΕΙΓΜΑ ∆ΥΝΑΤΗΣ ΤΑΥΤΟΧΡΟΝΗΣ ΕΚΤΕΛΕΣΗΣ ..............................................................61 ΣΧΗΜΑ 13: 1Ο ΠΑΡΑ∆ΕΙΓΜΑ ∆ΥΝΑΤΗΣ ΤΑΥΤΟΧΡΟΝΗΣ ΕΚΤΕΛΕΣΗΣ: ΠΙΟ ΕΥΑΝΑΓΝΩΣΤΗ ΠΑΡΟΥΣΙΑΣΗ .......62 ΣΧΗΜΑ 14: 2Ο ΠΑΡΑ∆ΕΙΓΜΑ ∆ΥΝΑΤΗΣ ΤΑΥΤΟΧΡΟΝΗΣ ΕΚΤΕΛΕΣΗΣ ..............................................................63 ΣΧΗΜΑ 15: ΚΩ∆ΙΚΕΣ ΠΟΥ ΕΚΤΕΛΟΥΝ ΟΙ ∆ΙΕΡΓΑΣΙΕΣ Α ΚΑΙ Β. .......................................................................64 ΣΧΗΜΑ 16: ΣΕΝΑΡΙΟ ΠΟΥ Ο∆ΗΓΕΙ ΣΤΗΝ ΜΙΚΡΟΤΕΡΗ ΤΙΜΗ ΓΙΑ ΤΗ ∆ΙΑΜΟΙΡΑΖΟΜΕΝΗ ΜΕΤΑΒΛΗΤΗ TALLY. ...68 ΣΧΗΜΑ 17: ΟΥΡΑ ΕΤΕΡΟΧΡΟΝΙΣΜΕΝΗΣ ΕΚΤΥΠΩΣΗΣ......................................................................................70 ΣΧΗΜΑ 18: ΣΕΝΑΡΙΟ ΠΟΥ Ο∆ΗΓΕΙ ΣΕ ΑΝΑΛΗΨΗ 1000€ ΑΠΟ ΕΝΑ ΛΟΓΑΡΙΑΣΜΟ ΠΟΥ ΠΕΡΙΕΧΕΙ ΜΟΝΟ 500€....72 ΣΧΗΜΑ 19: ΕΝΑΛΛΑΚΤΙΚΕΣ ΙΣΟ∆ΥΝΑΜΕΣ ΓΕΝΙΚΕΣ ΜΟΡΦΕΣ ΚΩ∆ΙΚΑ ∆ΙΕΡΓΑΣΙΩΝ. ........................................74 ΣΧΗΜΑ 20: ∆ΙΑΚΟΠΕΣ ΕΝΟΣΩ ΜΙΑ ∆ΙΕΡΓΑΣΙΑ ΕΚΤΕΛΕΙ ΤΟ ΚΡΙΣΙΜΟ ΤΜΗΜΑ ΤΗΣ. ..........................................75 ΣΧΗΜΑ 21: Η ΕΝΤΟΛΗ TEST&SET()...............................................................................................................76 ΣΧΗΜΑ 22: ΛΥΣΗ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΜΕ ΧΡΗΣΗ ΤΗΣ ΕΝΤΟΛΗΣ TEST&SET()...76 ΣΧΗΜΑ 23: ΚΩ∆ΙΚΑΣ ΠΟΥ ΕΚΤΕΛΕΙΤΑΙ ΣΤΗΝ ΠΡΑΞΗ ΑΠΟ ∆ΥΟ ∆ΙΕΡΓΑΣΙΕΣ 0 ΚΑΙ 1 ΟΤΑΝ Η TEST&SET() ΧΡΗΣΙΜΟΠΟΙΕΙΤΑΙ ΓΙΑ ΕΠΙΤΕΥΞΗ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ...............................................................77 ΣΧΗΜΑ 24: ΕΚΤΕΛΕΣΗ ΠΟΥ ΠΑΡΑΒΙΑΖΕΙ ΤΗ ΣΥΝΘΗΚΗ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΑΝ Η TEST&SET() ∆ΕΝ ΕΚΤΕΛΕΙΤΑΙ ΑΤΟΜΙΚΑ...........................................................................................................................78 ΣΧΗΜΑ 25: ΑΤΟΜΙΚΗ ΕΚ∆ΟΣΗ ΤΗΣ DEPOSIT()................................................................................................79 ΣΧΗΜΑ 26: ΑΤΟΜΙΚΗ ΕΚ∆ΟΣΗ ΤΗΣ WITHDRAW()...........................................................................................79 ΣΧΗΜΑ 27: ΕΚΤΕΛΕΣΗ ΠΟΥ Ο∆ΗΓΕΙ ΣΕ ΠΑΡΑΤΕΤΑΜΕΝΗ ΣΤΕΡΗΣΗ ΓΙΑ ΤΗ ∆ΙΕΡΓΑΣΙΑ 1 ΣΤΗ ΛΥΣΗ ΠΟΥ ΧΡΗΣΙΜΟΠΟΙΕΙ ΤΗΝ TEST&SET(). .........................................................................................................80 ΣΧΗΜΑ 28: Η ΕΝΤΟΛΗ FETCH&ADD(). ..........................................................................................................81 ΣΧΗΜΑ 29: Η ΕΝΤΟΛΗ SWAP().......................................................................................................................81 ΣΧΗΜΑ 30: Η ΕΝΤΟΛΗ RMW(). .....................................................................................................................82 ΣΧΗΜΑ 31: ΠΡΩΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ......................................................................................................................................83 ΣΧΗΜΑ 32: ΣΕΝΑΡΙΟ ΠΟΥ ΚΑΤΑΣΤΡΑΤΗΓΕΙ ΤΗ ΣΥΝΘΗΚΗ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ΤΗΝ 1Η ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ...........................................................................................................................84 ΣΧΗΜΑ 33: ∆ΕΥΤΕΡΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ......................................................................................................................................84 ΣΧΗΜΑ 34: ΣΕΝΑΡΙΟ ΠΟΥ ΚΑΤΑΣΤΡΑΤΗΓΕΙ ΤΗ ΣΥΝΘΗΚΗ ΤΗΣ ΠΡΟΟ∆ΟΥ ΓΙΑ ΤΗ 2Η ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ. ....85 ΣΧΗΜΑ 35: ΤΡΙΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ (ΛΥΣΗ ΤΗΣ ΑΥΣΤΗΡΗΣ ΕΝΑΛΛΑΓΗΣ) ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ. .......................................................................................86 ΣΧΗΜΑ 36: ΣΕΝΑΡΙΟ ΠΟΥ ΚΑΤΑΣΤΡΑΤΗΓΕΙ ΤΗ ΣΥΝΘΗΚΗ ΤΗΣ ΠΡΟΟ∆ΟΥ ΓΙΑ ΤΗΝ 3Η ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ...87 ΣΧΗΜΑ 37: ΤΕΤΑΡΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ∆ΥΟ ∆ΙΕΡΓΑΣΙΕΣ......................................................................................................87 ΣΧΗΜΑ 38: Η ΛΥΣΗ ΤΟΥ PETERSON. ..............................................................................................................90 ΣΧΗΜΑ 39: ΠΕΜΠΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ∆ΥΟ ∆ΙΕΡΓΑΣΙΕΣ......................................................................................................92 ΣΧΗΜΑ 40: ΈΚΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ......................................................................................................................................93

ΣΧΗΜΑ 41: ΣΕΝΑΡΙΟ ΠΟΥ ΚΑΤΑΣΤΡΑΤΗΓΕΙ ΤΗ ΣΥΝΘΗΚΗ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ΤΗΝ 5Η ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ...........................................................................................................................95 ΣΧΗΜΑ 42: ΕΛΑΦΡΩΣ ΤΡΟΠΟΠΟΙΗΜΕΝΗ ΕΚ∆ΟΣΗ ΤΗΣ ΛΥΣΗΣ ΤΟΥ PETERSON................................................95 ΣΧΗΜΑ 43: ΈΝΑ ΣΕΝΑΡΙΟ ΠΟΥ Ο∆ΗΓΕΙ ΣΕ ΠΑΡΑΤΕΤΑΜΕΝΗ ΣΤΕΡΗΣΗ ΤΗΝ ΕΛΑΦΡΩΣ ΤΡΟΠΟΠΟΙΗΜΕΝΗ ΕΚ∆ΟΣΗ ΤΗΣ ΛΥΣΗΣ ΤΟΥ PETERSON......................................................................................................96 ΣΧΗΜΑ 44: ΈΒ∆ΟΜΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ......................................................................................................................................97 ΣΧΗΜΑ 45: ΌΓ∆ΟΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ......................................................................................................................................97 ΣΧΗΜΑ 46: ΠΙΟ ΑΝΑΛΥΤΙΚΗ ΠΕΡΙΓΡΑΦΗ ΤΟΥ ΚΩ∆ΙΚΑ ΤΩΝ ∆ΙΕΡΓΑΣΙΩΝ ΤΗΣ ΟΓ∆ΗΣ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗΣ. 98 ΣΧΗΜΑ 47: ΈΝΑΤΗ ΠΡΟΤΕΙΝΟΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΓΙΑ ∆ΥΟ ∆ΙΕΡΓΑΣΙΕΣ............................................................................................................................................98 ΣΧΗΜΑ 48: ΥΛΟΠΟΙΗΣΗ ΛΕΙΤΟΥΡΓΙΩΝ UP() ΚΑΙ DOWN() ΜΕ ΧΡΗΣΗ ΛΟΓΙΣΜΙΚΟΥ........................................100 ΣΧΗΜΑ 49: ΛΥΣΗ ΠΡΟΒΛΗΜΑΤΟΣ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΜΕ ΣΗΜΑΦΟΡΟΥΣ. ....................................100 ΣΧΗΜΑ 50: ΈΝΑ ΚΟΙΝΟ ΛΑΘΟΣ ΣΤΗ ΧΡΗΣΗ ΣΗΜΑΦΟΡΩΝ.............................................................................101 ΣΧΗΜΑ 51: ΈΝΑ ΑΚΟΜΗ ΚΟΙΝΟ ΛΑΘΟΣ ΣΤΗ ΧΡΗΣΗ ΣΗΜΑΦΟΡΩΝ. ...............................................................102 ΣΧΗΜΑ 52: ΥΛΟΠΟΙΗΣΗ ΣΗΜΑΦΟΡΩΝ ΧΡΗΣΙΜΟΠΟΙΩΝΤΑΣ ΑΚΕΡΑΙΕΣ ∆ΙΑΜΟΙΡΑΖΟΜΕΝΕΣ ΜΕΤΑΒΛΗΤΕΣ ΚΑΙ ΤΗΝ ΑΤΟΜΙΚΗ ΛΕΙΤΟΥΡΓΙΑ TEST&SET() ΠΟΥ ΠΑΡΕΧΕΤΑΙ ΑΠΟ ΤΟ ΥΛΙΚΟ...........................................104 ΣΧΗΜΑ 53: ΠΕΡΙΓΡΑΦΗ ΕΝΕΡΓΕΙΩΝ ΠΟΥ ΕΚΤΕΛΟΥΝ ΟΙ ∆ΙΕΡΓΑΣΙΕΣ ΠΕΛΑΤΩΝ ΚΑΙ ΕΞΥΠΗΡΕΤΗ. ..................107 ΣΧΗΜΑ 54: ΜΙΑ ΠΡΩΤΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΠΕΛΑΤΩΝ-ΕΞΥΠΗΡΕΤΗ. .......................................................109 ΣΧΗΜΑ 55: ΜΙΑ ΒΕΛΤΙΣΤΟΠΟΙΗΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΠΕΛΑΤΩΝ-ΕΞΥΠΗΡΕΤΗ. .................................110 ΣΧΗΜΑ 56: ΜΙΑ ΛΑΘΟΣ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΠΕΛΑΤΩΝ-ΕΞΥΠΗΡΕΤΗ........................................................110 ΣΧΗΜΑ 57: ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΩΝ ΚΑΠΝΙΣΤΩΝ-ΠΩΛΗΤΗ. ....................................................................112 ΣΧΉΜΑ 58: PING-PONG................................................................................................................................113 ΣΧΗΜΑ 59: ΠΕΡΙΓΡΑΦΗ ΑΠΛΩΝ ΕΝΕΡΓΕΙΩΝ ΠΟΥ ΕΚΤΕΛΟΥΝ ΟΙ ∆ΙΕΡΓΑΣΙΕΣ ΕΠΙΒΑΤΩΝ ΚΑΙ ΑΥΤΟΚΙΝΗΤΩΝ..114 ΣΧΗΜΑ 60: ΑΠΛΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ............................................................114 ΣΧΗΜΑ 61: ΠΡΩΤΗ ΒΕΛΤΙΩΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ. ..................................115 ΣΧΗΜΑ 62: ∆ΕΥΤΕΡΗ ΒΕΛΤΙΩΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ.................................116 ΣΧΗΜΑ 63: ΛΥΣΗ ΣΤΗΝ ΑΠΛΗ ΕΚ∆ΟΣΗ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΤΟΥ ΚΟΥΡΕΑ...................................................117 ΣΧΗΜΑ 64: ΜΙΑ ΑΚΟΜΗ ΣΩΣΤΗ ΛΥΣΗ ΣΤΗΝ ΑΠΛΗ ΕΚ∆ΟΣΗ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΤΟΥ ΚΟΥΡΕΑ. ...................118 ΣΧΗΜΑ 65: ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΚΟΥΡΕΑ. .........................................................................................119 ΣΧΗΜΑ 66: ΠΡΩΤΗ ΠΡΟΣΠΑΘΕΙΑ ΕΠΙΛΥΣΗΣ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ. .....................119 ΣΧΗΜΑ 67: ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ.....................................................................120 ΣΧΗΜΑ 68: ΣΚΕΛΕΤΟΣ ΚΩ∆ΙΚΑ ∆ΙΕΡΓΑΣΙΑΣ ΑΥΤΟΚΙΝΗΤΟΥ. ........................................................................121 ΣΧΗΜΑ 69: ΠΕΡΙΓΡΑΦΗ ΤΩΝ ΕΝΕΡΓΕΙΩΝ ΤΩΝ ∆ΙΕΡΓΑΣΙΩΝ ΤΗΣ ΆΣΚΗΣΗΣ ΑΥΤΟΑΞΙΟΛΟΓΗΣΗΣ 44. ..............123 ΣΧΗΜΑ 70: ΣΚΕΛΕΤΟΣ ΚΩ∆ΙΚΑ ∆ΙΕΡΓΑΣΙΩΝ ΓΙΑ ΤΟ ΠΡΟΒΛΗΜΑ ΠΟΥ ΠΕΡΙΓΡΑΦΕΤΑΙ ΣΤΗΝ ΆΣΚΗΣΗ ΑΥΤΟΑΞΙΟΛΟΓΟΓΗΣΗΣ 45...................................................................................................................125 ΣΧΗΜΑ 71: ΓΛΩΣΣΙΚΗ ΈΚΦΡΑΣΗ ΤΟΥ HANSEN. ...........................................................................................126 ΣΧΗΜΑ 72: ΑΤΟΜΙΚΗ ΕΚ∆ΟΣΗ ΤΩΝ DEPOSIT() ΚΑΙ WITHDRAW() ΧΡΗΣΙΜΟΠΟΙΩΝΤΑΣ ΤΗ ΓΛΩΣΣΙΚΗ ΕΚΦΡΑΣΗ ΤΟΥ HANSEN. ...................................................................................................... □ 127 ΣΧΗΜΑ 73: ΓΛΩΣΣΙΚΗ ΈΚΦΡΑΣΗ ΚΡΙΣΙΜΗΣ ΠΕΡΙΟΧΗΣ ΥΠΟ ΣΥΝΘΗΚΗ. ......................................................127 ΣΧΗΜΑ 74: ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ ΜΕ ΧΡΗΣΗ ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ (ΑΝΤΙΣΤΟΙΧΗ ΕΚΕΙΝΗΣ ΤΟΥ ΣΧΗΜΑΤΟΣ 60). .......................................................................................129 ΣΧΗΜΑ 75: ΠΡΩΤΗ ΒΕΛΤΙΩΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ ΜΕ ΧΡΗΣΗ ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ (ΑΝΤΙΣΤΟΙΧΗ ΕΚΕΙΝΗΣ ΤΟΥ ΣΧΗΜΑΤΟΣ 61)...............................................129 ΣΧΗΜΑ 76: ∆ΕΥΤΕΡΗ ΒΕΛΤΙΩΜΕΝΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΖΩΟΛΟΓΙΚΟΥ ΚΗΠΟΥ ΜΕ ΧΡΗΣΗ ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ (ΑΝΤΙΣΤΟΙΧΗ ΕΚΕΙΝΗΣ ΤΟΥ ΣΧΗΜΑΤΟΣ 62)...............................................130 ΣΧΗΜΑ 77: ΛΥΣΗ ΑΝΤΙΣΤΟΙΧΗ ΕΚΕΙΝΗΣ ΤΟΥ ΣΧΗΜΑΤΟΣ 76 ΑΛΛΑ ΠΟΥ ΕΠΙΤΥΓΧΑΝΕΙ ΜΕΓΑΛΥΤΕΡΟ ΠΑΡΑΛΛΗΛΙΣΜΟ. □ .............................................................................................................................131 ΣΧΗΜΑ 78: ΠΡΩΤΗ ΠΡΟΣΠΑΘΕΙΑ ΕΠΙΛΥΣΗΣ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ ΜΕ ΧΡΗΣΗ ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ...................................................................................................131 ΣΧΗΜΑ 79: ΣΩΣΤΗ ΛΥΣΗ ΤΟΥ ΠΡΟΒΛΗΜΑΤΟΣ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ ΜΕ ΧΡΗΣΗ ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ. ...................................................................................................................................132 ΣΧΗΜΑ 80: Ο ΑΛΓΟΡΙΘΜΟΣ ΤΟΥ ΑΡΤΟΠΩΛΕΙΟΥ. .........................................................................................134 ΣΧΗΜΑ 81: Η ΛΥΣΗ ΤΟΥ DEKKER. ...............................................................................................................135 ΣΧΗΜΑ 82: ∆ΙΚΑΙΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΟΥ ΑΜΟΙΒΑΙΟΥ ΑΠΟΚΛΕΙΣΜΟΥ ΜΕ ΧΡΗΣΗ TEST&SET()...........137

ΣΧΗΜΑ 83: ∆ΙΚΑΙΗ ΛΥΣΗ ΣΤΟ ΠΡΟΒΛΗΜΑ ΤΩΝ ΑΝΑΓΝΩΣΤΩΝ-ΕΓΓΡΑΦΕΩΝ. ................................................139 ΣΧΗΜΑ 84: ΕΝΕΡΓΕΙΕΣ ΠΟΥ ΕΠΙΤΕΛΟΥΝ ΟΙ ΛΕΙΤΟΥΡΓΙΕΣ SEM_DOWN() ΚΑΙ GSEM_UP()...............................141 ΣΧΗΜΑ 85: ΥΛΟΠΟΙΗΣΗ ΓΕΝΙΚΟΥ ΣΗΜΑΦΟΡΟΥ ΑΠΟ ∆ΥΑ∆ΙΚΟ ΣΗΜΑΦΟΡΟ. ................................................141 ΣΧΗΜΑ 86: ΣΚΕΛΕΤΟΣ ΚΩ∆ΙΚΑ ΓΙΑ ΤΗΝ ΥΛΟΠΟΙΗΣΗ ΚΡΙΣΙΜΩΝ ΠΕΡΙΟΧΩΝ ΥΠΟ ΣΥΝΘΗΚΗ ΧΡΗΣΙΜΟΠΟΙΩΝΤΑΣ ΣΗΜΑΦΟΡΟΥΣ. ....................................................................................................................................143

1ο Κεφάλαιο Εισαγωγή

1ο Κεφάλαιο

Εισαγωγή

1.1 Τι Είναι Λειτουργικό Σύστηµα – Σκοπός & Παρεχόµενες Υπηρεσίες Ένα Λειτουργικό Σύστηµα (ΛΣ) είναι ένα πρόγραµµα που λειτουργεί σαν διεπιφάνεια χρήσης (user interface) µεταξύ του χρήστη και του υλικού (hardware) ενός υπολογιστή. Ένα υπολογιστικό σύστηµα απαρτίζεται από (1) το υλικό, (2) το λειτουργικό σύστηµα, (3) διάφορα προγράµµατα εφαρµογών, και (4) τους χρήστες. Η τοποθέτηση του ΛΣ στο σύστηµα φαίνεται στο Σχήµα 1. Χρήστης 1

Χρήστης 2

Χρήστης 3

Βάση ∆εδοµένων Επεξεργαστής Κειµένου

Προγράµµατα Συστήµατος & Εφαρµογών Μεταγλωττιστής

Εκδότης

Λειτουργικό Σύστηµα

Υλικό

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

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

1ο Κεφάλαιο

Εισαγωγή

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

1.2 Σύντοµη Ανασκόπηση Υλικού Υπολογιστών Η κεντρική µονάδα επεξεργασίας (ΚΜΕ) είναι το βασικότερο συστατικό ενός υπολογιστή. Εκεί γίνεται η επεξεργασία των δεδοµένων κατά την εκτέλεση ενός προγράµµατος. Η ΚΜΕ εκτελεί επαναληπτικά τα εξής. ∆ιαβάζει από τη µνήµη την επόµενη προς εκτέλεση εντολή, την αποκωδικοποιεί και την εκτελεί. Η εκτέλεση των λειτουργιών αυτών είναι γνωστή ως κύκλος λειτουργίας ΚΜΕ. Η ΚΜΕ ξεκινά µε την πρώτη εντολή ενός προγράµµατος (1ος κύκλος λειτουργίας) και συνεχίζει µε όσους κύκλους είναι απαραίτητοι για να εκτελεστούν όλες οι εντολές του προγράµµατος. Κάθε χρονική στιγµή µπορεί να εκτελείται µόνο ένα πρόγραµµα στην ΚΜΕ. Ένα πρόγραµµα µπορεί να εκτελεστεί µόνο αν υπάρχει διαθέσιµη ΚΜΕ που θα το εκτελέσει. Κάθε υπολογιστής έχει ένα ρολόι συστήµατος (system clock) που χρησιµοποιείται από την ΚΜΕ για να συγχρονίζει τις λειτουργίες επεξεργασίας. Ένας κύκλος λειτουργίας συνήθως απαιτεί έναν µικρό σταθερό αριθµό από χτύπους του ρολογιού συστήµατος. Η ΚΜΕ χρησιµοποιεί ένα µικρό αριθµό γρήγορων στοιχείων αποθήκευσης που ονοµάζονται καταχωρητές. Ένας καταχωρητής συνήθως µπορεί να αποθηκεύσει δεδοµένα των 32 ή των 64 bit. Μερικοί καταχωρητές χρησιµοποιούνται για την εκτέλεση πολύ συγκεκριµένων λειτουργιών. Ένας τέτοιος καταχωρητής είναι ο µετρητής προγράµµατος (Program Counter, PC). Ο καταχωρητής αυτός περιέχει την πληροφορία του ποια είναι η επόµενη προς εκτέλεση εντολή του εκτελούµενου προγράµµατος. Οι καταχωρητές είναι ενσωµατωµένοι στο chip του επεξεργαστή και έτσι µπορούν να προσπελαστούν πάρα πολύ γρήγορα (µε την ίδια ταχύτητα όπως εκείνη της λειτουργίας 10

1ο Κεφάλαιο

Εισαγωγή

της ΚΜΕ), αλλά είναι εξαιρετικά ακριβοί. Έτσι, οι σηµερινοί υπολογιστές περιέχουν έναν µικρό µόνο αριθµό καταχωρητών που µπορούν να αποθηκεύουν µόνο µερικές δεκάδες ή µερικές εκατοντάδες bytes. Ωστόσο, απαιτείται η ύπαρξη πολύ µεγαλύτερου χώρου αποθήκευσης προκειµένου να αποθηκεύονται εκεί ολόκληρα προγράµµατα, καθώς και τα δεδοµένα που χρησιµοποιούνται από αυτά. Για το λόγο αυτό, το δεύτερο βασικό συστατικό κάθε υπολογιστή είναι η µνήµη. Η µνήµη χρησιµοποιείται ως χώρος αποθήκευσης και επεξεργασίας από τα προγράµµατα και περιέχει προσωρινά δεδοµένα (που χάνονται όταν πάψει να λειτουργεί ο υπολογιστής). Μπορεί να αποθηκεύει πολύ περισσότερα δεδοµένα από ότι οι καταχωρητές, αλλά είναι και αυτή πεπερασµένη. Ένα πρόγραµµα δεν µπορεί να εκτελεστεί αν δεν βρίσκεται στη µνήµη του υπολογιστή. Έτσι, προκειµένου να εκτελεστεί ένα πρόγραµµα θα πρέπει να φορτωθεί στη µνήµη. Η ΚΜΕ επικοινωνεί µε τη µνήµη (και τις υπόλοιπες µονάδες του υπολογιστή) διαµέσου του διαύλου του συστήµατος (system bus). Ο δίαυλος αποτελείται από έναν αριθµό καναλιών µέσω των οποίων γίνεται η ανταλλαγή των πληροφοριών µεταξύ των διαφορετικών µονάδων του συστήµατος (π.χ., µεταξύ µνήµης και ΚΜΕ). Ένας υπολογιστής χρησιµοποιεί πολλά είδη µνήµης. Τα δύο βασικότερα χαρακτηριστικά µιας µονάδας µνήµης είναι η ταχύτητα προσπέλασής της και το κόστος της. ∆υστυχώς, τα δύο αυτά χαρακτηριστικά αντιπαλεύονται το ένα το άλλο. Γρήγορες µνήµες είναι εξαιρετικά ακριβές για να µπορούν να παρέχονται σε µεγάλες ποσότητες. Έτσι, οι σηµερινοί υπολογιστές χρησιµοποιούν πολλές διαφορετικές µονάδες µνήµης που δηµιουργούν µια ιεραρχία. Η ιεραρχία αυτή φαίνεται στο Σχήµα 2. Στη βάση της ιεραρχίας βρίσκονται οι µονάδες εξωτερικής αποθήκευσης, όπως π.χ., µονάδες ταινίας, µαγνητικοί δίσκοι, οπτικοί δίσκοι, κλπ. Οι µονάδες αυτές έχουν το µικρότερο κόστος αλλά το µεγαλύτερο χρόνο προσπέλασης. Παρέχονται σε µεγάλες ποσότητες αλλά είναι εξαιρετικά αργές. Στο επόµενο επίπεδο της ιεραρχίας είναι οι (ηλεκτρονικοί) δίσκοι που είναι σχετικά φθηνοί αλλά και σχετικά αργοί (παρότι σαφώς πιο γρήγοροι από τις µονάδες εξωτερικής αποθήκευσης). Στη συνέχεια, βρίσκεται η κύρια µνήµη (main memory). Στη µονάδα αυτή αποθηκεύονται τα εκτελέσιµα προγράµµατα, καθώς και τα δεδοµένα που απαιτούνται για την εκτέλεση τους. Ο χρόνος προσπέλασης της κύριας µνήµης είναι σχετικά µικρός και σταθερός (αν και αρκετά µεγαλύτερος από το χρόνο προσπέλασης των καταχωρητών). Όπως είναι αναµενόµενο, οι µονάδες κύριας µνήµης είναι αρκετά ακριβότερες από τις µονάδες βοηθητικής αποθήκευσης (δίσκοι, µονάδες εξωτερικής αποθήκευσης). Το µέγεθος της κύριας µνήµης επηρεάζει σηµαντικά την ισχύ ενός υπολογιστή. Περισσότερη µνήµη σηµαίνει πως ο υπολογιστής µπορεί να εκτελεί περισσότερο απαιτητικά σε µνήµη προγράµµατα και µάλιστα περισσότερα του ενός από αυτά ταυτόχρονα. Όταν ένα πρόγραµµα εκτελείται, τα δεδοµένα που χρειάζονται θα πρέπει να τοποθετηθούν στους καταχωρητές του συστήµατος. Η ΚΜΕ λειτουργεί σε υψηλότερες ταχύτητες από τη µνήµη. Έτσι, πολύς χρόνος καταναλώνεται για την µετακίνηση δεδοµένων µεταξύ ΚΜΕ και κύριας µνήµης. Για τη µείωση του χρόνου αυτού, κάθε υπολογιστικό σύστηµα χρησιµοποιεί µία ή περισσότερες κρυφές µνήµες. Η κρυφή µνήµη βρίσκεται µεταξύ του επιπέδου της κύριας µνήµης και των καταχωρητών στην ιεραρχία του Σχήµατος 2. Οι κρυφές µνήµες είναι πολύ γρήγορες, αλλά έχουν µεγάλο κόστος και εποµένως το µέγεθος τους είναι περιορισµένο (αρκετά µικρότερο από εκείνο της κύριας µνήµης). 11

1ο Κεφάλαιο

Εισαγωγή

Καταχωρητές Κρυφή Μνήµη Κύρια Μνήµη (Ηλεκτρονικοί) ∆ίσκοι Μονάδες Εξωτερικής Αποθήκευσης (Μαγνητικοί ∆ίσκοι, Μαγνητικές Ταινίες, Οπτικοί ∆ίσκοι) Σχήµα 2: Ιεραρχία Μονάδων Αποθήκευσης

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

1.3 Θέµατα Απόδοσης Τα υπολογιστικά συστήµατα είναι αρκετά ακριβά. Οι χρήστες τους θα ήθελαν εποµένως η χρησιµοποίησή τους να είναι όσο το δυνατόν µεγαλύτερη (και ει δυνατόν 100%). Με άλλα λόγια, θα ήταν επιθυµητό, η ΚΜΕ να χρησιµοποιείται διαρκώς, χωρίς να µένει ποτέ ανενεργή. Τα προγράµµατα των χρηστών αποτελούνται από αλληλουχίες εκτέλεσης υπολογισµών και λειτουργιών Ε/Ε. Ένα πρόγραµµα επιτελεί λειτουργίες Ε/Ε αν δεν µπορεί να χρησιµοποιήσει την ΚΜΕ επειδή περιµένει κάποια εξωτερική συσκευή να ολοκληρώσει την εργασία της. Όσο ένα πρόγραµµα επιτελεί λειτουργίες Ε/Ε, η ΚΜΕ παραµένει ανενεργή περιµένοντας το τέλος των λειτουργιών αυτών (π.χ., τη µεταφορά σε έναν καταχωρητή ενός δεδοµένου που απαιτείται για να συνεχιστούν οι υπολογισµοί). Αυτό όµως δεν είναι επιθυµητό. ∆ιάφορες τεχνικές για την παράλληλη λειτουργία των µονάδων Ε/Ε και της ΚΜΕ έχουν υιοθετηθεί. Κάποιες από αυτές περιγράφονται συνοπτικά στη συνέχεια.

12

1ο Κεφάλαιο

1.3.1

Εισαγωγή

Ενδιάµεση Προσωρινή Αποθήκευση (Buffering)

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

1.3.2

Παροχέτευση (Spooling)

Η τεχνική της παροχέτευσης (Spooling – Simultaneous Peripheral Operation On Line) χρησιµοποιείται σήµερα από όλα τα σύγχρονα λειτουργικά συστήµατα. Η βασική ιδέα είναι να χρησιµοποιείται µεγάλο µέρος του δίσκου σαν ένας τεράστιος προσωρινός χώρος αποθήκευσης. Όταν ένα πρόγραµµα εκτελείται διαβάζει τα δεδοµένα εισόδου του 13

1ο Κεφάλαιο

Εισαγωγή

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

1.3.3

Πολυ-προγραµµατισµός (Multiprogramming)

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

1ο Κεφάλαιο

Εισαγωγή

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

1.3.4

∆ιαµοιρασµός Χρόνου (Time Sharing)

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

Τα προγράµµατα των χρηστών είναι συνήθως µικρά και απαιτούν πολύ Ε/Ε.



Η ταχύτητα µε την οποία ο χρήστης παρέχει δεδοµένα στο σύστηµα (π.χ., η ταχύτητα πληκτρολόγησης) είναι εξαιρετικά µικρή σε σχέση µε εκείνη της ΚΜΕ.



Η εναλλαγή των προγραµµάτων γίνεται πολύ γρήγορα, ώστε να µην είναι αντιληπτή.

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

1ο Κεφάλαιο

Εισαγωγή

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

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

1.4.1

Προστασία Ε/Ε

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

1ο Κεφάλαιο

Εισαγωγή

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

1.4.2

Προστασία ΚΜΕ

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

1.4.3

Προστασία ∆ιευθυνσιοδότησης Μνήµης

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

17

1ο Κεφάλαιο

Εισαγωγή

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

Καταχωρητής Βάσης

Όχι ΚΜΕ

< Ναι Παγίδευση προς τον πυρήνα

Ναι

<

ΜΝΗΜΗ

Όχι Παγίδευση προς τον πυρήνα

Σχήµα 3: Προστασία διευθυνσιοδότησης µνήµης µε χρήση των καταχωρητών βάσης και ορίου.

1.5 Τα Συστατικά Ενός Λειτουργικού Συστήµατος Μια διεργασία είναι ένα πρόγραµµα που εκτελείται. Περισσότερα για τις διεργασίες θα συζητηθούν στο Κεφάλαιο 2. Ένα σηµαντικό συστατικό ενός ΛΣ είναι ο διαχειριστής διεργασιών. Μερικές από τις λειτουργίες για τις οποίες είναι υπεύθυνος ο διαχειριστής διεργασιών είναι οι ακόλουθες: •

∆ηµιουργία και τερµατισµό διεργασιών (είτε συστήµατος, είτε χρήστη). 18

1ο Κεφάλαιο

Εισαγωγή



Αναστολή εκτέλεσης διεργασιών, καθώς και επαναπόδοση της ΚΜΕ σε αυτές.



Παροχή µηχανισµών που επιτρέπουν την επικοινωνία µεταξύ διεργασιών.



Παροχή µηχανισµών για την επίτευξη συγχρονισµού µεταξύ διεργασιών που επικοινωνούν ή µοιράζονται πόρους.

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

∆ιατήρηση πληροφοριών για τα µέρη της µνήµης που χρησιµοποιούνται, καθώς και για εκείνα που είναι αχρησιµοποίητα κάθε χρονική στιγµή.



Επιλογή διεργασιών που βρίσκονται κάθε χρονική στιγµή στη µνήµη.



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

∆ιαφορετικές τεχνικές διαχείρισης µνήµης θα µελετηθούν αναλυτικά στο κεφάλαιο 4. Το σύγγραµµα αυτό, ως εισαγωγικό στο χώρο των ΛΣ, επικεντρώνεται µόνο στα παραπάνω δύο σηµαντικότατα συστατικά των ΛΣ. Ωστόσο, ένα ΛΣ απαρτίζεται από πολύ περισσότερα συστατικά τα οποία αναφέρονται περιληπτικά στη συνέχεια. Ένα αρχείο είναι µια συλλογή από σχετιζόµενες πληροφορίες. Τα αρχεία αποτελούνται είτε από µια ακολουθία χαρακτήρων είτε έχουν κάποιου είδους µορφοποίηση (π.χ., είναι αρχεία εγγραφών). Ένα αρχείο µπορεί να αποθηκευτεί σε όλα τα δυνατά µέσα αποθήκευσης που χρησιµοποιεί ένα υπολογιστικό σύστηµα, π.χ., δίσκους, CDs, δισκέτες, κλπ. Κάθε ένα από τα µέσα αποθήκευσης έχει τις δικές του ιδιαιτερότητες. Τα αρχεία αποτελούν λογικές µονάδες αποθήκευσης που λειτουργούν αφαιρετικά ως προς τις ιδιαιτερότητες αυτές, τις οποίες χρειάζεται να γνωρίζει µόνο το ΛΣ. Έτσι, ένα ακόµη συστατικό κάθε ΛΣ είναι ο διαχειριστής αρχείων, ο οποίος είναι υπεύθυνος για την δηµιουργία και την διαγραφή αρχείων, την οργάνωση των αρχείων σε καταλόγους, την

19

1ο Κεφάλαιο

Εισαγωγή

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

Χειρισµός διακοπών.



∆ηµιουργία και τερµατισµός διεργασιών.



Μέρος του χρονοδροµολογητή.



Συντονισµός διεργασιών.



Αναστολή και αφύπνιση διεργασιών.



Υποστήριξη δραστηριοτήτων Ε/Ε.



Υποστήριξη δέσµευσης και αποδέσµευσης µνήµης, κ.α.

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

20

1ο Κεφάλαιο

Εισαγωγή

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

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

21

1ο Κεφάλαιο

Εισαγωγή

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

Οι εργασίες υποβάλλονται στον Η/Υ κατά οµάδες.



∆εν είναι δυνατή η αλληλεπίδραση ανάµεσα στον χρήστη και στο πρόγραµµα κατά τη διάρκεια της επεξεργασίας.



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

β) ΛΣ ∆ιαµοιρασµού Χρόνου. •

Επιτρέπει την παράλληλη εξυπηρέτηση πολλών χρηστών.



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



∆ιαµοιράζει τον χρόνο της ΚΜΕ µεταξύ των χρηστών, δηµιουργώντας την εντύπωση της παράλληλης επεξεργασίας.

γ) ΛΣ Πραγµατικού Χρόνου. •

Εξυπηρετεί εργασίες που απαιτούν αυστηρά καθορισµένο όριο χρόνου απόκρισης.

2. Είναι φανερό ότι τα συστήµατα οµαδικής επεξεργασίας είναι τα απλούστερα, ενώ τα συστήµατα πραγµατικού χρόνου είναι τα συνθετότερα. Μια απλή εφαρµογή που µπορεί να εξυπηρετηθεί από ένα σύστηµα οµαδικής επεξεργασίας, είναι επόµενο να µπορεί επίσης να εξυπηρετηθεί και από ένα σύστηµα διαµοιρασµού χρόνου και από ένα σύστηµα πραγµατικού χρόνου. Έτσι, το ερώτηµα ουσιαστικά ζητάει τον απλούστερο τύπο ΛΣ που µπορεί να εξυπηρετήσει ικανοποιητικά την κάθε εφαρµογή. Λέµε «ικανοποιητικά» γιατί µια εξίσου σηµαντική παράµετρος για την επιλογή µας είναι η αποδοτικότητα. Για παράδειγµα, ένα σύστηµα που χρησιµοποιεί εξεζητηµένους αλγόριθµους χρονοδροµολόγησης, µπορεί πράγµατι να εξυπηρετήσει µια απλή εφαρµογή, όµως ενδεχοµένως να σπαταλά πολύ χρόνο για τη

22

1ο Κεφάλαιο

Εισαγωγή

χρονοδροµολόγηση, χωρίς να χρειάζεται (επειδή η εφαρµογή είναι απλή). Σύµφωνα µε τα παραπάνω, οι καταλληλότερες επιλογές έχουν ως εξής: •

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



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



Οι παιχνιδοµηχανές είναι στην πραγµατικότητα πολύ απαιτητικά συστήµατα. Πρέπει να έχουν τη δυνατότητα να εξυπηρετούν παράλληλα πολλούς παίκτες, οι αντιδράσεις των οποίων θα πρέπει να επιδρούν στο παιχνίδι ακαριαία. Ένα ΛΣ πραγµατικού χρόνου απαιτείται για την υποστήριξη µιας ποιοτικής παιχνιδοµηχανής. Μια λιγότερο απαιτητική παιχνιδοµηχανή ενδεχοµένως να µπορούσε να υποστηριχθεί από ένα γρήγορο σύστηµα διαµοιρασµού χρόνου.



Στο σύστηµα για ανάπτυξη εφαρµογών το κατάλληλο ΛΣ είναι αυτό του διαµοιρασµού χρόνου. Επιτρέπει την φαινοµενικά παράλληλη εκτέλεση πολλών εργασιών (πχ. editor, help browser, παράθυρο εκτέλεσης προγράµµατος, κλπ), χωρίς να είναι κρίσιµος ο συγχρονισµός τους.

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

1ο Κεφάλαιο

Εισαγωγή

στ) Οι λειτουργίες Εισόδου/Εξόδου πρέπει να είναι προνοµιακές εντολές για τους λόγους που συζητήθηκαν στην ενότητα «Προστασία Ε/Ε». □

1.8 Μορφή ∆ιεργασίας

Ψευδοκώδικα

Περιγραφής

Προγράµµατος

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

1.8.1

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

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

1.8.2

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

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

1ο Κεφάλαιο

Εισαγωγή

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

1.8.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;

25

1ο Κεφάλαιο

Εισαγωγή

που υποδηλώνει την επαναληπτική εκτέλεση των προτάσεων για πάντα (αφού δεν υπάρχει συνθήκη τερµατισµού, η 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, αντίστοιχα. Παράδειγµα 1 Θεωρήστε το ακόλουθο απλό πρόγραµµα: int num = 0; while (num < 3) num = num+1; print num;

26

1ο Κεφάλαιο

Εισαγωγή

Ας εξετάσουµε τι θα εκτελέσει το πρόγραµµα αυτό. Η 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, κ.α. (αλλά δεν θα επεκταθούµε εδώ).

1.8.4

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

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

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

1ο Κεφάλαιο

Εισαγωγή

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

Η συνθήκη αποτιµάται. Αν είναι TRUE εκτελείται το µπλοκ εντολών 1, ενώ διαφορετικά εκτελείται το µπλοκ εντολών 2.

1.8.5

Πρόταση goto

Η πρόταση goto έχει δύο µέρη, το goto και ένα όνοµα ετικέτας: goto part1;

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

Η εντολή goto καθορίζει ότι η επόµενη προς εκτέλεση εντολή είναι η εντολή που ακολουθεί την ετικέτα part1. Έτσι, η goto αλλάζει τη ροή εκτέλεσης του προγράµµατος.

1.8.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

28

1ο Κεφάλαιο

Εισαγωγή

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

Η b είναι µια µεταβλητή τύπου δοµής 2fields. Η b αποτελείται από δύο πεδία, την ακέραια µεταβλητή b.a και την boolean µεταβλητή b.flag.

1.8.7

Typedef

Η typedef επιτρέπει τη δηµιουργία ενός νέου ονόµατος για κάποιο τύπο. Ας επανέλθουµε στον τύπο δοµής 2fields. Ο ορισµός: typedef struct 2fields 2FIELDS;

υποδηλώνει ότι το όνοµα 2FIELDS µπορεί να χρησιµοποιηθεί αντί των δύο λέξεων struct 2fields. Έτσι, η δήλωση: 2FIELDS b;

είναι ένας ακόµη τρόπος να δηλώσουµε µια µεταβλητή b τύπου δοµής 2fields. Οµοίως, ο ορισµός: typedef int boolean;

υποδηλώνει ότι η λέξη boolean µπορεί να χρησιµοποιείται αντί του int για τη δήλωση ακεραίων.

1.8.8

Συναρτήσεις

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

1.8.9

Άλλα Θέµατα

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

29

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

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

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

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

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

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

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

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

32

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

έχει το δικό της ξεχωριστό PCB. Το PCB µιας διεργασίας συνήθως περιέχει τις ακόλουθες πληροφορίες: •

Περιεχόµενα µετρητή προγράµµατος και άλλων καταχωρητών.



Κατάσταση διεργασίας.



∆ιάφορες παραµέτρους χρονοδροµολόγησης.



Ταυτότητα διεργασίας και άλλα στοιχεία ταυτοποίησης της διεργασίας.



Χρόνος εκκίνησης της διεργασίας, χρόνος χρήσης της ΚΜΕ και άλλα λογιστικά στοιχεία.



∆είκτες που καταγράφουν τις διευθύνσεις µνήµης στις οποίες βρίσκεται ο κώδικας, τα δεδοµένα και η στοίβα της διεργασίας.



∆είκτες που καταγράφουν τους πόρους που έχει στην κατοχή της η διεργασία (π.χ., ανοιχτά αρχεία, συσκευές Ε/Ε, κλπ).

Το PCB µιας διεργασίας φαίνεται στο Σχήµα 5. Κατάσταση διεργασίας

Στοιχεία ταυτοποίησης

Περιεχόµενα µετρητή προγράµµατος Περιεχόµενα άλλων καταχωρητών Παράµετροι χρονοδροµολόγησης Όρια µνήµης Λίστα ανοιχτών αρχείων Χρησιµοποιούµενοι πόροι Λογιστικά στοιχεία ... (άλλες χρήσιµες πληροφορίες) Σχήµα 5: Το PCB µιας διεργασίας.

2.4 Λειτουργίες επί ∆ιεργασιών Οι πιο συνηθισµένες λειτουργίες που µπορούν να εκτελεστούν επί των διεργασιών είναι: •

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

33

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

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

Τερµατισµός διεργασίας. Εκτός από τον κανονικό τερµατισµό µιας διεργασίας που γίνεται όταν εκτελεστεί ο κώδικάς της, το ΛΣ µπορεί να αποφασίσει να τερµατίσει µια διεργασία για άλλους λόγους, π.χ., για λόγους προστασίας. Όταν τερµατίζει µια διεργασία, το PCB της καταστρέφεται και όλοι οι πόροι που κατείχε ελευθερώνονται.



Αναστολή-Επανενεργοποίηση διεργασίας. Μια διεργασία µπορεί να ανασταλεί επειδή π.χ., περιµένει την εκτέλεση κάποιας λειτουργίας Ε/Ε. Μια διεργασία συνήθως αναστέλλεται κάθε φορά που καλεί µια κλήση συστήµατος. Η επανενεργοποίηση της διεργασίας είναι αποκλειστική ευθύνη του ΛΣ και γίνεται µόνο µετά από ενέργειες που πραγµατοποιούνται από αυτό.



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

2.5 ∆ιακοπές Μια διακοπή µπορεί να προκληθεί για πολλούς λόγους. Οι πιο σηµαντικοί από αυτούς παρατίθενται στη συνέχεια: •

∆ιακοπές κλήσεις επιβλέποντος. Πραγµατοποιούνται κάθε φορά που µια διεργασία καλεί µια κλήση συστήµατος.



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



∆ιακοπές που προκαλούνται από το ρολόι του συστήµατος, διακοπές ελέγχου του υλικού του συστήµατος, κ.α.



∆ιακοπές Ε/Ε. Είναι διακοπές που προκαλούνται από τις συσκευές Ε/Ε, όταν µια λειτουργία Ε/Ε περατωθεί, όταν συµβεί ένα λάθος, όταν ένα περιφερειακό είναι έτοιµο για λειτουργία, κλπ.



∆ιακοπές που προκαλούνται από τον χρήστη µε την πίεση κάποιων πλήκτρων, όπως π.χ., το restart και το INT.

∆ιακοπές µπορεί να προκληθούν για πολλούς ακόµη διαφορετικούς λόγους (οι παραπάνω κατηγορίες δεν είναι εξαντλητικές). ∆εδοµένου του κώδικα µιας διεργασίας, είναι πολύ σηµαντικό να γίνει σαφές ποια είναι τα δυνατά σηµεία διακοπής της εκτέλεσης της διεργασίας. Στο παρακάτω παράδειγµα γίνεται προσπάθεια να αποσαφηνιστεί το ερώτηµα αυτό. Παράδειγµα 2 Θεωρείστε µια διεργασία που εκτελεί τον παρακάτω απλό κώδικα. int b = 3, c = 4, p = 5; int tmp, flag = 0;

34

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

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 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 35

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

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). Κάθε µια από αυτές είναι υπεύθυνη για την επεξεργασία κάποιου είδους διακοπής. Κάθε φορά που προκαλείται µια διακοπή, οι ενέργειες που πρέπει να πραγµατοποιηθούν περιγράφονται συνοπτικά στη συνέχεια: •

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



Το είδος της διακοπής αποσαφηνίζεται και καλείται ο κατάλληλος χειριστής διακοπής.



Όταν ο χειριστής διακοπής ολοκληρώσει τις λειτουργίες που πρέπει να επιτελέσει, αρχίζει να εκτελείται ο χρονοδροµολογητής που αποφασίζει ποια θα είναι η επόµενη προς εκτέλεση διεργασία. 36

2ο Κεφάλαιο •

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

Τέλος, ξεκινά η εκτέλεση της διεργασίας στην οποία αποδόθηκε η ΚΜΕ.

Το διάγραµµα του Σχήµατος 6 αναπαριστά τον τρόπο µε τον οποίο επιτυγχάνεται η εναλλαγή των διεργασιών στην ΚΜΕ.

Σχήµα 6: Εναλλαγή διεργασιών.

Παράδειγµα 3 Ας εστιάσουµε και πάλι στον (ψευδο-) κώδικα του Παραδείγµατος 2. Είναι γραµµένος σε µορφή που θυµίζει κώδικα κάποιας σύγχρονης γλώσσας προγραµµατισµού (αφού περιέχει δοµηµένες προτάσεις, όπως π.χ., η while και η if else). Προκειµένου να εκτελεστεί, ο κώδικας θα πρέπει να µετατραπεί σε µορφή που να θυµίζει περισσότερο γλώσσα µηχανής. Μια τέτοια µορφή παρουσιάζεται στη συνέχεια: 2012 2016 2020 2024 2028 2032 2036 2040 2044 2048 2052 2056 2060 2064 2068 2072 2076 2080

b = 3; c = 4; p = 5; flag = 0; tmp = 1; label1: if (p <= 0) goto label2; if (flag == 0) goto label3; tmp = tmp * c; goto label4; label3: tmp = tmp * b; label4: flag = 1 – flag; p = p – 1; goto label1; label2: print tmp;

Κάθε ένας από τους αριθµούς που προηγούνται των εντολών στον κώδικα αποτελεί τη θέση µνήµης από όπου διαβάζεται αυτή η εντολή (δηλαδή ο κώδικας της διεργασίας βρίσκεται στις διευθύνσεις µνήµης 2012-2080, ενώ στη διεύθυνση µνήµης 2056 βρίσκεται η εντολή «tmp = tmp * b;»).

37

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

Ο αναγνώστης θα πρέπει να επενδύσει τον απαραίτητο χρόνο για να κατανοήσει γιατί όταν ο παραπάνω κώδικας εκτελείται, επιτελεί ακριβώς τις ίδιες λειτουργίες µε εκείνες που περιγράφονται στο Παράδειγµα 2 (είναι δηλαδή ισοδύναµος µε τον κώδικα του Παραδείγµατος 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 της διεργασίας, προκειµένου να είναι σε θέση να επανεκτελέσει την διεργασία αργότερα. □ Παράδειγµα 4 (Καλλές, Σγάρµπας, Ταµπακάς, «Σηµαντικά Σηµεία στη µελέτη του τόµου Λειτουργικά Συστήµατα Ι της ΘΕ ΠΛΗ-11: Αρχές Τεχνολογίας Λογισµικού) Έστω µια διεργασία που στόχος της είναι να πληροφορεί το χρήστη για το ρυθµό µε τον οποίο αυξοµειώνεται το πλήθος όλων των διεργασιών που εξυπηρετούνται από το ΛΣ σε κάποιο χρονικό διάστηµα. Έστω ότι ο αριθµός των διεργασιών που εκτελούνται την τρέχουσα χρονική στιγµή στο σύστηµα δίνεται από τη συνάρτηση count-processes(). Το πρόγραµµα που περιγράφεται στη συνέχεια επιτελεί 100 δειγµατοληψίες (δηλαδή καλεί 100 φορές τη συνάρτηση count-processes()) και αναφέρει αν ο αριθµός των διεργασιών παρουσιάζει αύξηση ή µείωση από την τελευταία δειγµατοληψία, καθώς και τον τρέχοντα αριθµό διεργασιών στο σύστηµα. Όταν εκτελείται αποτελεί και αυτό µια από τις διεργασίες του συστήµατος. Το πρόγραµµα δίνεται στη συνέχεια σε µορφή που είναι αρκετά κοντά σε µια αναπαράσταση σε επίπεδο µηχανής (δηλαδή δεν περιέχει εντολές ανακύκλωσης, όπως π.χ., while, repeat, for, κλπ.) 1004

print “Start”;

38

2ο Κεφάλαιο 1008 1012 1016 1020 1024 1028 1032 1036 1040 1044 1048 1052 1056 1060 1064 1068

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

i = 0; old_proc = 0; label1: if (i == 100) goto label2; new_proc = count_processes (); if (new_proc > old_proc) print “Up”; else if (old_proc > new_proc) print “Down”; print new_proc; old_proc = new_proc; i ++; goto label1: label2: 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 των

39

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

διεργασιών που είναι έτοιµες προς εκτέλεση. Μια ουρά έτοιµων διεργασιών για ένα σύστηµα µε 5 έτοιµες διεργασίες Α, Β, Γ, ∆ και Ε αναπαρίσταται στο Σχήµα 7. τέλος

PCB A

PCB B

PCB Γ

PCB ∆

PCB Ε

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

Η αρχική κατάσταση κάθε διεργασίας είναι έτοιµη. Εποµένως, το PCB κάθε διεργασίας που εκκινείται εισάγεται στην ουρά έτοιµων διεργασιών. Είναι σηµαντικό να γίνει κατανοητό ότι παρά το όνοµά της, η υλοποίηση της ουράς έτοιµων διεργασιών δεν γίνεται πάντα µε µια απλή FIFO ουρά. Ο χρονοδροµολογητής διαλέγει µια από τις διεργασίες της ουράς έτοιµων διεργασιών για να εκτελεστεί στην ΚΜΕ. Ο χρονοδροµολογητής εκτελείται µετά από κάποιο από τα ακόλουθα γεγονότα: •

Η εκτελούµενη διεργασία τερµατίζει ή αναστέλλεται προκειµένου να επιτελέσει κάποια λειτουργία Ε/Ε.



Μια νέα διεργασία εισέρχεται στο σύστηµα.



Προκαλείται διακοπή.

Εκτός από την ουρά έτοιµων διεργασιών υπάρχει και ένα σύνολο άλλων ουρών (ή γενικότερα δοµών δεδοµένων) στο σύστηµα. Για παράδειγµα, πολλές από τις συσκευές Ε/Ε έχουν από µια ουρά η κάθε µια. Σε κάθε µια από τις ουρές αυτές αποθηκεύονται τα PCB των διεργασιών που έχουν ανασταλεί περιµένοντας τη διεκπεραίωση κάποιας λειτουργίας από την αντίστοιχη συσκευή Ε/Ε. Μια διεργασία που εκτελείται µπορεί είτε να ανασταλεί (περιµένοντας την περάτωση λειτουργιών Ε/Ε), ή να διακοπεί (παρότι θα µπορούσε να συνεχίσει την εκτέλεσή της αν εξακολουθούσε να κατέχει την ΚΜΕ), ή να τερµατίσει. Στην πρώτη περίπτωση, το PCB της διεργασίας εισάγεται στην κατάλληλη ουρά Ε/Ε. Στην δεύτερη, η κατάσταση της διεργασίας µετατρέπεται από εκτελούµενη σε έτοιµη και έτσι εισάγεται στην ουρά των έτοιµων διεργασιών, ενώ στην τρίτη περίπτωση, το PCB της διεργασίας καταστρέφεται. Το Σχήµα 8 παρέχει µια σχηµατική περιγραφή της χρονοδροµολόγησης διεργασιών. Υπάρχουν δύο µεγάλες κατηγορίες αλγορίθµων χρονοδροµολόγησης, οι προεκχωρητικοί και οι µη-προεκχωρητικοί. Οι µη-προεκχωρητικοί αλγόριθµοι, αφού διαλέξουν τη διεργασία που θα εκτελεστεί, της επιτρέπουν να κρατήσει την ΚΜΕ µέχρι είτε να ανασταλεί (π.χ., γιατί περιµένει την περάτωση απαιτούµενων λειτουργιών Ε/Ε) ή να τερµατίσει. Με άλλα λόγια από τη στιγµή που η ΚΜΕ αποδίδεται από τον 40

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

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

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

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

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

διεργασίας είναι ο χρόνος που µεσολαβεί από την είσοδό της στο σύστηµα µέχρι την περάτωση της εκτέλεσής της. Για παράδειγµα αν µια διεργασία εισέρχεται στο σύστηµα την χρονική στιγµή t1 και τερµατίζει την χρονική στιγµή t2, o χρόνος διεκπεραίωσης της διεργασίας είναι Χ∆ = t2 – t1 (αφού t2-t1 χρονικές µονάδες παρέµεινε στο σύστηµα). Ο χρόνος διεκπεραίωσης συµπεριλαµβάνει οποιαδήποτε καθυστέρηση µπορεί να υπέστη η διεργασία πριν ή και κατά τη διάρκεια εκτέλεσής της. Ο µέσος χρόνος διεκπεραίωσης n διεργασιών p1, ..., pn, ορίζεται ως ΜΧ∆ = (Χ∆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, αντίστοιχα. Στα συστήµατα οµαδικής επεξεργασίας είναι σηµαντικό να γίνεται επίσης βέλτιστη χρήση της ΚΜΕ και να µεγιστοποιείται ο ρυθµός απόδοσης, δηλαδή ο αριθµός των διεργασιών που διεκπεραιώνονται στη µονάδα του χρόνου. Αντίθετα, σε ένα σύστηµα διαµοιρασµού χρόνου, ο σηµαντικότερος στόχος είναι να ελαχιστοποιηθεί ο χρόνος απόκρισης, δηλαδή ο χρόνος που µεσολαβεί από τη χρονική στιγµή που ο χρήστης υπέβαλε την διεργασία του µέχρι τη χρονική στιγµή που έλαβε κάποια απόκριση από το σύστηµα. Θα πρέπει να τονιστεί πως κάποιοι από τους παραπάνω στόχους µπορεί να είναι αντικρουόµενοι. ∆εδοµένου ότι τα σηµερινά συστήµατα είναι γενικού σκοπού (και άρα λειτουργούν, ανάλογα µε τις ανάγκες των χρηστών, άλλοτε ως συστήµατα οµαδικής επεξεργασίας και άλλοτε ως συστήµατα απόκρισης), ο σχεδιασµός ενός καλού αλγόριθµου χρονοδροµολόγησης είναι επίπονη εργασία. Υπάρχουν συστήµατα στα οποία οι διεργασίες είναι τόσες πολλές, ώστε δεν είναι εφικτό (ή δεν είναι αποδοτικό) να βρίσκονται όλες ταυτόχρονα στη µνήµη. Οι διεργασίες που δεν χωρούν στη µνήµη κρατούνται σε κάποια δοµή δεδοµένων στο δίσκο και ένας δεύτερος χρονοδροµολογητής, ο χρονοδροµολογητής µακράς διάρκειας, αποφασίζει σε ποιες από αυτές θα αποδοθεί η απαραίτητη µνήµη για την εκτέλεση τους. Με άλλα λόγια ένα σύστηµα µπορεί να έχει περισσότερους από έναν χρονοδροµολογητές και περισσότερες από µια ουρές χρονοδροµολόγησης. Ο χρονοδροµολογητής ΚΜΕ και ο χρονοδροµολογητής µακράς διαρκείας είναι δύο από τα σηµαντικότερα παραδείγµατα. Η κύρια διαφορά τους είναι πως ο χρονοδροµολογητής ΚΜΕ αναλαµβάνει καθήκον πολύ πιο συχνά από τον χρονοδροµολογητή µακράς διαρκείας. Συχνά, παρόµοιοι αλγόριθµοι χρονοδροµολόγησης χρησιµοποιούνται και στις δύο περιπτώσεις. Το Σχήµα 9 αναπαριστά το είδος της χρονοδροµολόγησης που παρέχεται από τους χρονοδροµολογητές ΚΜΕ και µακράς διαρκείας.

42

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών Χρονοδροµολογητής ΚΜΕ

Χρονοδροµολογητής µακράς διάρκειας Μνήµη ∆ίσκος

ΚΜΕ

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

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

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

43

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

Είναι σηµαντικό µικρές υπολογιστικά διεργασίες να χρονοδροµολογούνται όσο το δυνατόν συντοµότερα. Υπάρχουν διάφοροι λόγοι για αυτό. Ένας από αυτούς είναι πως διαφορετικά δεν επιτυγχάνονται καλοί χρόνοι απόκρισης από το σύστηµα. Επιπλέον όµως, οι µικρές υπολογιστικά διεργασίες, αφού εκτελέσουν τους λίγους υπολογισµούς τους, θα συνεχίσουν µε λειτουργίες Ε/Ε που είναι εξαιρετικά πιο αργές, ενώ η ΚΜΕ µπορεί να απασχοληθεί µε τις µεγάλες υπολογιστικά διεργασίες. Με τον τρόπο αυτό, η ΚΜΕ και οι συσκευές Ε/Ε διατηρούνται απασχοληµένες ταυτόχρονα. Παρατηρήστε ότι η εκτέλεση της Β πριν την Α επιφέρει χρονική καθυστέρηση µόνο µιας χρονικής µονάδας στην Α, καθυστέρηση αµελητέα δεδοµένου ότι ο χρόνος απόκρισης της Α δεν µπορεί να είναι µικρότερος από 100 µονάδες. Θα πρέπει να τονιστεί ωστόσο, πως παρότι η χρονοδροµολόγηση µικρών διεργασιών πρώτων είναι µια γενικά αποδεκτή τακτική, θα πρέπει να λαµβάνεται πρόνοια ώστε οι µεγάλες διεργασίες να µην καθυστερούνται επ’ άπειρον. Η καθυστέρηση χρονοδροµολόγησης µιας διεργασίας επ’ άπειρον λέγεται παρατεταµένη στέρηση. Στην περίπτωση χρονοδροµολόγησης µικρών διεργασιών πρώτων, παρατεταµένη στέρηση θα µπορούσε να συµβεί, π.χ., αν υπάρχει µια µεγάλη διεργασία στο σύστηµα αλλά µικρότερες διεργασίες διαρκώς εισέρχονται στο σύστηµα και της παίρνουν τη σειρά. Χρονοδροµολογητές που µπορεί να προκαλέσουν παρατεταµένη στέρηση δεν είναι δίκαιοι και άρα δεν θεωρείται πως επιτελούν το έργο τους µε µεγάλη επιτυχία.

2.7.2

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

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

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

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

2.7.3

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

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

45

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

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

2.7.4

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

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

Προτεραιότητα

Χρόνος Εκτέλεσης

A

3

70 ms

B

1

10 ms

Γ

3

20 ms

46

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών



4

10 ms

Ε

2

50 ms

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

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

∆ 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. Επίσης, ΧΑΑ = 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

48

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών Χρόνος σε 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 παρουσιάζει τους χειρότερους χρόνους διεκπεραίωσης και αναµονής. Έστω ΧΕΑ ο χρόνος εκτέλεσης της Α, ΧΕΒ ο χρόνος εκτέλεσης της Β, κ.ο.κ. Παρατηρήστε ότι, για κάθε αλγόριθµο, ισχύει η σχέση ΜΧ∆ = ΜΧΑ + (ΧΕΑ + ΧΕΒ + ... + ΧΕΕ)/5. Ο ΜΧ∆ και ΜΧΕ µπορεί να είναι διαφορετικοί για κάθε έναν από τους αλγόριθµους, αλλά η ποσότητα (ΧΕΑ + ΧΕΒ + ... + ΧΕΕ)/5 είναι προφανώς ίδια για όλους τους αλγόριθµους. Άρα, ο αλγόριθµος που έχει το µεγαλύτερο ΜΧ∆ έχει και το µεγαλύτερο ΜΧΑ και αντίστροφα. Είναι τέλος αξιοσηµείωτο πως στην περίπτωση που όλες οι διεργασίες είναι διαθέσιµες εξ αρχής (όπως σε αυτό το παράδειγµα), η µη-προεκχωρητική και η προεκχωρητική έκδοση του Priority λειτουργούν µε ακριβώς τον ίδιο τρόπο. Το ίδιο προφανώς ισχύει και για τους SJF και SRTF (αφού αυτοί δεν είναι παρά ειδικές περιπτώσεις των δύο εκδόσεων του Priority). □ Παράδειγµα 6 Ας µελετήσουµε τώρα µια γενικευµένη µορφή του Θέµατος 7 της 4ης γραπτής εργασίας του Ακ. Έτους 2003-04. Θεωρείστε πως οι παρακάτω διεργασίες εισέρχονται στο σύστηµα τις χρονικές στιγµές που αναγράφονται: ∆ιαδικασία

Χρόνος Άφιξης

Χρόνος Εκτέλεσης

Α

1

8

Β

2

4

Γ

3

9



4

5

Μελετήστε το µέσο χρόνο διεκπεραίωσης και το µέσο χρόνο αναµονής για τους ακόλουθους αλγορίθµους: 1. FCFS 49

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

2. SJF 3. SRTF 4. RR µε κβάντο χρόνου 2 χρονικές µονάδες Κάνετε την απλουστευτική παραδοχή ότι ο χρόνος εναλλαγής είναι 0. Τα διαγράµµατα Gantt για κάθε έναν από τους παραπάνω αλγόριθµους φαίνονται στο Σχήµα 11. Κατά την πρώτη χρονική µονάδα το σύστηµα είναι ανενεργό, αφού δεν υπάρχει καµία διεργασία στο σύστηµα. 1. Ο FCFS δροµολογεί τις διεργασίες µε τη σειρά άφιξής τους. Εποµένως, πρώτα δροµολογείται η Α, στη συνέχεια η Β, στη συνέχεια η Γ, και τέλος η ∆. Από το 1ο διάγραµµα Gantt του Σχήµατος 11 παρατηρούµε ότι Χ∆Α = (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 χρονικές µονάδες. Άρα, ΜΧΑ = (ΧΑΑ+ΧΑΒ+ΧΑΓ+ΧΑ∆) / 4 = (0+7+10+18) / 4 χρονικές µονάδες = 8,75 χρονικές µονάδες. 2. Ο SJF ξεκινά µε την διεργασία Α, τη µόνη διαθέσιµη διεργασία στο σύστηµα την χρονική στιγµή 1. Όταν η Α τερµατίζει την χρονική στιγµή 9, οι διεργασίες Β, Γ και ∆ βρίσκονται όλες στο σύστηµα. Ο SJF διαλέγει τη µικρότερη από αυτές, δηλαδή την Β και την εκτελεί. Στη συνέχεια εκτελείται η ∆ και τέλος η Γ. Από το 2ο διάγραµµα Gantt του Σχήµατος 11 παρατηρούµε ότι Χ∆Α = (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 καλείται να διαλέξει εκ 50

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

νέου ποια διεργασία θα εκτελεστεί στην ΚΜΕ. Ο υπολειπόµενος χρόνος της Α είναι 7 µονάδες, ο υπολειπόµενος χρόνος της Β είναι 3 µονάδες και ο απαιτούµενος χρόνος της Γ είναι 9 µονάδες. Εποµένως, η Β συνεχίζει την εκτέλεσή της µέχρι να προκληθεί και νέα διακοπή. Αυτό συµβαίνει την χρονική στιγµή 4, που στο σύστηµα εισέρχεται η ∆. Οι απαιτούµενοι (υπολειπόµενοι) χρόνοι συγκρίνονται και πάλι και ο SRTF αποφασίζει πως η Β εξακολουθεί να είναι η διεργασία µε τη χαµηλότερη απαίτηση σε ΚΜΕ, οπότε και εκτελείται ξανά. Όταν η Β τελειώνει τη χρονική στιγµή 6, εκτελούνται οι ∆, Α και Γ (µε αυτή τη σειρά). Από το 3ο διάγραµµα Gantt του Σχήµατος 11 παρατηρούµε ότι Χ∆Α = (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 χρονικές µονάδες. 4. Για να κατανοήσουµε τον τρόπο εκτέλεσης του RR θα πρέπει να µελετήσουµε σε κάθε χρονική στιγµή τι περιέχει η ουρά έτοιµων εργασιών. Την χρονική στιγµή 0 το σύστηµα είναι ανενεργό, ενώ την 1 έρχεται η Α και ξεκινά να εκτελείται στην ΚΜΕ. Την χρονική στιγµή 2 γίνεται διακοπή και στο σύστηµα καταφθάνει η Β. Τώρα υπάρχουν δύο επιλογές. Είτε ο χρονοδροµολογητής θα τοποθετήσει στην ουρά την Α τη στιγµή της διακοπής και µετά θα τοποθετηθεί στην ουρά και η Β, ή θα γίνει το αντίστροφο. Στην πρώτη περίπτωση, η πρώτη διεργασία στην ουρά είναι η Α και η ΚΜΕ θα αποδοθεί και πάλι σε αυτήν για το επόµενο κβάντο χρόνου, ενώ στη δεύτερη περίπτωση, η πρώτη διεργασία στην ουρά είναι η Β και άρα η ΚΜΕ θα αποδοθεί στην Β για το επόµενο κβάντο χρόνου. Θεωρούµε στη συνέχεια ότι ο χρονοδροµολογητής λειτουργεί όπως καθορίζεται από την πρώτη περίπτωση. Θα πρέπει να τονιστεί πως εξίσου σωστό είναι και να θεωρηθεί ότι ακολουθείται η δεύτερη περίπτωση. Μπορούµε εποµένως να φτιάξουµε έναν πίνακα που θα παρέχει, σε κάθε χρονική στιγµή, τις διεργασίες που εισέρχονται, τη διεργασία που απασχολεί την ΚΜΕ, τις διεργασίες που βρίσκονται στην ουρά έτοιµων διεργασιών, καθώς και τις διεργασίες που τερµατίζουν. Ο πίνακας φαίνεται στο Σχήµα 10. Στη γραµµή i της στήλης «ΚΜΕ» καταγράφεται κάθε φορά η διεργασία που θα απασχολήσει την ΚΜΕ από τη χρονική στιγµή i έως την (i+1). Η διεργασία αυτή ήταν η πρώτη διεργασία στην ουρά έτοιµων διεργασιών την χρονική στιγµή i. Εποµένως, η ουρά διεργασιών την χρονική στιγµή 4 περιέχει τις διεργασίες Α, Γ, Β, ∆ µε τη σειρά που αναγράφονται (παρότι η Α δεν συµπεριλαµβάνεται στη στήλη «Ουρά Έτοιµων ∆ιεργασιών» της γραµµής 4). Η διεργασία Α είναι αυτή που θα κατέχει την ΚΜΕ την χρονική µονάδα που µεσολαβεί από τη χρονική στιγµή 4 έως τη χρονική στιγµή 5. Την χρονική στιγµή 1 εισέρχεται η Α και αµέσως καταλαµβάνει την ΚΜΕ. Την χρονική στιγµή 2 εισέρχεται η Β και τοποθετείται στην ουρά µετά την Α. Την χρονική στιγµή 3 εισέρχεται η Γ. Τη στιγµή της διακοπής η ουρά περιέχει την Β. Μετά την διακοπή τοποθετείται στην ουρά και η διεργασία Α και στη συνέχεια η

51

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

διεργασία Γ που µόλις κατέφθασε στο σύστηµα. Αντίστοιχες ενέργειες λαµβάνονται όταν την χρονική στιγµή 4 εισέρχεται η διεργασία ∆. Παρατηρήστε ότι η διεργασία Β απασχολεί την ΚΜΕ από τη χρονική στιγµή 14 έως την 15 και άρα τερµατίζει την χρονική στιγµή 15 (και όχι την 14). Οµοίως, οι διεργασίες ∆, Α και Γ τερµατίζουν τις χρονικές στιγµές 22, 23 και 27, αντίστοιχα. Χρονική Στιγµή Άφιξη ΚΜΕ Ουρά Έτοιµων ∆ιεργασιών Τερµατισµός 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

-

-

-

Γ

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

52

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών

Από το 4ο διάγραµµα Gantt του Σχήµατος 11 παρατηρούµε ότι Χ∆Α = (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. Έστω πέντε διεργασίες Α, Β, Γ, ∆ και Ε που εκτελούν υπολογισµούς στην ΚΜΕ µε απαιτήσεις χρόνου ΧΕΑ = 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.

53

1. FCFS ανενεργό

0

Α

Β

1

Γ

9



13

22

27

Χρόνος σε χρονικές µονάδες 2. SJF ανενεργό

0

Α

Β

1



9

Γ

13

18

27

Χρόνος σε χρονικές µονάδες 3. SRTF ανενεργό

0

1

Α 2

Β 3

Β

Β

4



Α

6

Γ

11

18

27

Χρόνος σε χρονικές µονάδες 4. RR τερµατισµός Β

ανενεργό

0

1

Α 2

Α 3

Β 4

Α

Γ

Β



Α

Γ

Β

5

6

7

8

9

10 11

τερµατισµός ∆



Α

Γ

Β



Α

12

13

14

15

16 17

Χρόνος σε χρονικές µονάδες Σχήµα 11: ∆ιαγράµµατα Gannt για τους αλγόριθµους χρονοδροµολόγησης του Παραδείγµατος 6.

Γ 18

∆ 19

Α

Γ



Α

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

Γ

Γ

Γ

Γ

20 21 22 23 24 25 26 27

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 µε τη σειρά που υπαγορεύει ο παράκατω πίνακας.

56

2ο Κεφάλαιο

∆ιεργασίες – Χρονοδροµολόγηση ∆ιεργασιών ∆ιεργασία ∆ιάρκεια Α

3

Β

1

Γ

2



1

Ε

1

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

57

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

(γρ. 1)

59

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός Α: tmp = 1 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 1 * 3 = 3 flag = 1 p=4 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 3 * 4 = 12 flag = 0 p=3 Έλεγχος της συνθήκης της while Β: Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 1 * 3 = 3 flag = 1 p=4 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 3 * 4 = 12 flag = 0 p=3 Έλεγχος της συνθήκης της while Έξεγχξε τη συνθήκη της if tmp = 12 * 3 = 36 flag = 1 p=2 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 36 * 4 = 144 flag = 0 Α: Έλεγχος της συνθήκης της if Β: p = 1 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 144 * 3 = 432 flag = 1 p=0 Α: tmp = 12 * 3 = 36 flag = 1 p=2 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 36 * 4 = 144 flag = 0 p=1 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 144 * 3 = 432 Β: Έλεγχος της συνθήκης της while Α: flag = 1

(γρ. 1) (γρ. 2) (γρ. 3) (γρ. 4) (γρ. 5) (γρ. 6) (γρ. 7) (γρ. 8) (γρ. 9) (γρ. 10) (γρ. 11) (γρ. 12) (γρ. 2) (γρ. 3) (γρ. 4) (γρ. 5) (γρ. 6) (γρ. 7) (γρ. 8) (γρ. 9) (γρ. 10) (γρ. 11) (γρ. 12) (γρ. 13) (γρ. 14) (γρ. 15) (γρ. 16) (γρ. 17) (γρ. 18) (γρ. 19) (γρ. 20) (γρ. 13) (γρ. 21) (γρ. 22) (γρ. 23) (γρ. 24) (γρ. 25) (γρ. 26) (γρ. 14) (γρ. 15) (γρ. 16) (γρ. 17) (γρ. 18) (γρ. 19) (γρ. 20) (γρ. 21) (γρ. 22) (γρ. 23) (γρ. 24) (γρ. 27) (γρ. 25)

60

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Β:

p=0 Έλεγχος της συνθήκης της while print 432 print 432

(γρ. 26) (γρ. 27) (γρ. 28) (γρ. 28)

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

∆ιεργασία Α tmp = 1 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 1 * 3 = 3 flag = 1 p=4 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 3 * 4 = 12 flag = 0 p=3 Έλεγχος της συνθήκης της while

∆ιεργασία Β

Χρόνος

Έλεγχος της συνθήκης της if

tmp = 12 * 3 = 36 flag = 1 p=2

tmp = 1

(γρ. 1)

Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 1 * 3 = 3 flag = 1 p=4 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 3 * 4 = 12 flag = 0 p=3 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 12 * 3 = 36 flag = 1 p=2 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 36 * 4 = 144 flag = 0

(γρ. 2) (γρ. 3) (γρ. 4) (γρ. 5) (γρ. 6) (γρ. 7) (γρ. 8) (γρ. 9) (γρ. 10) (γρ. 11) (γρ. 12) (γρ. 13) (γρ. 14) (γρ. 15) (γρ. 16) (γρ. 17) (γρ. 18) (γρ. 19) (γρ. 20)

p=1 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 144 * 3 = 432 flag = 1 p=0

(γρ. 21) (γρ. 22) (γρ. 23) (γρ. 24) (γρ. 25) (γρ. 26)

(γρ. 1) (γρ. 2) (γρ. 3) (γρ. 4) (γρ. 5) (γρ. 6) (γρ. 7) (γρ. 8) (γρ. 9) (γρ. 10) (γρ. 11) (γρ. 12)

(γρ. 13)

(γρ. 14) (γρ. 15) (γρ. 16)

61

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 36 * 4 = 144 flag = 0 p=1 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 144 * 3 = 432

(γρ. 17) (γρ. 18) (γρ. 19) (γρ. 20) (γρ. 21) (γρ. 22) (γρ. 23) (γρ. 24)

flag = 1 p=0 Έλεγχος της συνθήκης της while print 432

(γρ. 25) (γρ. 26) (γρ. 27) (γρ. 28)

Έλεγχος της συνθήκης της while

(γρ. 27)

print 432

(γρ. 28)

Σχήµα 13: 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 Έλεγχος της συνθήκης της if tmp = 12 * 3 = 36 flag = 1 p=2 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if

(γρ. 12) (γρ. 13) (γρ. 14) (γρ. 15) (γρ. 16) (γρ. 17) (γρ. 18)

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)

62

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

tmp = 36 * 4 = 144 flag = 0 p=1

(γρ. 19) (γρ. 20) (γρ. 21)

Έλεγχος της συνθήκης της while

(γρ. 22)

Έλεγχος της συνθήκης της if

(γρ. 23)

tmp = 144 * 3 = 432 flag = 1 p=0 Έλεγχος της συνθήκης της while print 432

Έλεγχος της συνθήκης της if tmp = 12 * 3 = 36 flag = 1 p=2 Έλεγχος της συνθήκης της while Έλεγχος της συνθήκης της if tmp = 36 * 4 = 144 flag = 0 p=1

(γρ. 13) (γρ. 14) (γρ. 15) (γρ. 16) (γρ. 17) (γρ. 18) (γρ. 19) (γρ. 20) (γρ. 21)

Έλεγχος της συνθήκης της while

(γρ. 22)

Έλεγχος της συνθήκης της if

(γρ. 23)

tmp = 144 * 3 = 432 flag = 1 p=0 Έλεγχος της συνθήκης της while

(γρ. 24) (γρ. 25) (γρ. 26) (γρ. 27)

print 432

(γρ. 28)

(γρ. 24) (γρ. 25) (γρ. 26) (γρ. 27) (γρ. 28)

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

Η εντολή (γλωσσική έκφραση) cobegin s1; s2; ... sn; coend

ή parbegin s1; s2; ... sn; parend

υποδηλώνει ότι τα µπλοκ κώδικα s1, s2, ..., sn εκτελούνται ταυτόχρονα. Η σειρά µε την οποία θα εκτελεστούν οι εντολές s1, s2, ..., sn είναι άγνωστη. Παράδειγµα 7 ∆ίνεται το ακόλουθο πρόγραµµα: shared int tally; begin tally := 0; cobegin increase(); increase(); coend

63

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

end

όπου increase() είναι µια ρουτίνα (που θα δοθεί στη συνέχεια). Τι συµβαίνει όταν εκτελείται ο παραπάνω κώδικας; Με βάση όσα ήδη ειπώθηκαν, όταν η εντολή cobegin coend εκτελείται, δηµιουργούνται δύο διεργασίες που κάθε µια εκτελεί την ρουτίνα increase(). Οι δύο διεργασίες εκτελούνται ταυτόχρονα. Ενδιαφέρον ωστόσο παρουσιάζει και το γεγονός ότι η tally είναι µια διαµοιραζόµενη (shared) µεταβλητή. Μια διαµοιραζόµενη µεταβλητή µπορεί να γραφτεί ή να αναγνωστεί από περισσότερες από µια διεργασίες που εκτελούνται ταυτόχρονα. Οι διαµοιραζόµενες µεταβλητές δηλώνονται και αρχικοποιούνται πριν δηµιουργηθούν οι διεργασίες που θα τις διαχειριστούν. (Στο παράδειγµά µας, οι δύο αυτές διεργασίες είναι οι διεργασίες που εκτελούν την increase() ταυτόχρονα.) Η τιµή της tally είναι µια θέση µνήµης που µπορεί να προσπελαστεί και από τις δύο διεργασίες. Όταν η µία γράφει µια τιµή στην tally, η τιµή αυτή µπορεί να διαβαστεί και από την άλλη διεργασία. □ Παράδειγµα 8 Έστω ότι ο κώδικας της increase() του Παραδείγµατος 7 είναι ο ακόλουθος: void increase() int tmp, i; begin tmp = tally; tmp = tmp+1; tally = tmp; end

/* τοπικές µεταβλητές */

Έχουµε δύο διεργασίες που εκτελούνται ταυτόχρονα στο σύστηµα µας, έστω ότι η πρώτη είναι η Α και η δεύτερη η Β. Ο κώδικας που εκτελεί η κάθε διεργασία έχει τη µορφή που φαίνεται στο Σχήµα 15 (επειδή η µεταβλητή tmp είναι τοπική, έχει αντικατασταθεί στο Σχήµα 15 από δύο µεταβλητές, tmpA, tmpB, µία για κάθε διεργασία): Κώδικας διεργασίας A

Κώδικας διεργασίας B

1. 2. 3.

1. 2. 3.

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

tmpB = tally; tmpB = tmpB +1; tally = tmpB;

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

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

64

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. Τι θα συνέβαινε, π.χ., αν η διεργασία Β εκτελούσε την τελευταία εντολή της πριν από την εκτέλεση της τελευταίας εντολής της Α; Τι θα συνέβαινε αν η εκτέλεση των δύο διεργασιών γινόταν εντολή-εντολή εναλλάξ; □ Παράδειγµα 9 Έστω τώρα ότι ο κώδικας της 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 begin

for iB = 1 to 5 do begin

65

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

1. 2. 3. end

1. 2. 3. end

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

tmpB = tally; tmpB = tmpB +1; tally = tmpB;

όπου οι µεταβλητές tmpA, tmpB, iA και iB είναι τοπικές (µη διαµοιραζόµενες) µεταβλητές. Ας µελετήσουµε µερικές δυνατές εκτελέσεις και τις τιµές που θα έχει η tally στο τέλος κάθε µιας από αυτές. Ας υποθέσουµε πρώτα ότι οι διεργασίες εκτελούνται σειριακά, η µια µετά την άλλη. Η πρώτη διεργασία (που θα µπορούσε να είναι είτε η Α ή η Β) αυξάνει την τιµή της tally σε 5. Στη συνέχεια, ξεκινά η δεύτερη διεργασία, η οποία αρχικά διαβάζει την τιµή 5 στην tally και αυξάνει την tally κατά 1 πέντε φορές (µια φορά σε κάθε ανακύκλωση της for). Εποµένως, στο τέλος κάθε σειριακής εκτέλεσης, η tally έχει την τιµή 10. Το ερώτηµα τώρα είναι τι τιµή θα έχει η tally, αν οι δύο διεργασίες εκτελούν από µια εντολή η κάθε µια πριν γίνει διακοπή και εκτελεστεί η άλλη (δηλαδή αν η εκτέλεση των διεργασιών γινόταν εντολή-εντολή εναλλάξ). Συνίσταται ισχυρά στον αναγνώστη να επαληθεύσει, ότι σε αυτή την περίπτωση η τελική τιµή της tally θα είναι 5. Ο αναγνώστης θα πρέπει να φτιάξει ένα πίνακα εκτέλεσης, όπως αυτοί που φαίνονται στα Σχήµατα 13 και 14 και να εξετάσει ποια είναι η τιµή της 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, κάτι που ποτέ η διεργασία Α

66

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

δεν θα µάθει, αφού στην αµέσως επόµενη εντολή (εντολή 3) πανωγράφει την τιµή της tally µε την τιµή 1. Πριν προχωρήσει στην εκτέλεση των υπολοίπων 4 ανακυκλώσεών της, εκτελείται ξανά η διεργασία B. •

Η διεργασία 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.

Το παραπάνω σενάριο περιγράφεται συνοπτικά (και µε λίγο συµπυκνωµένο τρόπο) στο Σχήµα 16. ∆ιεργασία Α

∆ιεργασία Β

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;

67

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός /* Η tally έχει τιµή 2 */

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

Παρατήρηση Το σετ εντολών: 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. Έστω ότι ο κώδικας της 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 σε αυτή την περίπτωση; 68

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Υπόδειξη: ∆είξτε ότι η µικρότερη τελική τιµή που µπορεί να έχει η tally είναι και πάλι 2. To σενάριο που οδηγεί στην τιµή αυτή δεν είναι πολύ διαφορετικό από αυτό που περιγράφεται στο Σχήµα 16. Είναι ώστοσο λίγο πιο πολύπλοκο, λόγω της ύπαρξης της τρίτης διεργασίας (της οποίας οι αυξήσεις στην tally θα πρέπει επίσης να πανωγραφτούν).

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€ εκτίµηση, ο καθηγητής σας. Στο παράδειγµά µας, δυο διεργασίες εκτελούνται ταυτόχρονα και χρησιµοποιούν τον ίδιο πόρο συστήµατος, τον εκτυπωτή. Το ΛΣ οφείλει να συντονίσει τις διεργασίες που επιχειρούν να χρησιµοποιήσουν τον ίδιο πόρο, ώστε η µία να περιµένει την άλλη. Ο πολύ προσεκτικός αναγνώστης θα πρέπει να αντιδράσει στο παραπάνω (λίγο απλουστευτικό) σενάριο (το οποίο περιγράφεται µόνο για λόγους κατανόησης της ανάγκης συγχρονισµού). Όπως έχουµε ήδη αναφέρει, οι διεργασίες του χρήστη δεν έχουν από µόνες τους πρόσβαση σε συσκευές Ε/Ε, όπως ο εκτυπωτής, και άρα το παραπάνω σενάριο µπερδεµένης εκτύπωσης δεν θα µπορούσε να συµβεί, αφού το ΛΣ είναι εκείνο 69

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

front

file1.txt

8

file2.txt

9

back

10 . . .

Σχήµα 17: Ουρά ετεροχρονισµένης εκτύπωσης.

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

70

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. Εκτύπωση του µηνύµατος "Το νέο σας υπόλοιπο είναι " και τερµατισµός εκτέλεσης. Ας υποθέσουµε ότι κάποια µέρα ο χρήστης και η αδελφή του πηγαίνουν ταυτόχρονα σε δύο διαφορετικά ΑΤΜ (έστω ATM-1 και ATM-2) θέλοντας να κάνουν ανάληψη 500€ ο καθένας. Τι θα συµβεί; Αν η αδελφή του χρήστη είναι πιο τυχερή από τον ίδιο θα προλάβει να τελειώσει την ανάληψη της πριν η ανάληψη του χρήστη ξεκινήσει. Έτσι, εκείνη θα πάρει τα 500€ από το κοινό βιβλιάριο και ο χρήστης θα διαβάσει το µήνυµα «∆εν έχετε τόσα χρήµατα στο λογαριασµό σας». Στην περίπτωση τώρα που ο χρήστης είναι πιο τυχερός, θα συµβεί ακριβώς το αντίθετο. Υπάρχει όµως και ένα ακόµη σενάριο στο οποίο η τύχη ευνοεί και τους δυο (εις βάρος φυσικά της τράπεζας). Ας προσπαθήσουµε να το διερευνήσουµε. Το σενάριο παρουσιάζεται στο Σχήµα 18. Το σενάριο αυτό θα οδηγούσε σε ανάληψη συνολικού ποσού 1000€ από ένα λογαριασµό που περιέχει µόνο 500€ και είναι ένας καλός τρόπος µε τον οποίο ο χρήστης και η αδελφή του µπορούν να κλέψουν την τράπεζα! Όλα αυτά φυσικά θα συµβούν µόνο στην περίπτωση που ο προγραµµατιστής του υπολογιστή της τράπεζας δεν γνωρίζει καλά το αντικείµενο των λειτουργικών συστηµάτων και δεν έχει µεριµνήσει για το συντονισµό

71

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

των διεργασιών που διαχειρίζονται τον κοινό πόρο (που στο παράδειγµα αυτό δεν είναι άλλος από την διαµοιραζόµενη µεταβλητή 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

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

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

72

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Θεωρείστε ότι και οι δύο γονείς φθάνουν σε διαφορετικά υποκαταστήµατα της τράπεζας ταυτόχρονα. Περιγράψτε ένα «ατυχές» σενάριο στο οποίο, παρότι και οι δύο γονείς θα καταθέσουν από 500€ ο καθένας, ο λογαριασµός δεν θα έχει περισσότερα από 1000€ στο τέλος (αντί για 1500€ που θα έπρεπε να περιέχει). Αυτός θα είναι και ένας καλός τρόπος για να κλέβει η τράπεζα τους χρήστες της!

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

Οι κώδικες εισόδου και εξόδου θα πρέπει να σχεδιαστούν µε τέτοιο τρόπο ώστε να εγγυώνται τα ακόλουθα: 1. Αµοιβαίο αποκλεισµό. Όταν µια διεργασία εκτελεί το κρίσιµο τµήµα της καµία άλλη διεργασία δεν επιτρέπεται να εκτελεί το κρίσιµο τµήµα της (δηλαδή το κρίσιµο τµήµα πρέπει να εκτελείται ατοµικά). 2. Πρόοδος. Αν καµία διεργασία δεν εκτελεί το κρίσιµο τµήµα της και υπάρχει κάποια διεργασία που επιθυµεί να εισέλθει (να εκτελέσει) στο κρίσιµο τµήµα της, τότε µόνο εκείνες οι διεργασίες που δεν εκτελούν το µη-κρίσιµο τµήµα τους θα πρέπει να 73

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

2η Εναλλακτική

3η Εναλλακτική

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

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

while (TRUE) begin <κώδικας εισόδου>; <κρίσιµο τµήµα>; <κώδικας εξόδου>; <µη κρίσιµο τµήµα>; end

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

74

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

∆ιεργασία Β

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

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

3.4 Λύσεις µε τη Βοήθεια του Υλικού Στην ενότητα αυτή, θα συζητηθούν λύσεις στο πρόβληµα του αµοιβαίου αποκλεισµού, βασισµένες στην υπόθεση ότι το υλικό παρέχει κάποιες εντολές που µπορούν να εκτελεστούν ατοµικά. Οι εντολές αυτές εκτελούνται σε ένα κύκλο λειτουργίας ΚΜΕ και άρα είναι εγγυηµένο από το υλικό, ότι ακόµη και αν πολλές διεργασίες επιθυµούν να εκτελέσουν ταυτόχρονα κάποιες τέτοιες εντολές, αυτές θα εκτελεστούν τελικά σειριακά µε κάποια σειρά (η σειρά είναι ωστόσο αυθαίρετη, ενώ υποθέσεις για αυτήν δεν επιτρέπονται). Μια τέτοια εντολή είναι η Test&Set() που περιγράφεται στο Σχήµα 21. 75

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

boolean Test&Set(boolean lock) int tmp;

/* τοπική µεταβλητή */

begin tmp = lock; lock = TRUE; return(tmp); end Σχήµα 21: Η εντολή 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()). Κάθε µια από τις διεργασίες εκτελεί τον κώδικα του Σχήµατος 22. shared boolean lock;

/* κοινή µεταβλητή, αρχικά, FALSE */

int tmp; /* τοπική µεταβλητή της εκάστοτε διεργασίας*/ repeat begin tmp = Test&Set(lock); while (tmp == TRUE) do tmp = Test&Set(lock); <κρίσιµο τµήµα>; lock = FALSE; <µη-κρίσιµο τµήµα>; end forever; Σχήµα 22: Λύση του προβλήµατος αµοιβαίου αποκλεισµού µε χρήση της εντολής Test&Set().

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

76

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

lock σε FALSE για να δώσει τη δυνατότητα σε κάποια από τις διεργασίες που ενδιαφέρονται να εισέλθουν στο κρίσιµο τµήµα τους να το πραγµατοποιήσει. Άσκηση Αυτοαξιολόγησης 7 (Θέµα 1, Ερώτηµα 5, Εργασία 4, Ακ. Έτος 2001-2002 ) Θα ήταν σωστή η λύση του Σχήµατος 22 αν η Test&Set() δεν ήταν εγγυηµένο πως θα εκτελεστεί ατοµικά; Λύση Ας υποθέσουµε ότι η Test&Set() δεν εκτελείται ατοµικά. Θα παρουσιάσουµε µια εκτέλεση δύο διεργασιών (έστω ότι οι διεργασίες ονοµάζονται 0 και 1), στην οποία και οι δύο διεργασίες βρίσκονται την ίδια χρονική στιγµή στο κρίσιµο τµήµα τους. Στην πράξη, κάθε µια από τις διεργασίες εκτελεί τον κώδικα που φαίνεται στο Σχήµα 23 (και µπορεί να διακόπτεται σε οποιοδήποτε σηµείο της εκτέλεσης της). ∆ιεργασία 0 repeat begin tmp0 = lock; lock = TRUE; while (tmp0 == TRUE) do tmp0 = Test&Set(lock);

∆ιεργασία 1 repeat begin tmp1 = lock; lock = TRUE; while (tmp1 == TRUE) do tmp1 = Test&Set(lock);

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

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

lock = FALSE;

lock = FALSE;

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

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

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

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

∆ιεργασία 1

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

77

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Σχήµα 24: Εκτέλεση που παραβιάζει τη συνθήκη αµοιβαίου αποκλεισµού αν η 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(), προκειµένου να το περικλείσουµε µεταξύ του κώδικα εισόδου και του κώδικα εξόδου για να είµαστε σίγουροι ότι θα εκτελεστεί ατοµικά. Το κρίσιµο τµήµα στην deposit() είναι η ανάγνωση και η εγγραφή της κοινής µεταβλητής balance. Ο σωστός κώδικας για την deposit() παρουσιάζεται στο Σχήµα 25. shared int balance; boolean deposit(int amount)

78

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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 Σχήµα 25: Ατοµική έκδοση της deposit().

Ας δούµε τώρα τι αλλαγές χρειάζεται η withdraw(). Κάθε προσπέλαση (ανάγνωση ή εγγραφή) της διαµοιραζόµενης µεταβλητής balance πρέπει να είναι εγγυηµένο πως θα εκτελεστεί ατοµικά. Ο σωστός κώδικας για την withdraw() παρουσιάζεται στο Σχήµα 26. 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 Σχήµα 26: Ατοµική έκδοση της withdraw().

Ο αναγνώστης θα πρέπει να επενδύσει αρκετό χρόνο για να κατανοήσει σε βάθος γιατί οι κακές εκτελέσεις που περιγράφτηκαν στην Ενότητα 3.2 (η τράπεζα κλέβει τους πελάτες της ή οι πελάτες κλέβουν την τράπεζα) δεν θα προκύψουν αν οι αναλήψεις και οι καταθέσεις πραγµατοποιούνται χρησιµοποιώντας τις εκδόσεις των deposit() και withdraw() που παρουσιάζονται στα Σχήµατα 25 και 26. □ Άσκηση Αυτοαξιολόγησης 9 Ικανοποιεί η λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που παρουσιάζεται στο Σχήµα 15 κάποια από τις συνθήκες δικαιοσύνης;

79

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Λύση Θα εξετάσουµε πρώτα αν η λύση µπορεί να οδηγήσει σε παρατεταµένη στέρηση. Για να αποδείξουµε ότι µια λύση µπορεί να οδηγήσει σε παρατεταµένη στέρηση θα πρέπει να βρούµε σενάριο στο οποίο κάποιες διεργασίες εισέρχονται συνεχώς στο κρίσιµο τµήµα, ενώ κάποια άλλη παρότι επιθυµεί να εισέλθει στο κρίσιµο τµήµα της θα παραµείνει για πάντα µπλοκαρισµένη στον κώδικα εισόδου. Ας δοκιµάσουµε να “µαγειρέψουµε” ένα τέτοιο σενάριο για την σχετικά απλή περίπτωση δύο διεργασιών. Το σενάριο περιγράφεται στο Σχήµα 27. ∆ιεργασία 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 */ . . .

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

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

80

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Αξίζει να παρατηρήσουµε ότι αν µια λύση ικανοποιεί τη συνθήκη της άνω φραγµένης καθυστέρησης, τότε ικανοποιεί και τη συνθήκη της αποφυγής παρατεταµένης στέρησης. Άρα, αν µια λύση δεν ικανοποιεί τη συνθήκη της αποφυγής παρατεταµένης στέρησης, τότε δεν µπορεί να ικανοποιεί και τη συνθήκη της άνω φραγµένης καθυστέρησης. Αρκεί εποµένως να αποδείξουµε ότι µια λύση οδηγεί σε παρατεταµένη στέρηση για να συµπεράνουµε ότι καµία από τις δύο συνθήκες δικαιοσύνης δεν ικανοποιείται. □ Άσκηση Αυτοαξιολόγησης 10 (Άσκηση Αυτοαξιολόγησης 3.19 Τόµου Γ) Θεωρείστε ότι το υλικό εγγυάται ότι η εντολή Fetch&Add(), που περιγράφεται στο Σχήµα 28 εκτελείται ατοµικά. Λύστε το πρόβληµα του αµοιβαίου αποκλεισµού χρησιµοποιώντας την Fetch&Add(). (Μόνο η ατοµική εκτέλεση της Fetch&AddA() και όχι της Test&Set() είναι εγγυηµένη από το υλικό.) int Fetch&Add(int a, int b) int tmp;

/* τοπική µεταβλητή */

begin tmp = a; a = a + b; return(tmp); end Σχήµα 28: Η εντολή Fetch&Add().

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

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

/* τοπική µεταβλητή */

begin tmp = a; a = b; b = tmp; return(tmp); end Σχήµα 29: Η εντολή Swap().

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

81

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

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

/* τοπική µεταβλητή */

begin tmp = a; a = f(a); return(tmp); end Σχήµα 30: Η εντολή 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() που παρέχεται από το υλικό.

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

82

3ο Κεφάλαιο

3.5.1

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

Η ανεύρεση λύσης στο πρόβληµα του αµοιβαίου αποκλεισµού, χωρίς τη βοήθεια του υλικού, είναι επίπονη εργασία. Για να γίνει αυτό κατανοητό, καθώς και για να οξύνουµε τη διαίσθηση του αναγνώστη, θα παρουσιάσουµε στη συνέχεια µερικές προσπάθειες επίλυσης του προβλήµατος, όχι απαραίτητα σωστές, και θα συζητήσουµε τα προβλήµατα που ενδεχόµενα παρουσιάζει η κάθε µια. Ας θυµηθούµε πως η εργασία που έχουµε να κάνουµε είναι να σχεδιάσουµε κώδικα εισόδου και κώδικα εξόδου που θα ικανοποιούν τις συνθήκες του αµοιβαίου αποκλεισµού και της προόδου. Η πρώτη προτεινόµενη λύση στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες φαίνεται στο Σχήµα 31, όπου το µπλοκ εντολών κόκκινου χρώµατος αποτελεί τον κώδικα εισόδου και εκείνο πράσινου χρώµατος αποτελεί τον κώδικα εξόδου. Κοινές Μεταβλητές shared boolean in;

/* αρχικά, FALSE */

Κώδικας ∆ιεργασίας 0

Κώδικας ∆ιεργασίας 1

repeat begin while (in == TRUE) do noop; in = TRUE; <κρίσιµο τµήµα>; in = FALSE; <µη-κρίσιµο τµήµα>; end forever;

repeat begin while (in == TRUE) do noop; in = TRUE; <κρίσιµο τµήµα>; in = FALSE; <µη-κρίσιµο τµήµα>; end forever;

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

κρίσιµο τµήµα της. Η συνέχεια είναι εύκολο να προβλεφθεί. Και οι δύο διεργασίες θέτουν την τιµή της in σε TRUE και εισέρχονται στο κρίσιµο τµήµα τους. Το παραπάνω σενάριο περιγράφεται πιο αναλυτικά στο Σχήµα 32. ∆ιεργασία Α

∆ιεργασία Β

Έλεγχος συνθήκης while; /* είναι FALSE */ Έλεγχος συνθήκης while; /*είναι FALSE */ free = TRUE; <κρίσιµο τµήµα>; free = TRUE; <κρίσιµο τµήµα>; Σχήµα 32: Σενάριο που καταστρατηγεί τη συνθήκη του αµοιβαίου αποκλεισµού για την 1η προτεινόµενη λύση.

Εφόσον υπάρχει σενάριο που µπορεί να οδηγήσει τις δύο διεργασίες να εκτελούν το κρίσιµο τµήµα την ίδια χρονική στιγµή, η συνθήκη του αµοιβαίου αποκλεισµού δεν ισχύει για την προτεινόµενη λύση του Σχήµατος 31 και άρα η λύση αυτή δεν είναι σωστή. Αξίζει να αναρωτηθούµε ποιο είναι το πρόβληµα στη λύση αυτή και να προσπαθήσουµε να το διορθώσουµε. Το πρόβληµα φαίνεται να είναι πως κάθε διεργασία πρώτα ελέγχει αν η in είναι FALSE και µετά αλλάζει την τιµή της in σε TRUE. Με άλλα λόγια, κάθε διεργασία πρώτα ελέγχει αν προτίθεται η άλλη διεργασία να µπει στο κρίσιµο τµήµα της (έλεγχος συνθήκης while) και στη συνέχεια εκφράζει τη δική της πρόθεση να µπει στο κρίσιµο τµήµα (αλλάζοντας την τιµή της in σε TRUE). Ως δεύτερη προτεινόµενη λύση, ας δοκιµάσουµε εποµένως να κάνουµε ακριβώς το αντίθετο. Ο κώδικας των δύο διεργασιών φαίνεται στο Σχήµα 33. Κοινές µεταβλητές shared boolean flag[2];

/* αρχικά, FALSE */

Κώδικας ∆ιεργασίας 0 repeat begin flag[0] = TRUE; while (flag[1] == TRUE) do noop; <κρίσιµο τµήµα>; flag[0] = FALSE; <µη-κρίσιµο τµήµα>; end forever;

Κώδικας ∆ιεργασίας 1 repeat begin flag[1] = TRUE; while (flag[0] == TRUE) do noop; <κρίσιµο τµήµα>; flag[1] = FALSE; <µη-κρίσιµο τµήµα>; end forever;

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

Στη λύση του Σχήµατος 33, χρησιµοποιούνται δύο κοινές µεταβλητές, µία για κάθε διεργασία. Η µεταβλητή flag[i] χρησιµοποιείται από τη διεργασία i για να δηλώσει την πρόθεση της να εισέλθει στο κρίσιµο τµήµα. Αρχικά, η διεργασία i θέτει την flag[i] στην τιµή TRUE για να υποδηλώσει ότι θέλει να εισέλθει στο κρίσιµο τµήµα της. Στη 84

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

συνέχεια ελέγχει µήπως η άλλη διεργασία (δηλαδή, η διεργασία 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 για πάντα */ Σχήµα 34: Σενάριο που καταστρατηγεί τη συνθήκη της προόδου για τη 2η προτεινόµενη λύση.

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

/* αρχικά, 0 ή 1, δεν παίζει ρόλο */

Κώδικας ∆ιεργασίας 0 repeat begin while (turn == 1) do noop;

Κώδικας ∆ιεργασίας 1 repeat begin while (turn == 0) do noop;

85

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

<κρίσιµο τµήµα>; turn = 1; <µη-κρίσιµο τµήµα>; end forever;

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

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

Ας µελετήσουµε αν η παραπάνω λύση είναι σωστή. Η συνθήκη του αµοιβαίου αποκλεισµού ικανοποιείται. Κάθε χρονική στιγµή, η κοινή µεταβλητή turn έχει τιµή είτε 0 ή 1 και έτσι µία µόνο από τις διεργασίες µπορεί να εισέλθει στο κρίσιµο τµήµα της, ενώ η άλλη θα µπλοκάρει στη while του κώδικα εισόδου. Έστω π.χ., ότι η διεργασία 1 είναι αυτή που εισέρχεται πρώτη στο κρίσιµο τµήµα της. Όσο η 1 βρίσκεται στο κρίσιµο τµήµα της, η turn θα έχει την τιµή 1. Εποµένως, αν η διεργασία 0 θελήσει να εισέλθει στο κρίσιµο τµήµα της, θα µπλοκάρει στη while του κώδικα εισόδου. Όταν η 1 εξέλθει από το κρίσιµο τµήµα της, θα αλλάξει την turn σε 0 και τότε η 0 θα µπορέσει να εισέλθει στο κρίσιµο τµήµα της. Παρατηρήστε ότι η 1 δεν θα µπορέσει να µπει στο κρίσιµο τµήµα της για δεύτερη φορά, αν η 0 δεν αλλάξει την τιµή της turn σε 1. H λύση του Σχήµατος 35 προϋποθέτει την αυστηρή εναλλαγή των διεργασιών κατά την εισαγωγή τους στα κρίσιµα τµήµατά τους. Το ερώτηµα τώρα είναι αν ικανοποιείται η συνθήκη της προόδου. ∆υστυχώς, υπάρχει σενάριο στο οποίο η µια διεργασία ενδιαφέρεται να εισέλθει στο κρίσιµο τµήµα της αλλά δεν τα καταφέρνει, παρότι η άλλη διεργασία εκτελεί το µη-κρίσιµο τµήµα της. Συνίσταται στον αναγνώστη να επενδύσει λίγο χρόνο στην ανακάλυψη του σεναρίου αυτού πριν συνεχίσει. Το σενάριο περιγράφεται στο Σχήµα 36 (όπου έχουµε υποθέσει ότι η αρχική τιµή της turn είναι 1). Κώδικας ∆ιεργασίας 0

Κώδικας ∆ιεργασίας 1 Έλεγχος συνθήκης while; /* είναι FALSE */ <κρίσιµο τµήµα>; turn = 0; µέρος µη-κρίσιµου τµήµατος;

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

. . .

86

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

/* παρότι η 1 είναι στο µη-κρίσιµο τµήµα της, η 0 δεν µπορεί να εισέλθει (πάλι) στο κρίσιµο τµήµα της */ Σχήµα 36: Σενάριο που καταστρατηγεί τη συνθήκη της προόδου για την 3η προτεινόµενη λύση.

Στο σενάριο του Σχήµατος 36, η διεργασία 0 έχει ένα µεγάλο µη-κρίσιµο τµήµα, ενώ αντίθετα το µη-κρίσιµο τµήµα της 1 είναι πολύ µικρό. Η διεργασία 1 ξεκινά πρώτη. Εισέρχεται και εξέρχεται από το κρίσιµο τµήµα της. Στη συνέχεια, η διεργασία 0 εισέρχεται και εξέρχεται από το κρίσιµο τµήµα της. Είναι και πάλι η σειρά της 1 να εισέλθει και να εξέλθει από το κρίσιµο τµήµα της. Όµως, η 1 δεν ενδιαφέρεται άµεσα να ξανα-εισέλθει στο κρίσιµο τµήµα της γιατί εκτελεί ένα πολύ µεγάλο µη-κρίσιµο τµήµα. Η 0 που ενδιαφέρεται να ξανα-εισέλθει, πρέπει να περιµένει να εισέλθει και πάλι η 1 πριν ξαναέρθει η σειρά της. Η 1 παρότι εκτελεί το µη-κρίσιµο τµήµα της αποτρέπει την είσοδο της 0 στο κρίσιµο τµήµα της, το οποίο καταστρατηγεί τη συνθήκη της προόδου. Εποµένως, και η λύση αυτή δεν είναι σωστή. Άσκηση Αυτοαξιολόγησης 14 Σχολιάστε την ορθότητα της λύσης του προβλήµατος του αµοιβαίου αποκλεισµού για δύο διεργασίες που περιγράφεται στο Σχήµα 37. Κοινές Μεταβλητές shared boolean flag[2];

/* αρχικά, TRUE */

Κώδικας ∆ιεργασίας 0 repeat begin while (flag[1] == TRUE) do noop; flag[0] = TRUE; <κρίσιµο τµήµα>; flag[0] = FALSE; <µη-κρίσιµο τµήµα>; end forever;

Κώδικας ∆ιεργασίας 1 repeat begin while (flag[0] == TRUE) do noop; flag[1] = TRUE; <κρίσιµο τµήµα>; flag[1] = FALSE; <µη-κρίσιµο τµήµα>; end forever;

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

Άσκηση Αυτοαξιολόγησης 15 Η Ελένη και ο ∆ήµος είναι γείτονες και µοιράζονται µια αυλή. Η Ελένη έχει µια γάτα, ενώ ο ∆ήµος έχει ένα σκύλο. Τόσο στη γάτα όσο και στο σκύλο αρέσει να παίζουν στην αυλή. Ωστόσο, όσες φορές έχει τύχει τα δύο κατοικίδια να συναντηθούν στην αυλή, έχει γίνει µεγάλος καυγάς. Έτσι, η Ελένη και ο ∆ήµος αποφάσισαν πως δεν πρέπει να αφήνουν τα κατοικίδια να βρίσκονται την ίδια ώρα στην αυλή. Άρα, θα πρέπει µε κάποιο τρόπο να συντονίζονται πριν αφήσουν τα κατοικίδια τους να βγαίνουν στην αυλή.

87

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

89

3ο Κεφάλαιο

3.5.2

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Λύση του Peterson

Ενδεχόµενα, ο αναγνώστης έχει ήδη πειστεί πως το να βρεθεί µια λύση στο πρόβληµα του αµοιβαίου αποκλεισµού δεν είναι πάρα πολύ εύκολη υπόθεση. Στη συνέχεια παρουσιάζεται µια σωστή λύση του προβλήµατος για δύο διεργασίες που παρουσιάστηκε από τον Peterson. Κοινές Μεταβλητές shared in turn; shared boolean flag[2];

/* αρχικά 0 ή 1 */ /* αρχικά FALSE */

∆ιεργασία 0

∆ιεργασία 1

repeat begin 1. flag[0] = TRUE; 2. turn = 0; 3. while (turn == 0 AND flag[1] == TRUE) do noop; 4. <κρίσιµο τµήµα>; 5. flag[0] = FALSE; 6. <µη-κρίσιµο τµήµα>; end forever;

repeat begin 1. flag[1] = TRUE; 2. turn = 1; 3. while (turn == 1 AND flag[0] == TRUE) do noop; 4. <κρίσιµο τµήµα>; 5. flag[1] = FALSE; 6. <µη-κρίσιµο τµήµα>; end forever;

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

91

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

δουλεύει σωστά µόνο για δύο διεργασίες. Ο αλγόριθµος του Dekker θα συζητηθεί στην ενότητα «Λίγο πιο ∆ύσκολα». Άσκηση Αυτοαξιολόγησης 18 (Μέρος Θέµατος 2, 4η Εργασία, Ακ. Έτος 2001-2002) Ας µην ξεχνιόµαστε! Τώρα που έχουµε ακόµη µια λύση του προβλήµατος του αµοιβαίου αποκλεισµού στα χέρια µας, περιγράψτε και πάλι τις δύο ρουτίνες boolean deposit(int amount), και boolean withdraw(int amount), που περιγράφτηκαν στην Άσκηση Αυτοαξιολόγησης 8. Η λύση σας θα πρέπει να επιτυγχάνει αµοιβαίο αποκλεισµό για δύο διεργασίες χρησιµοποιώντας την λύση του Peterson. Άσκηση Αυτοαξιολόγησης 19 (Μέρος Θέµατος 3, 4η Εργασία, Ακ. Έτος 2001-2002) Μια ακόµη λύση στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες προτείνεται στο Σχήµα 39. Κοινές µεταβλητές shared int turn; shared boolean flag[2];

/* αρχικά 0 ή 1 */ /* αρχικά 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; Σχήµα 39: Πέµπτη προτεινόµενη λύση µε χρήση λογισµικού στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες.

Βρείτε σενάριο κατά το οποίο και οι δύο διεργασίες βρίσκονται στο κρίσιµο τµήµα τους. Εξηγήστε τις οµοιότητες και τις διαφορές µε τη λύση του Peterson. Τι είναι αυτό που εγγυάται ορθότητα στη λύση του Peterson, αλλά όχι στην παραπάνω λύση; Άσκηση Αυτοαξιολόγησης 20 (Άσκηση Αυτοαξιολόγησης 3.7 Τόµου Γ & Θέµα 3, Β’ Τελική Εξέταση, Ιούνιος 2001) Σχολιάστε την ορθότητα της λύσης του προβλήµατος του αµοιβαίου αποκλεισµού για δύο διεργασίες που περιγράφεται στο Σχήµα 40. ∆ιεργασία 0

∆ιεργασία 1

shared in turn;

/* αρχικά 0 ή 1 */

shared boolean flag[2];

/* αρχικά FALSE */

repeat

repeat

92

3ο Κεφάλαιο begin flag[0] = TRUE; while (turn != 0) do begin while (flag[1] == TRUE) do noop; turn = 0; end <κρίσιµο τµήµα>; flag[0] = FALSE; <µη-κρίσιµο τµήµα>; end forever;

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός begin flag[1] = TRUE; while (turn != 1) do begin while (flag[0] == TRUE) do noop; turn = 1; end <κρίσιµο τµήµα>; flag[1] = FALSE; <µη-κρίσιµο τµήµα>; end forever;

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

εξωτερικής 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 και θα µπει και αυτή στο κρίσιµο τµήµα της! Ο σκοπός µας επιτεύχθηκε! Μόλις τελειώσαµε το «µαγείρεµα» του σεναρίου που ψάχναµε, το οποίο παρουσιάζεται πιο αναλυτικά στο Σχήµα 41. ∆ιεργασία 0

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

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

94

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

εκτέλεση µέρους του κρίσιµου τµήµατος; turn = 1; Έλεγχος συνθήκης εξωτερικής while; /* είναι FALSE */ <κρίσιµο τµήµα>; Σχήµα 41: Σενάριο που καταστρατηγεί τη συνθήκη του αµοιβαίου αποκλεισµού για την 5η προτεινόµενη λύση.

Θα ήταν καλό αν ο αναγνώστης συγκρατήσει ότι οι διεργασίες συνήθως διακόπτονται αφού έχουν κάνει τις αποτιµήσεις (σε FALSE) των συνθηκών που τις αποτρέπουν από το να εισέλθουν στο κρίσιµο τµήµα τους, αλλά πριν αλλάξουν οποιεσδήποτε κοινές µεταβλητές που θα επηρεάσουν το αν άλλες διεργασίες θα εισέλθουν στο κρίσιµο τµήµα τους. Αυτό ακριβώς συνέβη και στο κακό σενάριο του Σχήµατος 41. Επίσης, όταν ο κώδικας εισόδου περιέχει φωλιασµένες εντολές ανακυκλώσεις, πολλές φορές το κακό σενάριο προκύπτει αν η µια διεργασία αποτιµήσει τη συνθήκη της εξωτερικής ανακύκλωσης σε FALSE, ενώ η άλλη αποτιµήσει τη συνθήκη της εσωτερικής ανακύκλωσης σε FALSE. Αυτό ωστόσο δεν είναι ο κανόνας, και άρα κάποιες φορές µπορεί και να µην ισχύει. Συνήθως ωστόσο, όταν υπάρχει κακό σενάριο αλλά δεν εφαρµόζεται η παραπάνω παρατήρηση για την εύρεση του, το σενάριο είναι απλό και είναι σχετικά εύκολο να βρεθεί. □ Άσκηση Αυτοαξιολόγησης 21 Εξετάστε αν η προτεινόµενη λύση του Σχήµατος 42 είναι σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες. Κοινές Μεταβλητές shared in turn; shared boolean flag[2];

/* αρχικά 0 ή 1 */ /* αρχικά FALSE */

∆ιεργασία 0

∆ιεργασία 1

repeat begin 1. flag[0] = TRUE; 2. turn = 1; 3. while (turn == 0 && flag[1] == TRUE) do noop; 4. <κρίσιµο τµήµα>; 5. flag[0] = FALSE; 6. <µη-κρίσιµο τµήµα>; end forever;

repeat begin 1. flag[1] = TRUE; 2. turn = 0; 3. while (turn == 1 && flag[0] == TRUE) do noop; 4. <κρίσιµο τµήµα>; 5. flag[1] = FALSE; 6. <µη-κρίσιµο τµήµα>; end forever;

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Εδώ θα µας απασχολήσει ωστόσο το ερώτηµα αν η λύση αυτή είναι δίκαιη. Θα εξετάσουµε πρώτα αν η λύση µπορεί να οδηγήσει σε παρατεταµένη στέρηση. Για να αποδείξουµε ότι µια λύση µπορεί να οδηγήσει σε παρατεταµένη στέρηση θα πρέπει να βρούµε σενάριο στο οποίο η µια διεργασία εισέρχεται συνεχώς στο κρίσιµο τµήµα της ενώ η άλλη, παρότι θέλει να εισέλθει στο κρίσιµο τµήµα της, παραµένει για πάντα µπλοκαρισµένη στον κώδικα εισόδου. (Ας θυµηθούµε τη σχετική συζήτηση της Άσκησης Αυτοαξιολόγησης 9.) Το σενάριο περιγράφεται στο Σχήµα 43. ∆ιεργασία 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; . . .

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

(είναι κακότυχη και) η συνθήκη της while που εκτελεί είναι πάντα αληθής όταν ο χρονοδροµολογητής αποφασίζει να τη δροµολογήσει. Εξετάστε αν η λύση του Peterson είναι δίκαιη λύση. Στην περίπτωση που η λύση του Peterson είναι δίκαιη λύση, τι είναι το διαφορετικό στις δύο λύσεις που κάνει τη µια δίκαιη και την άλλη να οδηγεί σε παρατεταµένη στέρηση; □ Άσκηση Αυτοαξιολόγησης 22 Εξετάστε αν η προτεινόµενη λύση του Σχήµατος 44 είναι σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες. Κοινές µεταβλητές shared boolean flag[2];

/* αρχικά, FALSE */

∆ιεργασία 0 flag[0] = 1; while (flag[1] != 0) do noop; <κρίσιµο τµήµα>; flag[0] = 0; <µη-κρίσιµο τµήµα>;

∆ιεργασία 1 start: flag[1] = 0; while (flag[0] != 0) do noop; flag[1] = 1; if (flag[0] == 1) then goto start; <κρίσιµο τµήµα>; flag[1] = 0; <µη-κρίσιµο τµήµα>;

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

Είναι η λύση του Σχήµατος 44 δίκαιη λύση; Άσκηση Αυτοαξιολόγησης 23 (Μέρος Θέµατος 3, 4η Εργασία, Ακ. Έτος 2001-2002) Εξετάστε αν η λύση που προτείνεται στο Σχήµα 45 είναι σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες. Κοινές Μεταβλητές 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;

<κρίσιµο τµήµα>; busy = 0; <µη-κρίσιµο τµήµα>; Σχήµα 45: Όγδοη προτεινόµενη λύση µε χρήση λογισµικού στο πρόβληµα του αµοιβαίου αποκλεισµού.

97

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Η ρουτίνα GetPid() επιστρέφει τον ίδιο πάντα ακέραιο όταν καλείται από τη διεργασία 0 (π.χ., ας υποθέσουµε ότι η GetPid() επιστρέφει το 0 στη διεργασία 0) και τον ίδιο πάντα ακέραιο, διαφορετικό από αυτόν που επιστρέφει στη διεργασία 0, όταν καλείται από τη διεργασία 1 (π.χ., ας υποθέσουµε ότι η GetPid() επιστρέφει το 1 στη διεργασία 1). Εποµένως, ο κώδικας που εκτελεί η κάθε διεργασία είναι αυτός που φαίνεται στο Σχήµα 46. ∆ιεργασία 0

∆ιεργασία 1

1. start: while (busy == 1) noop; 2. trying = 0; 3. if (busy == 1) goto start; 4. busy = 1; 5. if (trying != 0) goto start;

1. start: while (busy == 1) noop; 2. trying = 1; 3. if (busy == 1) goto start; 4. busy = 1; 5. if (trying != 1) goto start;

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

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

busy = 0;

busy = 0;

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

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

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

Άσκηση Αυτοαξιολόγησης 24 (Μέρος Θέµατος 3, 4η Εργασία, Ακ. Έτος 2001-2002) Εξετάστε αν η λύση που προτείνεται στο Σχήµα 47 είναι σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες. Κοινές µεταβλητές 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; end <κρίσιµο τµήµα>; busy = 0; <µη-κρίσιµο τµήµα>; Σχήµα 47: Ένατη προτεινόµενη λύση στο πρόβληµα του αµοιβαίου αποκλεισµού για δύο διεργασίες.

98

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

3.6 Σηµαφόροι Οι σηµαφόροι προτάθηκαν από τον Dijkstra [] το 1965. Η χρήση τους συζητείται εκτενώς σε όλα τα βιβλία λειτουργικών συστηµάτων []. Ένας σηµαφόρος είναι µια διαµοιραζόµενη ακέραια µεταβλητή την οποία οι διεργασίες µπορούν να προσβαίνουν µόνο µέσω της εκτέλεσης δύο λειτουργιών που ονοµάζονται 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() ενός σηµαφόρου µε χρήση λογισµικού φαίνεται στο Σχήµα 48. Στην υλοποίηση αυτή δεν παρουσιάζεται ο τρόπος µε τον οποίο επιτυγχάνεται η ατοµική εκτέλεση των λειτουργιών αυτών. Λειτουργία up(semaphore S) S = S + 1;

Λειτουργία down(semaphore S) while (S <= 0) do noop; S = S -1;

99

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

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

/* αρχική τιµή 1 */

Κώδικας που εκτελεί κάθε διεργασία repeat begin down(mutex); <κρίσιµο τµήµα>; up(mutex); <µη-κρίσιµο τµήµα>; end forever; Σχήµα 49: Λύση Προβλήµατος Αµοιβαίου Αποκλεισµού µε Σηµαφόρους.

Εξηγούµε στη συνέχεια γιατί η λύση του Σχήµατος 49 είναι σωστή. Η λύση χρησιµοποιεί έναν σηµαφόρο mutex που έχει αρχική τιµή 1. Έστω ότι δύο ή περισσότερες διεργασίες επιθυµούν να εισέλθουν στο κρίσιµο τµήµα τους. Ας θυµηθούµε ότι η down() εκτελείται 100

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

ατοµικά. Η πρώτη διεργασία, έστω Α, που θα την καλέσει, θα µειώσει την τιµή του σηµαφόρου σε 0 και θα εισέλθει στο κρίσιµο τµήµα της. Όλες οι υπόλοιπες διεργασίες που θα εκτελέσουν την down() όσο η Α βρίσκεται στο κρίσιµο τµήµα της, θα βρουν την τιµή του mutex ίση µε 0 και θα απενεργοποιηθούν. Μόνο όταν αργότερα η Α εξέλθει από το κρίσιµο τµήµα της και εκτελέσει την up(), η τιµή του σηµαφόρου θα αλλάξει σε 1 και έτσι θα επιτραπεί σε µια ακόµη διεργασία να εισέλθει στο κρίσιµο τµήµα της (οι υπόλοιπες θα βρουν και πάλι την τιµή του σηµαφόρου ίση µε 0 και θα απενεργοποιηθούν για άλλη µια φορά). Έτσι, µόνο µια διεργασία µπορεί να βρίσκεται κάθε χρονική στιγµή στο κρίσιµο τµήµα της. ∆εν είναι δύσκολο να συµπεράνουµε ότι η λύση του Σχήµατος 49 ικανοποιεί και τη συνθήκη προόδου. Εποµένως, η λύση του Σχήµατος 49 είναι σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού. Η απλότητα της λύσης αυτής οφείλεται στην υπόθεση ότι το σύστηµα µας παρέχει ένα ισχυρό εργαλείο συγχρονισµού, όπως οι σηµαφόροι. Σηµαφόροι όπως ο mutex στο Σχήµα 49, που χρησιµοποιούνται µε τον τρόπο που περιγράφτηκε πιο πάνω για την επίτευξη αµοιβαίου αποκλεισµού, ονοµάζονται δυαδικοί σηµαφόροι. Ένας δυαδικός σηµαφόρος έχει αρχική τιµή 1 (προκειµένου να επιτραπεί στην 1η διεργασία που θα εκτελέσει την down() πάνω στο σηµαφόρο να µην απενεργοποιηθεί και εποµένως να εισέλθει στο κρίσιµο τµήµα της), ενώ οι µοναδικές τιµές που µπορεί να πάρει είναι 0 και 1. Επαφίεται στον αναγνώστη να επαληθεύσει ότι οι ισχυρισµοί αυτοί είναι αληθείς για το σηµαφόρο mutex του Σχήµατος 49. Άσκηση Αυτοαξιολόγησης 25 (Θέµα 1, Ερώτηµα 4, Εργασία 4, Ακ. Έτος 2001-2002 & Θέµα 2, Ερώτηµα 1, Εργασία 4, Ακ. Έτος 2003-2004) Υποθέστε ότι στην υλοποίηση της λειτουργίας down() του Σχήµατος48, ο έλεγχος της while και η ενδεχόµενη µείωση της τιµής του S που ακολουθεί δεν εκτελούνται ατοµικά. Είναι σε αυτή την περίπτωση σωστή η λύση του προβλήµατος του αµοιβαίου αποκλεισµού που περιγράφεται στο Σχήµα 49; Άσκηση Αυτοαξιολόγησης 26 (Μέρος Άσκησης Αυτοαξιολόγησης 3.12 Τόµου Γ) Αποτελεί ο κώδικας του Σχήµατος 50 σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού; Σηµαφόροι semaphore mutex;

/* µε αρχική τιµή 1 */

Κώδικας που εκτελεί κάθε διεργασία down(mutex); <κρίσιµο τµήµα>; down(mutex); <µη-κρίσιµο τµήµα>; Σχήµα 50: Ένα κοινό λάθος στη χρήση σηµαφόρων.

Λύση Είναι σχετικά εύκολο να επαληθεύσετε ότι ο κώδικας του Σχήµατος 50 ικανοποιεί τη συνθήκη του αµοιβαίου αποκλεισµού. Μια µόνο διεργασία, έστω η Α, θα καταφέρει να 101

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

/* µε αρχική τιµή 1 */

Κώδικας που εκτελεί κάθε διεργασία up(mutex); <κρίσιµο τµήµα>; down(mutex); <µη-κρίσιµο τµήµα>; Σχήµα 51: Ένα ακόµη κοινό λάθος στη χρήση σηµαφόρων.

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

102

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

του αν δεν έχει βεβαιωθεί πως έχει κατανοήσει σε βάθος που οφείλεται η ορθότητα της πρώτης λύσης και ποια είναι τα προβλήµατα των δύο τελευταίων. Άσκηση Αυτοαξιολόγησης 28 (Μέρος Θέµατος 2, Εργασία 4, Ακ. Έτος 2001-2002 ) Περιγράψτε τις ρουτίνες boolean deposit(int amount) και boolean withdraw(int amount), που περιγράφτηκαν στην Άσκηση Αυτοαξιολόγησης 8. Η λύση σας θα πρέπει να επιτυγχάνει αµοιβαίο αποκλεισµό χρησιµοποιώντας σηµαφόρους. Άσκηση Αυτοαξιολόγησης 29 Είναι η λύση που παρουσιάζεται στο Σχήµα 49 δίκαιη λύση του προβλήµατος του αµοιβαίου αποκλεισµού; Περιγράψτε σενάριο στο οποίο µια διεργασία εισέρχεται µηπεπερασµένο αριθµό φορών στο κρίσιµο τµήµα της, ενώ άλλες διεργασίες παραµένουν µπλοκαρισµένες επ’ άπειρον εκτελώντας τον κώδικα εισόδου. Άσκηση Αυτοαξιολόγησης 30 (Παραλλαγή Άσκησης Αυτοαξιολόγησης 3.11 Τόµου Γ) Έχει γίνει σαφές πως αν έχουµε σηµαφόρους µπορούµε εύκολα να επιλύσουµε το πρόβληµα του αµοιβαίου αποκλεισµού. Ωστόσο, οι λειτουργίες των σηµαφόρων δεν παρέχονται εξ ουρανού. Οι λειτουργίες αυτές είναι σχετικά σύνθετες και η ατοµική τους εκτέλεση πρέπει µε κάποιο τρόπο να υλοποιηθεί. Θεωρείστε ότι το ΛΣ ενός συστήµατος δεν υποστηρίζει σηµαφόρους, αλλά το υλικό του συστήµατος παρέχει την λειτουργία Test&Set(). Υποθέστε ότι η εταιρεία στην οποία εργάζεστε σας αναθέτει να υλοποιήσετε τις ρουτίνες down() και up() που υποστηρίζει µια µεταβλητή τύπου σηµαφόρος. Παρουσιάστε τη λύση που θα προτείνατε. Λύση Η άσκηση αυτή δεν είναι απλή. Ο αναγνώστης δεν θα πρέπει να απαγοητεύεται αν νιώθει πως δεν µπορεί να σκιαγραφήσει κάποια πιθανή λύση. Το νέο στοιχείο στην άσκηση αυτή είναι πως ζητείται να υλοποιηθεί (ή να προσοµοιωθεί όπως λέγεται) ένα αντικείµενο, στην περίπτωση µας οι σηµαφόροι, χρησιµοποιώντας άλλου είδους αντκείµενα, στην περίπτωση µας κοινές µεταβλητές που υποστηρίζουν την ατοµική εκτέλεση της Test&Set() λειτουργίας. Θα εργαστούµε ως εξής. Πρέπει να παρέχουµε κώδικα για τις λειτουργίες down() και up() που υποστηρίζονται από τους σηµαφόρους χρησιµοποιώντας κοινές µεταβλητές και την Test&Set() που µας παρέχει το υλικό. Ο κώδικας πρέπει να σχεδιαστεί προσεκτικά, ώστε όταν καλείται οποιαδήποτε από τις λειτουργίες up() και down() που υποστηρίζονται από τους σηµαφόρους, οι ενέργειες που πραγµατοποιούνται να είναι οι αναµενόµενες µε βάση τον ορισµό των down() και up() που δόθηκε παραπάνω. Εποµένως, όταν καλείται η λειτουργία down() θα πρέπει ο κώδικας που θα φτιάξουµε να εγγυάται τα ακόλουθα: 1. Ελέγχει την τιµή του σηµαφόρου και αν είναι µεγαλύτερη του 0 την µειώνει κατά 1. Οι δύο αυτές λειτουργίες πρέπει να εκτελούνται ατοµικά (δηλαδή η εκτέλεση των δύο αυτών λειτουργιών αποτελεί τώρα το κρίσιµο τµήµα µας). 2. Αν η τιµή του σηµαφόρου είναι 0, η διεργασία θα πρέπει να µπλοκάρεται.

103

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Όταν καλείται η λειτουργία up(), η τιµή του σηµαφόρου πρέπει να αυξάνει κατά 1. Η λειτουργία αυτή θα πρέπει να εκτελείται ατοµικά. Η λύση παρουσιάζεται στο Σχήµα 52. Σηµαφόροι και Κοινές Μεταβλητές 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 Σχήµα 52: Υλοποίηση σηµαφόρων χρησιµοποιώντας ακέραιες διαµοιραζόµενες µεταβλητές και την ατοµική λειτουργία Test&Set() που παρέχεται από το υλικό.

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

ακόµη διαµοιραζόµενη µεταβλητή lock που µας επιτρέπει να χρησιµοποιήσουµε τον κώδικα εισόδου και τον κώδικα εξόδου του Σχήµατος 22, ο οποίος χρησιµοποιεί την Test&Set() για να επιτύχει τον αµοιβαίο αποκλεισµό. Η αύξηση της sem που εκτελεί η up() θα πρέπει να πραγµατοποιείται επίσης ατοµικά. Έτσι, ο κώδικας εισόδου και ο κώδικας εξόδου του Σχήµατος 22 χρησιµοποιούνται για άλλη µια φορά. Φυσικά η ίδια κοινή µεταβλητή lock χρησιµοποιείται τόσο στον κώδικα της down() όσο και στον κώδικα της up(), αφού δεν επιτρέπεται οι δύο λειτουργίες να εκτελούνται ταυτόχρονα. Ο αναγνώστης θα πρέπει να επενδύσει λίγο χρόνο για να βεβαιωθεί πως οι ρουτίνες down() και up() που παρουσιάζονται στο Σχήµα 52 λειτουργούν µε σωστό τρόπο (δηλαδή συµβατό ως προς τον ορισµό τους). □ Άσκηση Αυτοαξιολόγησης 31 (Παραλλαγή Άσκησης Αυτοαξιολόγησης 3.11 Τόµου Γ) Υποθέστε τώρα ότι ούτε το ΛΣ ενός συστήµατος υποστηρίζει σηµαφόρους, ούτε και το υλικό υποστηρίζει την ατοµική εκτέλεση σύνθετων εντολών, όπως η Test&Set() κ.α. Παρουσιάστε µια υλοποίηση σηµαφόρων που θα µπορούν να χρησιµοποιούνται µόνο από δύο διεργασίες, χρησιµοποιώντας τη λύση του Peterson για να επιτύχετε την ατοµική εκτέλεση των λειτουργιών up() και down(). Άσκηση Αυτοαξιολόγησης 32 (Παραλλαγή Άσκησης Αυτοαξιολόγησης 3.11 Τόµου Γ) Επαναλάβετε την προηγούµενη άσκηση αυτοαξιολόγησης χρησιµοποιώντας τώρα, αντί για τη λύση του Peterson, τη λύση που περιγράφεται στο Σχήµα 42 για να επιτύχετε την ατοµική εκτέλεση των λειτουργιών up() και down().

3.7 Προβλήµατα ∆ιαδιεργασιακής Επικοινωνίας Στην ενότητα αυτή θα χρησιµοποιήσουµε σηµαφόρους για να επιλύσουµε ένα σύνολο από ενδιαφέροντα προβλήµατα συγχρονισµού.

3.7.1

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

106

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

∆ιεργασία Εξυπηρέτης repeat begin down(ServerSem); down(mutex); read X; up(mutex); up(ClientSem);

∆ιεργασία Πελάτη repeat begin produce_request(); down(ClientSem); down(mutex); write X; up(mutex);

108

3ο Κεφάλαιο process_request(); end forever;

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός up(ServerSem); end forever;

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

και αν παραληφθούν οι εντολές “down(mutex)” και “up(mutex)” από τον κώδικα του Σχήµατος 54. Η απλούστερη λύση του προβλήµατος πελατών-εξυπηρέτη παρουσιάζεται στο Σχήµα 55. Κοινές µεταβλητές & Σηµαφόροι: semaphore ClientSem = 1; semaphore ServerSem = 0; request X;

∆ιεργασία-Εξυπηρέτης repeat begin down(ServerSem); read X; up(ClientSem); process_request(); end forever;

∆ιεργασία-Πελάτης repeat begin produce_request(); down(ClientSem); write X; up(ServerSem); end forever;

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

Στο σηµείο αυτό τελειώνει η µελέτη του πρώτου απλού προβλήµατος. Άσκηση Αυτοαξιολόγησης 33 (Θέµα 2α, Β’ Τελική Εξέταση, Ιούλιος 2002) Υποθέστε ότι για να συγχρονιστούν οι διεργασίες πελατών και εξυπηρετητή που συζητήθηκαν πιο πάνω σας δίνεται η ακόλουθη λύση. Κοινές µεταβλητές & Σηµαφόροι: semaphore ClientSem = 1; semaphore ServerSem = 0; request X;

∆ιεργασία-Εξυπηρέτης repeat begin up(ClientActive); read X; down(ServerActive); process_request(); end forever;

∆ιεργασία-Πελάτης repeat begin produce_request(); down(ServerActive); write X; up(ClientActive); end forever;

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

Εξηγείστε γιατί είναι λάθος η λύση του Σχήµατος 56. Άσκηση Αυτοαξιολόγησης 34 (Θέµα 2α, Β’ Τελική Εξέταση, Ιούλιος 2002) Σχολιάστε τη λύση του Σχήµατος 55 ως προς το αν είναι δίκαιη ή όχι.

110

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Άσκηση Αυτοαξιολόγησης 35 (Θέµα 4, 4η Εργασία, Ακ. Έτος 2001-2002) Τρεις µανιώδεις καπνιστές βρίσκονται στο ίδιο δωµάτιο µαζί µε ένα πωλητή ειδών καπνιστού. Για να φτιάξει και να χρησιµοποιήσει τσιγάρα, κάθε καπνιστής χρειάζεται τρία συστατικά: καπνό, χαρτί και σπίρτα. Όλα αυτά τα παρέχει ο πωλητής σε αφθονία. Ένας καπνιστής έχει το δικό του καπνό, ένας δεύτερος το δικό του χαρτί και ο τρίτος τα δικά του σπίρτα. Η δράση ξεκινά όταν ο πωλητής τοποθετεί στο τραπέζι δύο από τα απαραίτητα υλικά, επιτρέποντας έτσι σε έναν από τους καπνιστές να καπνίσει. Όταν ο κατάλληλος καπνιστής τελειώσει το κάπνισµα, ο πωλητής θα πρέπει να αφυπνίζεται, ώστε να τοποθετεί στο τραπέζι δύο ακόµη από τα υλικά του – τυχαία – µε αποτέλεσµα να επιτρέπει και σε άλλον καπνιστή να καπνίσει. Γράψτε κατάλληλα προγράµµατα για τους καπνιστές και τους πωλητές, µε σκοπό την επίλυση του συγκεκριµένου προβλήµατος. Η λύση σας θα πρέπει να βασίζεται στη χρήση σηµαφόρων. Σκιαγράφηση Λύσης: Θα πρέπει να δουλέψουµε µε παρόµοιο τρόπο όπως στο πρόβληµα πελατών-εξυπηρέτη. Αρχικά, θα πρέπει να απαντήσουµε το ερώτηµα τι είδους διεργασίες αναµειγνύονται; Υπάρχει µια διεργασία πωλητή και τρεις διεργασίες καπνιστών. Στο παράδειγµά µας, ο κοινός πόρος είναι το τραπέζι και η πρόσβαση σε αυτό θα πρέπει να γίνεται ατοµικά. Το επόµενο ερώτηµα είναι πόσες ακόµη καταστάσεις (ή περιπτώσεις) υπάρχουν στις οποίες κάποιο είδος διεργασιών πρέπει να απενεργοποιείται. Οι περιπτώσεις αυτές είναι οι ακόλουθες: •

Η διεργασία πωλητή πρέπει να απενεργοποιείται µέχρι ο εκάστοτε καπνιστής να χρησιµοποιήσει τα υλικά που τοποθετήθηκαν στο τραπέζι και το τραπέζι να γίνει άδειο ξανά. Χρειαζόµαστε εποµένως έναν σηµαφόρο για την απενεργοποίηση της διεργασίας πωλητή. Ας ονοµάσουµε τον σηµαφόρο αυτό SellerSem.

Η απάντηση του ερωτήµατος για τις διεργασίες καπνιστών είναι λίγο πιο δύσκολη. Έστω ότι οι τρεις διεργασίες καπνιστών είναι η 0, η 1 και η 2. Με βάση την εκφώνηση συµπεραίνουµε ότι: •

Η διεργασία καπνιστή 0 θα πρέπει να απενεργοποιείται όσο δεν υπάρχουν χαρτί και σπίρτα στο τραπέζι.



Η διεργασία Β θα πρέπει να απενεργοποιείται όσο δεν υπάρχουν καπνός και σπίρτα στο τραπέζι.



Η διεργασία Γ θα πρέπει να απενεργοποιείται όσο δεν υπάρχουν καπνός και χαρτί στο τραπέζι.

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

καπνιστή (κάτι που δεν ίσχυε για τις διεργασίες πελατών, όπου οποιαδήποτε από τις διεργασίες πελατών θα µπορούσε να είναι αυτή που θα παράγει την επόµενη αίτηση). Στη συνέχεια δουλεύουµε µε ίδιο ακριβώς τρόπο όπως στην Ενότητα 3.7.1, ώστε να καταλήξουµε στη λύση που παρουσιάζεται στο Σχήµα 57. Στη λύση αυτή, θεωρούµε ότι η ρουτίνα DecideWhichMaterialsToSell() αποφασίζει µε τυχαίο τρόπο ποια δύο από τα υλικά θα τοποθετηθούν κάθε φορά στο τραπέζι (µη κρίσιµο τµήµα διεργασίας πωλητή), ενώ µέσω της ρουτίνας TakeMaterialsFromTable() ένας καπνιστής παίρνει τα υλικά από το τραπέζι και τα χρησιµοποιεί (κρίσιµο τµήµα διεργασιών καπνιστών). Κοινές µεταβλητές & Σηµαφόροι: semaphore SmokerSem[3] = {0, 0, 0}; semaphore SellerSem = 1; shared variable table;

/* αρχικές τιµές 0 και για τους 3 σηµαφόρους */

∆ιεργασία-Πωλητής

∆ιεργασία-Καπνιστής i, 0 <= i <= 2

repeat repeat begin begin down(SmokerSem[i]); DecideWhichMaterialsToSell(); TakeMaterialsFromTable(); down(SellerSem); up(SellerSem); update table; end if (υλικά που τοποθετήθηκαν στο forever; τραπέζι είναι χαρτί και σπίρτα) then up(SmokerSem[0]); else if (υλικά που τοποθετήθηκαν στο τραπέζι είναι καπνός και σπίρτα) then up(SmokerSem[1]); else up(SmokerSem[2]); end forever; Σχήµα 57: Λύση στο πρόβληµα των καπνιστών-πωλητή.

Παρατηρήστε ότι στη λύση του Σχήµατος 57 δεν χρησιµοποιείται κανένας δυαδικός σηµαφόρος για επίτευξη αµοιβαίου αποκλεισµού, αφού αυτός επιτυγχάνεται λόγω του τρόπου χρήσης των υπολοίπων σηµαφόρων. □ Άσκηση Αυτοαξιολόγησης 36 (Θέµα 4, 4η Γραπτή Εργασία, Ακ. Έτος 2003-2004) Σας ζητείται να εξηγήσετε τι θα συµβεί όταν δύο διεργασίες Α και Β εκτελέσουν τον κώδικα του Σχήµατος 58, δεδοµένου ότι τα SA και SB είναι σηµαφόροι των οποίων ωστόσο δεν γνωρίζετε την αρχική τιµή. Ποιες προτείνετε να είναι οι αρχικές τιµές των σηµαφόρων αυτών και γιατί; ∆ιεργασία Α

∆ιεργασία Β

repeat begin down(SA); print “ping”;

repeat begin down(SB); print “pong”;

112

3ο Κεφάλαιο up(SB); end forever;

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός up(SA); end forever;

Σχήµα 58: Ping-Pong.

3.7.2

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

Ιούνιος 2003) Ένας ζωολογικός κήπος αποτελείται από το χώρο του µουσείου και ένα πάρκο στο οποίο οι επισκέπτες µπορούν να περιηγηθούν χρησιµοποιώντας µικρά αυτοκίνητα του ενός επιβάτη. Υπάρχουν µ αυτοκίνητα. Οι επιβάτες τριγυρνούν στο µουσείο για λίγη ώρα µε τα πόδια και στη συνέχεια περιµένουν σε ουρά για να ανέβουν σε ένα αυτοκίνητο ώστε να κάνουν µια βόλτα στο πάρκο. Όταν ένα αυτοκίνητο είναι διαθέσιµο, φορτώνει έναν επιβάτη (χωρά µόνο έναν) και τριγυρνά στο πάρκο για ένα τυχαίο χρονικό διάστηµα. Αν όλα τα αυτοκίνητα είναι κατειληµµένα από επιβάτες, τότε ένας επιβάτης που θέλει να χρησιµοποιήσει ένα αυτοκίνητο πρέπει να περιµένει. Αν ένα αυτοκίνητο είναι έτοιµο να φορτώσει αλλά δεν υπάρχουν επιβάτες τότε το αυτοκίνητο περιµένει. ∆ουλεύοντας όπως περιγράφτηκε στις προηγούµενες ενότητες, οδηγούµαστε στα εξής συµπεράσµατα: •

Υπάρχουν δύο είδη διεργασιών στο σύστηµα, διεργασίες επιβατών και διεργασίες αυτοκινήτων.



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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

∆ιεργασία Πελάτης 1. 2. 3.

Αν δεν υπάρχει ελεύθερο αυτοκίνητο απενεργοποιήσου; Ενηµέρωσε τα αυτοκίνητα ότι υπάρχει άλλος ένας διαθέσιµος πελάτης; Κάνε ταξίδι;

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

Η λύση παρουσιάζεται στο Σχήµα 60. Κοινές µεταβλητές & Σηµαφόροι: semaphore PassengerSem = 0; semaphore CarSem = µ;

∆ιεργασία-Αυτοκίνητο repeat begin down(PassengerSem); take_trip(); up(CarSem); end forever;

∆ιεργασία-Επιβάτης

down(CarSem); up(PassengerSem); take_trip();

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

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

114

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Χρειαζόµαστε εποµένως έναν δυαδικό σηµαφόρο mutex, µε αρχική τιµή 1, για την επίτευξη του αµοιβαίου αποκλεισµού. Η βελτιωµένη λύση παρουσιάζεται στο Σχήµα 61, όπου οι ρουτίνες ReserveCar () και ReleaseCar() εµπεριέχουν τις ενέργειες κατάληψης και ελευθέρωσης, αντίστοιχα, ενός αυτοκινήτου. Κοινές µεταβλητές & Σηµαφόροι: semaphore PassengerSem = 0; semaphore CarSem = µ; semaphore mutex = 1;

∆ιεργασία-Αυτοκίνητο repeat begin down(PassengerSem); take_trip(); up(CarSem); end forever;

∆ιεργασία-Επιβάτης down(CarSem); down(mutex); up(PassengerSem); car_embarkation(); up(mutex); take_trip(); down(mutex); leave_car(); up(mutex);

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

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

∆ιεργασία-Αυτοκίνητο repeat begin down(PassengerSem); take_trip(); down(EndOfTrip); up(CarSem); end

∆ιεργασία-Επιβάτης down(CarSem); down(mutex); up(PassengerSem); car_embarkation(); up(mutex); take_trip(); down(mutex);

115

3ο Κεφάλαιο forever;

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός leave_car(); up(EndOfTrip); up(mutex);

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

Άσκηση Αυτοαξιολόγησης 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];

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

116

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

Το Πρόβληµα του Κουρέα (Θέµα 3Β, Α’ Τελική Εξέταση, Μάιος 2001)

Ένα κουρείο αποτελείται από την "αίθουσα αναµονής" µε µ καρέκλες και το δωµάτιο στο οποίο εργάζεται ο κουρέας που περιέχει την καρέκλα του κουρέα. Θεωρήστε πως ένας πελάτης που εισέρχεται στο κουρείο περιµένει να εξυπηρετηθεί µόνο αν βρει κενή καρέκλα στην αίθουσα αναµονής, αλλιώς φεύγει από το κουρείο. Αν δεν υπάρχουν πελάτες να εξυπηρετηθούν, ο κουρέας πηγαίνει για ύπνο. Αν ένας πελάτης µπει στο κουρείο και βρει τον κουρέα να κοιµάται τον ξυπνάει. Χρησιµοποιώντας σηµαφόρους, ζητείται αλγόριθµος περιγραφής της λειτουργίας του κουρείου, ο οποίος θα συγχρονίζει τον κουρέα µε τους πελάτες. Ας µελετήσουµε πρώτα το εξής απλούστερο πρόβληµα. Ένα κουρείο απασχολεί ένα κουρέα και έχει άπλετο χώρο για να περιµένουν οι πελάτες του. Αν κάποιος πελάτης βρει τον κουρέα ελεύθερο εξυπηρετείται διαφορετικά περιµένει για να εξυπηρετηθεί. Αν δεν υπάρχει πελάτης για εξυπηρέτηση ο κουρέας απενεργοποιείται. Αν ένας πελάτης βρει τον κουρέα να κοιµάται, τον ξυπνάει. Το πρόβληµα µπορεί εύκολα να µελετηθεί µε τον γνωστό τρόπο που παρουσιάστηκε στις προηγούµενες ενότητες. Συνίσταται ισχυρά στον αναγνώστη να δοκιµάσει να δώσει λύση στο απλό αυτό πρόβληµα χρησιµοποιώντας σηµαφόρους (πριν διαβάσει τη λύση που περιγράφεται στη συνέχεια). Η λύση στο απλό πρόβληµα φαίνεται στο Σχήµα 63. Σηµαφόροι semaphore CustomersSem = 0; semaphore BarbersSem = 1;

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

∆ιεργασία-Πελάτης

up(CustomersSem); down(BarbersSem); get_haircut();

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

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

117

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

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός ∆ιεργασία-Πελάτης

up(CustomersSem); down(BarbersSem); get_haircut();

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

Θα προσπαθήσουµε τώρα να λύσουµε την πιο πολύπλοκη έκδοση του προβλήµατος που περιγράφτηκε αρχικά. Κάθε πελάτης θα πρέπει τώρα να ελέγχει πόσοι πελάτες περιµένουν ήδη στο κουρείο και αν αυτοί είναι περισσότεροι από µ θα πρέπει να αποχωρεί. Παρατηρήστε ότι ο σηµαφόρος CustomersSem έχει τιµή ίση µε τον αριθµό των πελατών σε αναµονή, αλλά αφού δεν επιτρέπεται να διαβάσουµε την τιµή ενός σηµαφόρου, η πληροφορία αυτή δεν είναι δυνατόν να ληφθεί µέσω του σηµαφόρου. Χρειαζόµαστε εποµένως µια διαµοιραζόµενη µεταβλητή που θα µετρά τον αριθµό των πελατών σε αναµονή. Ας ονοµάσουµε την µεταβλητή αυτή cc. Κάθε πελάτης θα ελέγχει αρχικά την cc και αν η τιµή της είναι µ θα αποχωρεί από το κουρείο. Στην αντίθετη περίπτωση, θα αυξάνει την cc κατά ένα και θα απενεργοποιείται περιµένοντας σε κάποια από τις διαθέσιµες καρέκλες για να κουρευτεί. Η διεργασία κουρέας µειώνει την τιµή της cc κατά ένα κάθε φορά που παραλαµβάνει έναν πελάτη για κούρεµα. Η προσπέλαση στην cc (είτε για ανάγνωση ή για εγγραφή) θα πρέπει να γίνεται ατοµικά. Χρησιµοποιούµε εποµένως έναν δυαδικό σηµαφόρο mutex προκειµένου να επιτευχθεί αµοιβαίος αποκλεισµός. Ο κώδικας φαίνεται στο Σχήµα 65. Σηµαφόροι και Κοινές µεταβλητές semaphore CustomersSem = 0; semaphore BarbersSem = 1; semaphore mutex = 1; shared int cc; /* µε αρχική τιµή 0 */

∆ιεργασία-Κουρέας repeat begin down(CustomersSem); down(mutex); cc = cc – 1; up(mutex); cut_hair(); up(BarbersSem); end forever;

∆ιεργασία-Πελάτης down(mutex); if (cc < µ) then begin up(CustomersSem); cc = cc + 1; up(mutex); down(BarbersSem); get_haircut(); end else up(mutex);

118

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

3.7.4

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

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

∆ιεργασία-Εγγραφέας down(DataBase); write_data(); up(DataBase);

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

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

/* µε αρχική τιµή 0 */ /* δυαδικός σηµαφόρος για αποκλειστική πρόσβαση στην rc */ /* δυαδικός σηµαφόρος για αποκλειστική πρόσβαση στη βάση */

119

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

∆ιεργασία-Εγγραφέας repeat begin down(DataBase); write_data(); up(DataBase); end forever;

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

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

120

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

Στην παραπάνω ρουτίνα, η παράµετρος direction µπορεί να είναι 0 ή 1, υποδηλώνοντας την κατεύθυνση του αυτοκινήτου. Σας ζητείται να δώσετε ψευδοκώδικα για τις υπορουτίνες ArriveBridge() και ExitBridge(). Θα πρέπει να χρησιµοποιήσετε σηµαφόρους προκειµένου να επιτύχετε συγχρονισµό. Η υπορουτίνα ArriveBridge() θα πρέπει να επιστρέφει µόνο αν είναι εγγυηµένο πως είναι ασφαλές για το αυτοκίνητο να περάσει τη γέφυρα (δηλαδή δεν υπάρχει αυτοκίνητο που διασχίζει την γέφυρα προς την αντίθετη κατεύθυνση). Η ExitBridge() πρέπει να παρέχει τις κατάλληλες ενέργειες, ώστε να είναι δυνατή η διάσχιση της γέφυρας από νέα αυτοκίνητα. Η λύση που θα δώσετε δεν απαιτείται να είναι δίκαιη. Ωστόσο, θα πρέπει να εξηγήσετε αν η λύση σας παρέχει κάποιου είδους δικαιοσύνη. Άσκηση Αυτοαξιολόγησης 42 – Το πρόβληµα της ∆ιάσχισης της Χαράδρας (Άσκηση Αυτοαξιολόγησης 3.1 Τόµου Γ ) Ένα σχοινί είναι δεµένο από τη µια άκρη µιας χαράδρας ως την άλλη, έτσι ώστε όποιος θέλει να διασχίσει τη χαράδρα να το κάνει κρεµασµένος από το σχοινί. Αν χρησιµοποιήσουν το σχοινί άτοµα που κινούνται και προς τις δύο κατευθύνσεις, θα δηµιουργηθεί πρόβληµα (αδιέξοδο) στο σηµείο συνάντησής τους πάνω από τη χαράδρα. Εποµένως, όταν κάποιος θελήσει να διασχίσει τη χαράδρα, πρέπει πρώτα να ελέγχει αν έρχεται άλλος από την αντίθετη κατεύθυνση. Επιλύστε το παραπάνω πρόβληµα συγχρονισµού.

3.7.5

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

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

121

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 διεργασιών φαίνεται στο Σχήµα 69. ∆ιεργασία P1

∆ιεργασία P2

Εκτέλεσε τον κώδικα σου;

Εκτέλεση τον κώδικά σου;

Ενηµέρωσε την P3 ότι τελείωσε η εκτέλεση;

Ενηµέρωσε την P3 ότι τελείωσε η εκτέλεση;

∆ιεργασία P3

∆ιεργασία P4

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

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

122

3ο Κεφάλαιο Περίµενε τον τερµατισµό της εκτέλεσης της P2;

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός Εκτέλεσε τον κώδικά σου;

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

∆ιεργασία P5 Περίµενε τον τερµατισµό εκτέλεσης της P3; Εκτέλεσε τον κώδικά σου; Σχήµα 69: Περιγραφή των ενεργειών των διεργασιών της Άσκησης Αυτοαξιολόγησης 44.

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

123

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός



Μια διεργασία αυτοκίνητο στο νησί απενεργοποιείται αν το πλοίο δεν είναι στο νησί και υπάρχει χώρος στο parking.



Μια διεργασία αυτοκίνητο στην ηπειρωτική χώρα απενεργοποιείται αν το πλοίο είναι στο νησί.



Η διεργασία πλοίο δεν απενεργοποιείται ποτέ.

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

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

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

124

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

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

3.7.6

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

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

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

125

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

shared T u; region u do begin <µπλοκ εντολών S>; end Σχήµα 71: Γλωσσική Έκφραση του Hansen.

Όπως έχουµε ήδη δει, η εκτέλεση της κρίσιµης περιοχής µιας διεργασίας απαιτεί αµοιβαίο αποκλεισµό. Το ερώτηµα πως θα υλοποιήσουµε κρίσιµες περιοχές (δηλαδή πως θα επιλύσουµε το πρόβληµα του αµοιβαίου αποκλεισµού) έχει ήδη αναλυθεί αρκετά. Για παράδειγµα, µια τέτοια υλοποίηση µπορεί να χρησιµοποιεί σηµαφόρους, ή την εντολή Test&Set(), ή τη λύση του Peterson, κλπ. Στην ενότητα αυτή θα χρησιµοποιούµε την γλωσσική έκφραση που περιγράφτηκε πιο πάνω, αφαιρετικά, για να καθορίσουµε ότι το µπλοκ εντολών S πρέπει να εκτελεστεί ατοµικά. Το πως αυτό θα επιτευχθεί εξαρτάται από το εκάστοτε σύστηµα και τα εργαλεία συγχρονισµού που παρέχει. Παράδειγµα (Μέρος Θέµατος 2, Εργασία 4, Ακ. Έτος 2001-2002) Περιγράψτε και πάλι τις ρουτίνες boolean deposit(int amount) και boolean withdraw(int amount), που περιγράφονται στην Άσκηση Αυτοαξιολόγησης 8, χρησιµοποιώντας τη γλωσσική έκφραση του Hansen για αµοιβαίο αποκλεισµό. Οι µη-ατοµικές εκδόσεις των ρουτινών deposit() και withdraw() έχουν συζητηθεί στην Ενότητα 3.2. ∆εδοµένου ότι τα κρίσιµα τµήµατα αυτών είναι γνωστά, ο ψευδοκώδικάς παρουσιάζεται στο Σχήµα 72. Κοινές µεταβλητές shared int balance;

boolean deposit(int amount) int tmp;

/* τοπική µεταβλητή */

begin region balance do begin balance = balance + amount; print “Το νέο σας ποσό είναι”; print balance; end return TRUE; end

boolean withdraw(int amount) begin region balance do begin tmp = balance; end if (amount > tmp) then begin print “∆εν επιτρέπεται ανάληψη τόσο µεγάλου ποσού!”; return FALSE; end else begin region balance do begin

126

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός balance = balance – amount; print “Το νέο σας ποσό είναι”; print balance; end return TRUE; end end

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

Άσκηση Αυτοαξιολόγησης 46 Εξηγήστε πως υλοποιείται η γλωσσική έκφραση του Hansen χρησιµοποιώντας όλους τους δυνατούς τρόπους συγχρονισµού που µελετήθηκαν σε προηγούµενες ενότητες. □ Θα δούµε στη συνέχεια µια ισχυρότερη γλωσσική έκφραση από εκείνη του Hansen, η οποία επιτρέπει σε µια διεργασία να περιµένει έως µια αυθαίρετη συνθήκη πάνω στο κοινό πόρο να ικανοποιηθεί. Η γλωσσική έκφραση περιγράφεται στη συνέχεια: shared T u;

/* η διαµοιραζόµενη µεταβλητή u είναι τύπου Τ */

region u do begin S 0; await B(u); S 1; end Σχήµα 73: Γλωσσική Έκφραση Κρίσιµης Περιοχής υπό Συνθήκη.

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

127

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

κρίσιµων περιοχών υπό συνθήκη µπορεί εποµένως να χρησιµοποιηθεί για την επίλυση σύνθετων προβληµάτων συγχρονισµού, όπως αυτά που µελετήθηκαν στην Ενότητα 3.7. Παράδειγµα 10 – Πρόβληµα Ζωολογικού Κήπου Ας επανέλθουµε στο πρόβληµα του Ζωολογικού Κήπου που µελετήθηκε στην Ενότητα 3.7.2. Ζητείται λύση στο πρόβληµα χρησιµοποιώντας κρίσιµες περιοχές υπό συνθήκη αντί για σηµαφόρους. Ο τρόπος εργασίας µας είναι αντίστοιχος µε εκείνον όταν χρησιµοποιούνται σηµαφόροι. Ωστόσο, για κάθε περίπτωση στην οποία κάποιου είδους διεργασίες πρέπει να απενεργοποιούνται, θα υπάρχει τώρα µια διαµοιραζόµενη µεταβλητή, που θα ονοµάζεται µεταβλητή συνθήκης, και όχι ένας σηµαφόρος. Επίσης, δεν θα χρησιµοποιούµε σηµαφόρους για αµοιβαίο αποκλεισµό, αφού η ίδια η γλωσσική έκφραση τον εγγυάται. Για το πρόβληµα του ζωολογικού κήπου θα χρησιµοποιήσουµε εποµένως δύο διαµοιραζόµενες µεταβλητές, µία που θα µετράει τον αριθµό των διαθέσιµων αυτοκινήτων (ας την ονοµάσουµε Cars) και µία που θα µετρά τον αριθµό των επιβατών (ας την ονοµάσουµε Passengers) στο σύστηµα. Η Cars έχει αρχική τιµή µ, ενώ η Passengers έχει αρχική τιµή 0 (παρατηρήστε την αντιστοιχία που υπάρχει µεταξύ των µεταβλητών Cars και Passengers και των σηµαφόρων CarSem και PassengerSem, αντίστοιχα). Η ανάλυση των ενεργειών που εκτελεί κάθε είδος διεργασιών γίνεται µε τον ίδιο ακριβώς τρόπο όπως και για τους σηµαφόρους . Μια απλή λύση φαίνεται στο Σχήµα 74. Κοινές µεταβλητές shared record zoo begin int Passengers; int Cars; end; shared record zoo b;

/* µε αρχική τιµή 0 για το πεδίο b.Passengers και µ για το πεδίο b.Cars */

∆ιεργασία Αυτοκίνητο repeat begin region b do begin await b.Passengers > 0; b.Passengers = b.Passengers – 1; end take_trip(); region b do begin b.Cars = b.Cars + 1; end end forever;

∆ιεργασία Επιβάτης repeat begin region b do begin await b.Cars > 0; b.Cars = b.Cars - 1; b.Passengers = b.Passengers +1; end take_trip(); end forever;

128

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

∆ιαισθητικά, για να µετατρέψουµε µια λύση βασισµένη σε σηµαφόρους, σε µια λύση βασισµένη σε κρίσιµες περιοχές υπό συνθήκη, κάνουµε τα εξής. Για κάθε σηµαφόρο S, που δεν χρησιµοποιείται απλά για αποκλειστική πρόσβαση σε κάποιο κοινό πόρο, χρησιµοποιούµε µια µεταβλητή συνθήκης V. Κάθε εντολή down(S) αντικαθίσταται µε µια εντολή await σε κάποια boolean συνθήκη της V που ακολουθείται από µια µείωση κατά ένα της τιµής της V. Κάθε εντολή up(S) αντικαθίσταται µε µια εντολή αύξησης κατά ένα της τιµής της V. Όλες οι παραπάνω ενέργειες πρέπει να αποτελούν το µπλοκ εντολών µίας (ή και περισσότερων) εκφράσεων region. Η λύση του Σχήµατος 74 είναι αντίστοιχη (δηλαδή παρουσιάζει τα προτερήµατα και µειονεκτήµατα) εκείνης του Σχήµατος 60. Ας προσπαθήσουµε να παρουσιάσουµε πιο βελτιωµένες λύσεις, όπως αυτές που περιγράφονται στα Σχήµατα 61 και 62. Η λύση του Σχήµατος 61 χρησιµοποιεί έναν ακόµη σηµαφόρο για επίτευξη αµοιβαίου αποκλεισµού. Η έκφραση region παρέχει αυτόµατα αµοιβαίο αποκλεισµό, και άρα δεν χρειαζόµαστε παραπάνω µεταβλητές συνθήκης. Η αντίστοιχη λύση παρουσιάζεται στο Σχήµα 75. Κοινές µεταβλητές shared record zoo begin int Passengers; int Cars; end; shared record zoo b;

/* µε αρχική τιµή 0 για το πεδίο b.Passengers και µ για το πεδίο b.Cars */

∆ιεργασία Αυτοκίνητο repeat begin region b do begin await b.Passengers > 0; b.Passengers = b.Passengers – 1; end take_trip(); region b do begin b.Cars = b.Cars + 1; end end forever;

∆ιεργασία Επιβάτης repeat begin region b do begin await b.Cars > 0; b.Cars = b.Cars - 1; b.Passengers = b.Passengers +1; car_embarkation(); end take_trip(); region b do begin leave_car(); end end forever;

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

129

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Η λύση του Σχήµατος 62 χρησιµοποιεί έναν ακόµη σηµαφόρο, τον EndOfTrip. Χρειαζόµαστε εποµένως µια ακόµη µεταβλητή συνθήκης (ας την ονοµάσουµε επίσης EndOfTrip) µε αρχική τιµή 0 (όπως και η αρχική τιµή του σηµαφόρου). Ο κώδικας φαίνεται στο Σχήµα 76. Κοινές Μεταβλητές shared record zoo begin int Passengers; int Cars; boolean EndOfTrip; end; shared record zoo b;

/* µε αρχική τιµή 0 για τα πεδία b.Passengers και b.EndOfTrip και αρχική τιµή µ για το πεδίο b.Cars */

∆ιεργασία Αυτοκίνητο repeat begin region b do begin await b.Passengers > 0; b.Passengers = b.Passengers – 1; end take_trip(); region b do begin await b.EndOfTrip > 0; b.EndOfTrip = b.EndOfTrip – 1; b.Cars = b.Cars + 1; end end forever;

∆ιεργασία Επιβάτης repeat begin region b do begin await b.Cars > 0; b.Cars = b.Cars - 1; b.Passengers = b.Passengers +1; car_embarkation(); end take_trip(); region b do begin leave_car(); b.EndOfTrip = 1; end end forever;

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

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

130

3ο Κεφάλαιο shared int Passengers; shared int Cars; shared boolean EndOfTrip;

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός /* µε αρχική τιµή 0 */ /* µε αρχική τιµή µ */ /* µε αρχική τιµή 0 */

∆ιεργασία Αυτοκίνητο repeat begin region Passengers do begin await Passengers > 0; Passengers = Passengers – 1; end take_trip(); region EndOfTrip do begin await EndOfTrip > 0; EndOfTrip = EndOfTrip – 1; end region Cars do begin Cars = Cars + 1; end end forever;

∆ιεργασία Επιβάτης repeat begin region Cars do begin await Cars > 0; Cars = Cars - 1; end region Passengers do begin Passengers = Passengers +1; car_embarkation(); end take_trip(); region EnfOfTrip do begin leave_car(); EndOfTrip = 1; end end forever;

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

Παράδειγµα 11 – Πρόβληµα Αναγνωστών - Εγγραφέων Ας συζητήσουµε τώρα πως µπορούµε να επιλύσουµε ένα ακόµη πρόβληµα, εκείνο των αναγνωστών-εγγραφέων, χρησιµοποιώντας κρίσιµες περιοχές υπό συνθήκη. Μια πρώτη προσπάθεια επίλυσης του προβλήµατος, αντίστοιχη εκείνης του Σχήµατος 66 φαίνεται στο Σχήµα 78. Κοινές Μεταβλητές shared int b;

∆ιεργασία-Αναγνώστης region b do begin read_data(); end

∆ιεργασία-Εγγραφέας region b do begin write_data(); end

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

(που θα παίζει το ρόλο του σηµαφόρου DataBase). Η DataBase θα έχει αρχική τιµή 1. Θα χρησιµοποιηθεί επίσης η κοινή µεταβλητή rc µε αρχική τιµή 0. Ο κώδικας φαίνεται στο Σχήµα 79. Κοινές Μεταβλητές shared record RW begin shared int rc; shared boolean DataBase; end shared record RW b; /* µε αρχική τιµή 0 για το πεδίο b.rc και αρχική τιµή 1 για το πεδίο b.DataBase */

∆ιεργασία-Αναγνώστης region b do begin if (b.rc == 0) then begin await b.DataBase > 0; b.DataBase = b.DataBase - 1; 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

∆ιεργασία-Εγγραφέας region b do begin await b.DataBase > 0; b.DataBase = b.DataBase – 1; write_data(); b.DataBase = b.DataBase + 1; end

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

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

κώδικα του Σχήµατος 67. Η διαφορά µεταξύ των δύο κωδίκων είναι πως αν µια διεργασία µπλοκάρει σε κάποια 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). Ο αλγόριθµος του Αρτοπωλείου παρουσιάζεται στο Σχήµα 80. Κοινές Μεταβλητές 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 <κρίσιµο τµήµα>;

133

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

number[i] = 0; <µη-κρίσιµο τµήµα>; Σχήµα 80: Ο αλγόριθµος του Αρτοπωλείου.

Κάθε διεργασία 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]).

134

3ο Κεφάλαιο

3.9.2

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Λύση του Dekker

Στο Σχήµα 81 παρουσιάζεται µια ακόµη δίκαιη λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που παρουσιάστηκε από τον Dekker. Η λύση είναι σωστή για δύο διεργασίες, αλλά µπορεί να γενικευτεί και για n > 2 διεργασίες. Κοινές Μεταβλητές shared int flag[2]; shared int priority;

/* µε αρχική τιµή TRUE ή FALSE, δεν παίζει ρόλο */ /* µε αρχική τιµή 0 ή 1, δεν παίζει ρόλο */

∆ιεργασία 0 repeat begin flag[0] = TRUE; while (flag[1] == TRUE) do begin if (priority == 1) then begin flag[0] = FALSE; while (priority == 1) do noop; flag[0] = TRUE; end else noop; end

∆ιεργασία 1 repeat begin flag[1] = TRUE; while (flag[0] == TRUE) do begin if (priority == 0) then begin flag[1] = FALSE; while (priority == 0) do noop; flag[1] = TRUE; end else noop; end

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

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

priority = 1; flag[0] = FALSE;

priority = 0; flag[1] = FALSE;

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

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

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

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

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, Τόµος Γ, Λειτουργικά Συστήµατα Ι) Προσθέστε σε κάθε γραµµή ψευδοκώδικα του Σχήµατος 81 σχόλια του τύπου: flag[0] = TRUE;

/* Η διαδικασία 0 δηλώνει την πρόθεση της να χρησιµοποιήσει τον πόρο */

while (flag[1] == TRUE) do

/* Όσο η διαδικασία 1 ενδιαφέρεται για τον πόρο */

...

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

3.9.3

∆ικαιοσύνη

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

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

Όπως είδαµε στην Άσκηση Αυτοαξιολόγησης 9 της Ενότητας 3.4, η λύση του προβλήµατος του αµοιβαίου αποκλεισµού µε χρήση της Test&Set() που παρουσιάστηκε στο Σχήµα 22 µπορεί να οδηγήσει σε παρατεταµένη στέρηση. Στην ενότητα αυτή θα χρησιµοποιήσουµε την Test&Set() για να σχεδιάσουµε λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που θα ικανοποιεί επιπρόσθετα και τη συνθήκη αποφυγής παρατεταµένης στέρησης. Η λύση παρουσιάζεται στο Σχήµα 82. Κοινές µεταβλητές /* µε αρχική τιµή FALSE για όλα τα στοιχεία */ /* µε αρχική τιµή FALSE */

boolean waiting[n]; boolean lock;

Κώδικας ∆ιεργασίας 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; Σχήµα 82: ∆ίκαιη λύση στο πρόβληµα του αµοιβαίου αποκλεισµού µε χρήση 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 (για να δώσει τη δυνατότητα σε κάποια από τις διεργασίες που θα επιθυµήσουν στο µέλλον να εισέλθουν να τα καταφέρει). Αν όµως υπάρχουν κάποιες 137

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

διεργασίες που βρίσκονται σε αναµονή (δηλαδή έχουν θέσει την µεταβλητή τους waiting σε TRUE) τότε διαλέγει εκείνη τη διεργασία της οποίας το αναγνωριστικό είναι το πρώτο που έπεται του k (ας ονοµάσουµε τη διεργασία αυτή j) και αλλάζει την τιµή της waiting[j] σε FALSE. Αυτό έχει σαν αποτέλεσµα, την επόµενη φορά που η j θα δροµολογηθεί, να αποτιµήσει την συνθήκη της while του κώδικα εισόδου σε FALSE και θα εισέλθει στο κρίσιµο τµήµα της. Άσκηση Αυτοαξιολόγησης 49 Προσθέστε σχόλια σε κάθε γραµµή ψευδοκώδικα του Σχήµατος 82. Ποιες τιµές έχουν οι µεταβλητές που χρησιµοποιούνται από τον αλγόριθµο όταν κάποια διεργασία i βρίσκεται στο κρίσιµο τµήµα της; Περιγράψτε τον αλγόριθµο σε πρώτο πρόσωπο από την πλευρά της διεργασίας. Άσκηση Αυτοαξιολόγησης 50 Περιγράψτε σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που να ικανοποιεί επιπρόσθετα και τη συνθήκη αποφυγής παρατεταµένης στέρησης, χρησιµοποιώντας την εντολή Fetch&Add() που περιγράφτηκε στο Σχήµα 28. Άσκηση Αυτοαξιολόγησης 51 Περιγράψτε σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που να ικανοποιεί επιπρόσθετα και τη συνθήκη αποφυγής παρατεταµένης στέρησης, χρησιµοποιώντας την εντολή Swap() που περιγράφτηκε στο Σχήµα 29. Άσκηση Αυτοαξιολόγησης 52 Περιγράψτε σωστή λύση στο πρόβληµα του αµοιβαίου αποκλεισµού που να ικανοποιεί επιπρόσθετα και τη συνθήκη αποφυγής παρατεταµένης στέρησης, χρησιµοποιώντας την εντολή RMW() που περιγράφτηκε στο Σχήµα 30. Πιο ∆ίκαιη Λύση στο Πρόβληµα των Αναγνωστών και Εγγραφέων Οι λύσεις στο πρόβληµα των αναγνωστών και εγγραφέων που παρουσιάζονται στην Ενότητα 3.7.4 οδηγούν σε παρατεταµένη στέρηση. Στην ενότητα αυτή θα περιγράψουµε λύση η οποία θα αποφεύγει την παρατεταµένη στέρηση. Αν υπάρχει επιθυµία και από εγγραφείς και από αναγνώστες να προσπελάσουν τη βάση ταυτόχρονα, θα την προσπελαύνουν εναλλάξ, ένας προς έναν, π.χ., πρώτα ένας αναγνώστης, στη συνέχεια ένας εγγραφέας, στη συνέχεια και πάλι ένας αναγνώστης, κ.ο.κ. Η λύση φαίνεται στο Σχήµα 83. Κοινές Μεταβλητές και Σηµαφόροι shared int rc, wc; shared boolean priority; semaphore cSem; semaphore DataBase;

/* µε αρχική τιµή 0 */ /* µε αρχική τιµή 0 ή 1 */ /* δυαδικός σηµαφόρος για αποκλειστική πρόσβαση στις rc, wc */ /* δυαδικός σηµαφόρος για αποκλειστική πρόσβαση στη βάση */

∆ιεργασία Αναγνώστης while (TRUE) do

∆ιεργασία Εγγραφέας while (TRUE) do

138

3ο Κεφάλαιο begin down(cSem); rc = rc + 1; up(cSem); down(DataBase); if (priority == 0) then begin read_data(); down (cSem); rc = rc - 1; if (wc != 0 ) priority = 1; up(cSem); end else begin down(cSem); if (wc == 0) priority = 0; rc = rc -1; up(cSem); end up(DataBase); end

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός begin down(cSem); wc = wc + 1; up(cSem); down(DataBase); if (priority == 1) then begin wite_data(); down (cSem); wc = wc - 1; if (rc != 0 ) priority = 0; up(cSem); end else begin down(cSem); if (rc == 0) priority = 1; wc = wc -1; up(cSem); end up(DataBase); end

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

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

3.9.4

Προσοµοιώσεις

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

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.

140

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

/* όπου s είναι η αρχική τιµή που θα θέλαµε να έχει ο γενικός σηµαφόρος */

Λειτουργία gsem_down()

Λειτουργία gsem_up()

Έλεγξε την τιµή της µεταβλητής cnt;

Αύξησε την τιµή της cnt κατά 1;

Μείωσε την τιµή της cnt κατά 1;

Αν η τιµή της cnt ήταν ≤ 0 ενηµέρωσε τις διεργασίες που επιτελούν την gsem_down() ώστε κάποια από αυτές να µπορέσει να επανενεργοποιηθεί;

Αν η τιµή της cnt είναι < 0 απενεργοποιήσου;

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

Ο τελικός κώδικας φαίνεται στο Σχήµα 85. ∆υαδικοί Σηµαφόροι και Κοινές Μεταβλητές semaphore mutex; semaphore block; int cnt = s;

/* µε αρχική τιµή 1 */ /* µε αρχική τιµή 0 */ /* όπου s είναι η αρχική τιµή που θα θέλαµε να έχει ο γενικός σηµαφόρος */

Λειτουργία gsem_down() down(mutex); cnt = cnt -1; if (cnt < 0) then begin up(mutex); down(block); end else up(mutex);

Λειτουργία gsem_up() down(mutex); cnt = cnt + 1; if (cnt ≤ 0) then up(block); up(mutex);

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

Είναι αξιοσηµείωτο ότι η cnt χρησιµοποιείται επιπρόσθετα για να µετρά τον αριθµό των διεργασιών που έχουν απενεργοποιηθεί πάνω στο γενικό σηµαφόρο. Η πληροφορία αυτή παρέχεται από την τιµή |cnt| όταν cnt < 0. Άσκηση Αυτοαξιολόγησης (Άσκηση Αυτοαξιολόγησης 3.18, Τόµου Γ) Εξηγήστε γιατί οι gsem_down() και gsem_up() λειτουργούν σωστά. Τι ρόλο παίζει κάθε µια από τις δυαδικές σηµαφόρους; Τι θα συµβεί αν αντιµεταθέσουµε τη σειρά των εντολών: 141

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

up(mutex); down(block);

στον κώδικα του gsem_down(); Εξηγήστε πως εξασφαλίζεται ο αµοιβαίος αποκλεισµός όταν τα παρακάτω ζευγάρια διεργασιών εκτελούνται παράλληλα: α) ∆ύο διεργασίες που καλούν ταυτόχρονα την gsem_down(); β) ∆ύο διεργασίες που καλούν ταυτόχρονα την gsem_up(); γ) ∆ύο διεργασίες που η µία καλεί την gsem_down() και η άλλη καλεί την gsem_up(); Άσκηση Αυτοαξιολόγησης (Θέµα 2, Ερώτηµα 2, 4η Γραπτή Εργασία, Ακ. Έτος 20032004) Όπως αναφέρθηκε στην Ενότητα 3.8, µια κρίσιµη περιοχή υπό συνθήκη (σελ. 65 του τόµου ΛΣ, ενότητα 3.7.4) ορίζεται µε την γλωσσική έκφραση: shared T u; region u do begin S0; await B(u); S1; end

Σε ένα ΛΣ που υποστηρίζει σηµαφόρους θα θέλαµε να υλοποιήσουµε τo υψηλότερου επιπέδου εργαλείο συγχρονισµού της κρίσιµης περιοχής υπό συνθήκη. Να συµπληρώσετε το σχέδιο ψευδοκώδικα που παρέχεται στο Σχήµα 86 για µια τέτοια υλοποίηση. Σηµαφόροι και Κοινές Μεταβλητές 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; 142

3ο Κεφάλαιο

∆ιαδιεργασιακή Επικοινωνία & Συγχρονισµός

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

143

Related Documents

Ls Chapters 123 New
November 2019 7
Chapters 123.docx
December 2019 13
Chapters 123 Cyrille.docx
December 2019 4
Ls
September 2019 36
Ls
June 2020 17