Λειτουργικά Συστήματα (ΗΥ-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(). Αυτήν την συνατηση θα πρέπει να τροποποιήσετε ώς εξής:
  1. Για κάθε μια διεργασία που είναι έτοιμη για εκτέλεση και υποψήφια για να γίνει η επόμενη διεργασία που θα εκτελεστεί (next), θα πρέπει αρχικά να ελέγχετε αν έχει οριστεί process family για αυτήν την διεργασία (root_pid!=-1) και αν υπάρχει όριο για τον χρόνο εκτέλεσης αυτού του process family (time_limit!=-1).
  2. Αν κάτι από τα δυο δεν ισχύει, η διεργασία αυτή παραμένει υποψήφια για να επιλεγεί για εκτέλεση.
  3. Αλλιώς:
    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 αντίστοιχα) θα μηδενίζεται.
    2. Αν ο χρόνος εκτέλεσης του process family για το current interval δεν ξεπερνάει το όριο time_limit του συγκεκριμένου process family, τότε η διεργασία αυτή παραμένει υποψήφια για να επιλεγεί για εκτέλεση.
    3. Αλλιώς, αν ο χρόνος εκτέλεσης του 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 προγράμματα, μπορείτε να φτιάξετε όσα περισσότερα θέλετε ώστε να βεβαιωθείτε ότι oι αλλαγές στον Linux scheduler δουλεύουν σωστά.


Τι πρέπει να παραδώσετε

θα πρέπει να παραδώσετε τα παρακάτω αρχεία:
  1. Το καινούργιο kernel image, δηλαδή το αρχείο linux-2.6.38.1/arch/x86/boot/bzImage.
  2. Όλα τα αρχεία που τροποποιήσατε ή δημιουργήσατε στον source code του Linux kernel 2.6.38.1 για να υλοποιήσετε τα system calls στην 3η άσκηση και τις αλλαγές στον scheduler στην 4η άσκηση. Δηλαδή όλα τα αρχεία .c, .h, Makefile κτλ που κάνατε κάποια αλλαγή, ή δημιουργήσατε εσείς στις ασκήσεις 3 και 4. Μην παραδώσετε αρχεία που δεν χρειάστηκε να τα τροποποιήσετε για την υλοποίησή σας.
  3. Τον source code από όλα τα test προγράμματα που γράψατε και τρέξατε μέσα στο guest Linux OS για να δοκιμάσετε τις αλλαγές που υλοποιήσατε στον scheduler για την άσκηση 4 χρησιμοποιώντας τα system calls της άσκησης 3. Και επίσης ότι header files χρησιμοποιήσατε. Δηλαδή τα αρχεία .c, .h και Makefile και ότι άλλο αρχείο δημιουργήσατε στο guest OS για να δοκιμάσετε τις αλλαγές σας (εκτός από τα executables).
  4. Ένα 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 για να παραδώσετε την άσκηση με τον γνωστό τρόπο.


Παρατηρήσεις

  1. Η άσκηση είναι ατομική. Τυχόν αντιγραφές μπορούν να ανιχνευθούν εύκολα από κατάλληλο πρόγραμμα και θα μηδενιστούν. Συμπεριλάβετε το όνομα σας και το λογαριασμό σας (account) σε όλα τα αρχεία.
  2. Τοποθετήστε σε ένα κατάλογο όλα τα αρχεία προς παράδοση για την άσκηση 4. Παραδώστε τα παραπάνω αρχεία χρησιμοποιώντας το πρόγραμμα submit (πληκτρολογήστε submit assignment_4@hy345 directory_name από τον κατάλογο που περιέχει τον κατάλογο directory_name με τα αρχέια της άσκησης).