HY-345
Λειτουργικά
Συστήματα
ΑΣΚΗΣΗ 4η
Φθινόπωρο 2008
Υλοποίηση
Ουρών
Προτεραιότητας
στο ΜΙΝΙΧ
Ο σκοπός αυτής
της άσκησης
είναι να
εξοικειωθείτε
με τις έννοιες
και την
υλοποίηση του
χρονοπρογραμματισμού
διεργασιών.
Για το σκοπό
αυτό θα αλλάξετε
και θα
επεκτείνετε
τον κυκλικό (round-robin)
αλγόριθμο
χρονοπρογραμματισμού
διεργασιών του
ΜΙΝΙΧ έτσι
ώστε να
χρησιμοποιεί
ουρές προτεραιότητας.
Ο
χρονοπρογραμματιστής
του ΜΙΝΙΧ
διατηρεί τρεις
ουρές : TASK_Q, SERVER_Q και USER_Q. Η TASK_Q έχει
την υψηλότερη
προτεραιότητα,
ενώ η USER_Q έχει
την
χαμηλότερη. Τα tasks
στην ουρά TASK_Q και οι servers στην
ουρά SERVER_Q
είναι non-preemtable,
ενώ οι
διεργασίες
στην ουρά USER_Q
χρονοπρογραμματίζονται
χρησιμοποιώντας
ένα κυκλικό
αλγόριθμο (round - robin) με
κβάντο χρόνου
ίσο με SCHED_RATE
(αρχείο kernel/clock.c,
γραμμή 80).
Σε αυτή την
άσκηση θα
αντικαταστήσετε
τον κυκλικό
αλγόριθμο με
ένα
Χρονοπρογραμματιστή
Προτεραιοτήτων.
Αυτό θα γίνει
ως εξής : η ουρά USER_Q θα
χωριστεί σε
τέσσερις
λογικές ουρές,
κάθε μία από
τις οποίες θα
αποτελεί μία
διαφορετική
ουρά προτεραιοτήτων.
Κάθε ουρά
προτεραιοτήτων
θα έχει μία
τιμή
προτεραιότητας,
από 0 η πρώτη
(υψηλότερη
προτεραιότητα)
μέχρι 3 που θα
έχει η τέταρτη
(χαμηλότερη
προτεραιότητα).
Όταν ο
χρονοπρογραμματιστής
του ΜΙΝΙΧ
πρέπει να
επιλέξει μία
διεργασία από
την ουρά USER_Q,
θα επιλέγει
πρώτα αυτήν
που θα έχει την
υψηλότερη
προτεραιότητα,
ενώ οι
διεργασίες που
έχουν χαμηλότερη
προτεραιότητα
από αυτήν δεν
θα τρέχουν.
Οι ουρές TASK_Q
και SERVER_Q θα
παραμείνουν
ίδιες.
Προτείνουμε να
χρησιμοποιήσετε
μια διπλά
συνδεδεμένη
λίστα ταξινομημένη
κατά την κλάση
προτεραιότητας
για να
υλοποιήσετε
τις τέσσερις
λογικές ουρές.
Για τις
διεργασίες που
ανήκουν στην
ίδια ουρά προτεραιοτήτων,
θα
χρησιμοποιείται
ο κυκλικός
αλγόριθμος (round-robin) με κβάντο
χρόνου όπως
στον παρακάτω
πίνακα
(Table 1):
Ουρά |
Καθορισμός κβάντου |
0 |
Χρήση του συνηθισμένου κβάντου SCHED_RATE (6 clock ticks, 100ms) |
1 |
Χρήση κβάντου διπλάσιου (x 2) από αυτό της ουράς 0. |
2 |
Χρήση κβάντου τετραπλάσιου (x 4) από αυτό της ουράς 0. |
3 |
Χρήση κβάντου οκταπλάσιου (x 8) από αυτό της ουράς 0. |
Table 1: Καθορισμός προτεραιοτήτων μέσα στην ίδια ουρά.
Η ουρά
προτεραιότητας
μιας
διεργασίας
αλλάζει δυναμικά
με τον
παρακάτω
αλγόριθμο.
Κάθε διεργασία
ξεκινάει με
προτεραιότητα
0 στην ουρά 0. Αν
χρησιμοποιήσει
όλο το κβάντο
χρόνου που της
δίνεται,
μετακινείται
στο τέλος της
ουράς 1. Αν δε
χρησιμοποιήσει
το κβάντο της,
τότε μένει στο
τέλος της
ουράς 0. Το ίδιο
συμβαίνει σε
όλες τις ουρές,
επειδή σε
διαφορετική
περίπτωση οι
διεργασίες
στις ουρές 1, 2,
και 3 μένουν
μπλοκαρισμένες.
Έτσι, σε κάθε
ουρά (εκτός της 0)
μετακινούμε
τις διεργασίες
που δεν
χρησιμοποίησαν
όλο το χρόνο
τους (2, 4 και 8
κβάντα
αντίστοιχα)
στο τέλος της
ουράς που βρίσκεται
από πάνω (αυτές
της 1 στην 0, της 2
στην 1, κ.ο.κ). Αν αντίθετα
χρησιμοποίησαν
όλο το χρόνο
τους, τις
μετακινούμε
στο τέλος της
ουράς που
βρίσκεται προς
τα κάτω (εκτός από
αυτές που
είναι ήδη στην
ουρά 3).
Όπως ίσως
παρατηρείτε
στον παραπάνω
αλγόριθμο, οι
διεργασίες που
έχουν πολλούς
υπολογισμούς (computation-bound processes) θα
καταλήξουν
στην ουρά 3 και
πιθανόν να μην
τρέχουν όσο
χρόνο πρέπει.
Για να
απλοποιήσουμε
το πρόβλημα
δεν θα
ασχοληθούμε το
πρόβλημα του starvation.
Ο
χρονοπρογραμματιστής
(sched()
και pick_proc())
δίνει
προτεραιότητα
σε tasks
και servers.
Aν
δεν υπάρχουν tasks ή servers για
να τρέξουν
επιλέγει μια
διεργασία
χρήστη. Ο δικός
σας
χρονοπρογραμματιστής
θα επιλέγει
την διεργασία
χρήστη από την
κατάλληλη
ουρά. Το κβάντο
σε κάθε ουρά
μπορεί να προσoμοιωθεί
με το να
δίνουμε
περισσότερα
κβάντα στις διεργασίες
που έχουν
υψηλή
προτεραιότητα,
χωρίς να
αλλάζουμε το
βασικό SCHED_RATE.
Έτσι, τα κβάντα
χρόνου των
ουρών 1, 2 και 3
είναι κατάλληλα
κατασκευασμένα
ώστε να είναι
πολλαπλάσια του
βασικού
κβάντου των 6 clock ticks, το
οποίο σημαίνει
ότι ο
χρονοπρογραμματιστής
σας θα τρέχει
κάθε 6 clock
ticks.
Τα αρχεία που
χρειάζεται να
διαβάσετε
ή/και να αλλάξετε
για να
υλοποιήσετε τα
ζητούμενα της
άσκησης
φαίνονται στον
παρακάτω
πίνακα:
clock.c |
Το task του ρολογιού. Καλεί τον χρονοπρογραμματιστή |
|
(lock_sched()) κάθε 6 clock ticks. |
proc.c |
Περιέχει τον κώδικα του χρονοπρογραμματιστή |
|
(sched(), pic_proc(), ready(), unready()). |
proc.h |
Ο Πίνακας διεργασιών του ΜΙΝΙΧ. |
const.h |
Δηλώσεις του λειτουργικού συστήματος. |
Table 2: Αρχεία προς αλλαγή.
Ο
χρονοπρογραμματιστής
δουλεύει
κανονικά όπως παρακάτω
: Μία διεργασία
μπλοκάρει όταν
κάνει I/O. Στο
ΜΙΝΙΧ αυτό
γίνεται όταν
μία διεργασία
στέλνει (SEND) ή λαμβάνει (RECEIVE) ένα
μήνυμα. Έτσι οι
διαδικασίες mini_send και mini_rec στο
αρχείο proc.c
(οι οποίες
είναι
υπεύθυνες για
όλο το πέρασμα
μηνυμάτων στο
ΜΙΝΙΧ) καλούν
τις συναρτήσεις
ready()
και unready().
Η συνάρτηση unready()
μετακινεί μια
διεργασία από
το ready
ή running state
σε blocked state.
Αντίθετα η
συνάρτηση ready()
μετακινεί μια
διεργασία από
το blocked state
σε ready state.
Ο
χρονοπρογραμματιστής
επίσης
ενεργοποιείται
σε μία διακοπή
ρολογιού (clock interrupt).
Εξήντα φορές
το
δευτερόλεπτο,
δημιουργείται
ένα hardware
interrupt από το ρολόι
του
συστήματος,
και καλείται η
συνάρτηση clock_handler() που
περιέχεται στο
αρχείο clock.c.
Αν υπάρχει
δουλειά
χρονοπρογραμματισμού,
η clock_handler()
στέλνει ένα
μήνυμα (μέσω
της κλήσης του
"interrupt (CLOCK)",
γραμμή 530) στο task του
ρολογιού
(περιέχεται
στο αρχείο clock.c), το οποίο
καλεί την do_clocktick(). Αυτή η
συνάρτηση
καλεί τον
χρονοπρογραμματιστή
(lock_sched()).
Γενικά όποια συνάρτηση
χρησιμοποιεί
την ουρά USER_Q
χρειάζεται
αλλαγές. Αυτό
περιλαμβάνει
τις συναρτήσεις
: pick_proc(), ready(), unready(),sched(), do_clocktick()
και clock_handler(). Οι
τέσσερις
πρώτες είναι
στο αρχείο proc.c και οι δύο
τελευταίες
είναι στο clock.c. Μην
αλλάξετε την
σταθερά NQ, στο αρχείο const.h, για να
προσθέσετε
νέες ουρές - η
σταθερά αυτή
περιέχει τον
αριθμό των
ουρών TASK_Q, SERVER_Q και USER_Q (δηλαδή 3) και
πρέπει να
μείνει η ίδια.
Απλά κρατήστε
τη μία ουρά
χρήστη (user queue)
ταξινομημένη
κατάλληλα, σαν
να υπήρχαν οι
τέσσερις
λογικές ουρές που
περιγράφουμε
παραπάνω. Οι
διεργασίες δεν
βγαίνουν από
την ready queue,
αλλά αντίθετα
η διεργασία
που τρέχει
κάθε στιγμή
βρίσκεται στην
κορυφή της ready queue. Οι
ουρές είναι
συνδεδεμένες
λίστες αλλά
όχι με δυναμική
μνήμη.
Αντίθετα είναι
απλά δείκτες
κορυφής (head pointer) και
τέλους (tail pointer) σε πεδία
του πίνακα
διεργασιών.
Θα χρειαστεί
επίσης να
προσθέσετε
έναν ακέραιο στη
δομή του
πίνακα
διεργασιών (process table) στο
αρχείο proc.h
που θα
αποθηκεύει την
προτεραιότητα
της διεργασίας
και ίσως
χρειαστείτε
άλλο ένα
ακέραιο για να
αποθηκεύετε
τον αριθμό των
κβάντων χρόνου
που
χρησιμοποίησε
κάθε
διεργασία.
Η συνάρτηση pick_proc() επιλέγει
τον επόμενη
διεργασία που
πρέπει να τρέξει
και άρα αυτή η
συνάρτηση
επιλέγει την
κατάλληλη
ουρά. Επίσης
αυτή η
συνάρτηση
θέτει τον proc_ptr, που
διαβάζεται από
τον interrupt
handler και τον
κώδικα του context-switch. Τέλος
η συνάρτηση sched()
καλείται μόνο
από την do_clocktick()
και μόνο όταν η
διεργασία που
τρέχει τη
στιγμή αυτή
έχει
εξαντλήσει το
κβάντο χρόνου
που της διατέθηκε.
Αν μία
διεργασία
μπλοκάρει πριν
χρησιμοποιήσει
όλο το κβάντο
χρόνου της,
καλείται η
συνάρτηση unready().
Για να
μπορέσετε να
δοκιμάσετε αν
τρέχουν σωστά
οι αλλαγές που
κάνατε στον
χρονοπρογραμματιστή
του ΜΙΝΙΧ (και
να εξεταστείτε
σε αυτες) θα
χρειαστεί να
φτιάξετε :
Παρατηρήσεις
:
README
,
το πολύ 30
γραμμών, με
επεξηγήσεις
για τον τρόπο
υλοποίησης
των system
calls. minix
.
img
,
το οποίο είναι
το image
του minix
που
φορτώνεται
από το bochs. Xρησιμοποιείστε
την εντολή tar
και για
συμπίεση το
πρόγραμμα gzip
).
Παραδώστε το
παραπάνω
αρχείο
χρησιμοποιώντας
το πρόγραμμα submit
(πληκτρολογήστε
~hy345/bin/submit 4 από το directory
που περιέχει
το αρχείο assign4.tar.gz).