Λειτουργικά Συστήματα (ΗΥ-345)
Χειμερινό Εξάμηνο 2013-2014
Άσκηση 4
Φροντιστήριο: 17/12/2013
Παράδοση: 20/1/2014
Limit the CPU usage of a process family in the Linux scheduler
Στην 4η άσκηση του μαθήματος θα τροποποιήσετε τον scheduler του λειτουργικού συστήματος Linux
έτσι ώστε να περιορίζει τον χρόνο εκτέλεσης των διεργασιών,
αν έχει δηλωθεί κάποιο όριο και process family για κάποια διεργασία που ο scheduler
θα μπορούσε να επιλέξει για να τρέξει στο επόμενο κβάντο.
Για να δηλώσετε ένα process family και ένα όριο στον χρόνο εκτέλεσης
των διεργασιών που αποτελούν αυτό το process family (μια διεργασία με όλες τις θυγατρικές της),
θα χρησιμοποιήσετε το system call setproclimit που δημιουργήσατε στην προηγούμενη άσκηση.
Έτσι θα μπορέσετε να δοκιμάσετε τις αλλαγές σας στον scheduler με κάποια test προγράμματα.
Σε αυτήν την άσκηση θα εξοικειωθείτε με τον scheduler του λειτουργικού συστήματος Linux.
1. Τροποποιήσεις στον Linux scheduler
Όπως και στην προηγούμενη άσκηση, θα χρησιμοποιήσετε τον emulator qemu με το ίδιο disk image που σας δώσαμε για
να να ξεκινήσετε το λειτουργικό σύστημα. Δείτε ξανά τις οδηγίες της 3ης άσκησης για τον emulator qemu, το compilation
του Linux kernel με τις δικές σας αλλαγές, και πως να κάνετε boot με το image του καινούργιου kernel image που δημιουργήσατε.
Επίσης, θα ξεκινήσετε την υλοποίηση αυτής της άσκησης από τον αλλαγμένο κώδικα του Linux kernel που έχετε από την 3η άσκηση
(π.χ., από ένα αντίγραφο του κώδικα της 3ης άσκησης).
Ο κώδικας του Linux scheduler βρίσκεται στο αρχείο linux-source-2.6.38.1/kernel/sched.c.
Μπορείτε να διαβάσετε περισσότερα για τον Linux scheduler εδώ.
Η βασική συνάρτηση του Linux scheduler είναι η συνάρτηση schedule().
Αυτήν την συνατηση θα πρέπει να τροποποιήσετε ώς εξής:
- Για κάθε μια διεργασία που είναι έτοιμη για εκτέλεση
και υποψήφια για να γίνει η επόμενη διεργασία που θα εκτελεστεί (next),
θα πρέπει αρχικά να ελέγχετε αν έχει οριστεί process family για αυτήν την διεργασία (root_pid!=-1)
και αν υπάρχει όριο για τον χρόνο εκτέλεσης αυτού του process family (time_limit!=-1).
-
Αν κάτι από τα δυο δεν ισχύει, η διεργασία αυτή παραμένει υποψήφια για να επιλεγεί για εκτέλεση.
-
Αλλιώς:
-
θα πρέπει πρώτα να υπολογίσετε τον χρόνο εκτέλεσης (user+system time) για όλες τις διεργασίες
που ανήκουν στο process family της συγκεκριμένης διεργασίας (τον χρόνο εκτέλεσης της διεργασίας με root_pid
και όλων των θυγατρικών της διεργασιών) μέσα στο τελευταίο time interval.
Δηλαδή, την ίδια διαδικασία που κάνατε για το system call getproclimit() στην προηγούμενη άσκηση,
με μία διαφορά: δεν μας ενδιαφέρει τώρα ο συνολικός χρόνος εκτέλεσης των διεργασιών, αλλά ο χρόνος εκτέλεσής τους
μέσα στο τελευταίο time interval.
Η διάρκεια του time interval έχει επίσης οριστεί από το setproclimit() system call στο πεδίο time_interval.
Οπότε θα έχουμε ένα καινούργιο time interval κάθε time_interval milliseconds, και τότε ο χρόνος εκτέλεσης κάθε
διεργασίας (και process family αντίστοιχα) θα μηδενίζεται.
-
Αν ο χρόνος εκτέλεσης του process family για το current interval δεν ξεπερνάει το όριο time_limit του συγκεκριμένου process family,
τότε η διεργασία αυτή παραμένει υποψήφια για να επιλεγεί για εκτέλεση.
-
Αλλιώς, αν ο χρόνος εκτέλεσης του process family για το current interval ξεπερνάει το όριο time_limit του συγκεκριμένου process family,
η διεργασία αυτή δεν θα θεωρείτε υποψήφια για να επιλεγεί για εκτέλεση σε όλα τα επόμενα βήματα του scheduler
στην συνάρτηση schedule().
Αν εκτός από της διεργασίες οι οποίες έχουν process family που έχει υπερβεί το time limit για το τρέχον time interval
υπάρχει κάποια άλλη διεργασία (ή κάποιες άλλες διεργασίες) που μπορεί να εκτελεστεί, τότε θα επιλεγεί αυτή η διεργασία
(ή κάποια από αυτές τις διεργασίες με βάση τα επόμενα βήματα του scheduler) για εκτέλεση.
Αν δεν υπάρχει καμμία άλλη διεργασία έτοιμη για εκτέλεση εκτός από διεργασίες που έχουν υπερβεί το παραπάνω όριο,
τότε καμμία διεργασία δεν θα πρέπει να εκτελεστεί στον επεξεργαστή.
Οι διεργασίες με process family που έχουν υπερβεί το όριο δεν θα πρέπει να εκτελούνται ούτε όταν δεν υπάρχουν
άλλες διεργασίες έτοιμες για εκτέλεση.
Πρακτικά, θα είναι σαν την περίπτωση που ο Linux scheduler δεν έχει καμμία runnable διεργασία. Θα πρέπει να έχουμε το ίδιο αποτέλεσμα τελικά.
Για να υλοποιήσετε τον παραπάνω αλγόριθμο στον Linux scheduler, θα πρέπει να γνωρίζετε πότε τελειώνει ένα time interval
και ξεκινάει ένα καινούργιο.
Για αυτό το λόγο μπορείτε να χρησιμοποιήσετε μια καινούργια μεταβλητή στον Linux scheduler που θα κρατάει την χρονική
στιγμή που ξεκίνησε το τελευταίο time interval (π.χ., prev_time).
Κάθε φορά που θα εκτελείται o scheduler, θα συγκρίνετε την τρέχουσα ώρα του συστήματος (π.χ., current_time)
με την χρονική στιγμή που κρατάτε για την αρχή του current time interval (prev_time):
αν η τρέχουσα ώρα είναι μεγαλύτερη από την χρονική στιγμή που ξεκίνησε το interval συν τον
χρόνο του κάθε time inteval (current_time > prev_time + time_interval), τότε θα ξεκινάτε ένα καινούργιο time interval.
Σε αυτή τη περίπτωση, η τιμή του prev_time θα αλλάζει σε current_time, και οι χρόνοι εκτέλεσης των διεργασιών θα πρέπει να μηδενίζονται.
θα πρέπει να προσέξετε όλες οι χρονικές τιμές να είναι στην ίδια μονάδα μέτρησης, π.χ., σε milliseconds.
Για να δείτε πως μπορείτε να βρείτε την τρέχουσα ώρα του συστήματος (π.χ. να διαβάσετε την μεταβλητή xtime του kernel) μπορείτε να δείτε το αρχείο linux-2.6.38.1/include/linux/time.h π.χ. για συναρτήσεις που μπορούν να επιστρέψουν την τιμή που ψάχνετε. Επίσης μπορείτε να κοιτάξετε παρόμοια system calls (όπως το gettimeofday) που διαβάζουν την τρέχουσα ώρα του συστήματος.
Αντί να μηδενίζετε τους χρόνους εκτέλεσης των διεργασιών
(επειδή τα πεδία utime και stime του task_struct
χρησιμοποιούνται και από άλλα system calls τα οποία περιμένουν να βρούν τους συνολικούς χρόνους εκτέλεσης των διεργασιών,
και όχι τους χρόνους εκτέλεσης σε κάποιο interval),
μπορείτε να ορίσετε δυο καινούργια πεδία στην δομή task_struct (π.χ., prev_utime και prev_stime).
Σε αυτά τα πεδία μπορείτε να κρατάτε τις αντίστοιχες τιμές των χρόνων εκτέλεσης κάθε διεργασίας όπως ήταν στο τέλος του προηγούμενου time interval.
Έτσι, ο χρόνος εκτέλεσης μιας διεργασίας στο current time interval θα είναι
(utime-prev_utime) + (stime-prev_stime).
Και αντί να μηδενίζετε τους χρόνους εκτέλεσης, απλά μπορείτε να ανανεώνετε τις παραπάνω τιμες για το προηγούμενο interval.
Όλα τα παραπάνω δεν είναι δεσμευτικά. Μπορείτε να υλοποιήσετε όπως προτιμάτε τις αλλαγές στον scheduler για να πετύχετε
τον στόχο που θέλουμε, να περιορίσουμε τον χρόνο εκτέλεσης διεργασιών (μαζί με το υπόλοιπο process family).
2. Δοκιμή του νέου scheduler
Θα πρέπει να φτιάξετε και κάποια test προγράμματα που να δείχνουν ότι οι αλλαγές που κάνατε
στον Linux scheduler δουλεύουν σωστά.
Δύο χαρακτηριστικά test που θέλουμε να κάνετε είναι τα παρακάτω:
- Test 1:
Θα φτιάξετε ένα πρόγραμμα το οποίο θα κάνει ένα δισεκατομύριο πολλαπλασιασμούς.
Το πρόγραμμα επίσης θα δέχετε ένα όρισμα από command line το οποίο θα πρέπει να είναι ακέραιος αριθμός
με τιμές από -1 μέχρι 1000. Αυτό το όρισμα θα είναι η τιμή για το time limit σε milliseconds.
Το time interval θα το ορίζετε ίσο με 1000 milliseconds.
Αν η τιμή του ορίσματος είναι -1, δεν θα κάνετε τίποτα άλλο πριν από τους ένα δισεκατομύριο πολλαπλασιασμούς.
Για οποιαδήποτε άλλη έγκυρη τιμή εκτός από -1, πριν από τους πολλαπλασιασμούς
θα καλείτε το system call setproclimit
με την αντίστοιχη τιμή του ορίσματος για time limit, time interval ίσο με 1000 milliseconds,
και root_pid ίσο με το pid της συγκεκριμένης διεργασίας.
Θα πρέπει να δοκιμάσετε το παραπάνω πρόγραμμα για διαφορετικές τιμές του time limit που θα δίνετε από command line:
π.χ., -1, 0, 10, 25, 50, 75, 100, 250, 500, 750, 1000.
Μετρήστε τον πραγματικό χρόνο εκτέλεσης (real time) του παραπάνω προγράμματος για διαφορετικές τιμές του
time limit (command line argument), χρησιμοποιώντας την εντολή time.
Τι παρατηρείτε? Πως αλλάζει το real time σε σχέση με το time limit? Πως συγκρίνετε με το user time και το system time?
Απαντήστε συνοπτικά στο README.
Επίσης, δοκιμάστε να τρέξετε ταυτόχρόνα (π.χ., χρησιμοποιώντας το & operant sto shell) δύο ή περισσότερες φορές το
παραπάνω πρόγραμμα με διαφορετικά ορίσματα. Τι παρατηρείτε? Η εκτέλεση με ποιό όρισμα τελειώνει νωρίτερα?
- Test 2:
Θα φτιάξετε ένα πρόγραμμα που θα δέχετε δυο command line arguments: το time limit (όπως το παραπάνω πρόγραμμα,
με τιμές από -1 μέχρι 1000) και τον αριθμο διεργασιών που θα δημιουργήσει.
Το πρόγραμμα θα καλέι το setproclimit όπως και στο test 1: μόνο αν η τιμή του πρώτου ορίσματος δεν είναι -1,
και με μέγιστη τιμή το 1000. Το time interval θα είναι 1000 milliseconds και το root_pid το pid της τρέχουσας διεργασίας.
Στη συνέχεια, το πρόγραμμα θα δημιουργεί τον αριθμό διεργασιών σύμφωνα με το δεύτερο όρισμα.
Η κάθε διεργασία θα εκτελεί ένα δισεκατομύριο πολλαπλασιασμούς και θα τερματίζει.
Επαναλάβετε παρόμοια πειράματα με το προηγούμενο test για διαφορετικές τιμές στα ζευγάρια time limit και αριθμό διεργασιών
και συνοψίσετε τα συμπεράσματά σας.
Εκτός από αυτά τα test προγράμματα, μπορείτε να φτιάξετε όσα περισσότερα θέλετε ώστε να
βεβαιωθείτε ότι oι αλλαγές στον Linux scheduler δουλεύουν σωστά.
Τι πρέπει να παραδώσετε
θα πρέπει να παραδώσετε τα παρακάτω αρχεία:
- Το καινούργιο kernel image, δηλαδή το αρχείο linux-2.6.38.1/arch/x86/boot/bzImage.
- Όλα τα αρχεία που τροποποιήσατε ή δημιουργήσατε στον source code του Linux kernel 2.6.38.1 για να υλοποιήσετε τα system calls
στην 3η άσκηση και τις αλλαγές στον scheduler στην 4η άσκηση.
Δηλαδή όλα τα αρχεία .c, .h, Makefile κτλ που κάνατε κάποια αλλαγή, ή δημιουργήσατε εσείς στις ασκήσεις 3 και 4.
Μην παραδώσετε αρχεία που δεν χρειάστηκε να τα τροποποιήσετε για την υλοποίησή σας.
- Τον source code από όλα τα test προγράμματα που γράψατε και τρέξατε μέσα στο guest Linux OS για να δοκιμάσετε
τις αλλαγές που υλοποιήσατε στον scheduler για την άσκηση 4 χρησιμοποιώντας τα system calls της άσκησης 3.
Και επίσης ότι header files χρησιμοποιήσατε.
Δηλαδή τα αρχεία .c, .h και Makefile και ότι άλλο αρχείο δημιουργήσατε στο
guest OS για να δοκιμάσετε τις αλλαγές σας (εκτός από τα executables).
- Ένα README file στο οποίο να περιγράφετε συνοπτικά (αλλά περιεκτικά και ξεκάθαρα) όλα τα βήματα που ακολουθήσατε
για την υλοποίηση της άσκησης.
Επίσης πρέπει να σχολιάσετε τι παρατηρήσατε από τα test προγράμματα που τρέξατε.
Αν έχετε κάνει κάτι διαφορετικό ή παραπάνω από όσα αναφέρουμε στην εκφώνηση της άσκησης
μπορείτε επίσης να το αναφέρετε στο README. Καλό θα ήταν το README να είναι από 20 μέχρι 30 γραμμές.
Προσοχή: δεν χρειάζεται να παραδώσετε το disk image (hy345-linux.img)
ακόμα και αν αυτό έχει τροποποιηθεί (όντως, το disk image μπορεί να αλλάξει
όσο χρησιμοποιείτε το guest OS, σαν ένας κανονικός δίσκος, αλλά δεν χρειάζεται να το παραδώσετε).
Δεν χρειάζεται επίσης να παραδώσετε κάποιο αρχείο με ολόκληρο τον source code του Linux kernel,
πρέπει να σημειώσετε και να παραδώσετε μόνο τα αρχεία που τροποποιήσατε ή δημιουργήσατε.
To kernel image (bzImage), τα header files, και τα test προγράμματα που θα παραδώσετε θα πρέπει
να είναι αρκετά ώστε η άσκησή σας να μπορεί να τρέξει με το αρχικό disk image και το QEMU,
έτσι ώστε να φαίνετε η σωστή υλοποίηση του ζητούμενου system call.
Μπορείτε να φτιάξετε έναν κατάλογο με τα τροποποιημένα
source code αρχεία του kernel
(αν θέλετε θα είναι καλό να κρατήσετε την δομή καταλόγων που υπάρχει στον linux kernel),
έναν κατάλογο με τα test προγράμματα και header files από το guest OS,
και τέλος να τους μεταφέρετε σε ένα κατάλογο μαζί το bzImage και το README file
για να παραδώσετε την άσκηση με τον γνωστό τρόπο.
Παρατηρήσεις
- Η άσκηση είναι ατομική. Τυχόν αντιγραφές μπορούν να ανιχνευθούν εύκολα
από κατάλληλο πρόγραμμα και θα μηδενιστούν. Συμπεριλάβετε το όνομα σας και
το λογαριασμό σας (account) σε όλα τα αρχεία.
- Τοποθετήστε σε ένα κατάλογο όλα τα
αρχεία προς παράδοση για την άσκηση 4. Παραδώστε τα
παραπάνω αρχεία χρησιμοποιώντας το πρόγραμμα submit (πληκτρολογήστε
submit assignment_4@hy345 directory_name από τον κατάλογο
που περιέχει τον κατάλογο directory_name με τα αρχέια της άσκησης).