Λειτουργικά Συστήματα (ΗΥ-345)
Χειμερινό Εξάμηνο 2013-2014
Άσκηση 1



Φροντιστήριο: 22/10/2013
Παράδοση: 11/11/2013

Υλοποίηση του δικού σας shell "mysh" με ενσωματωμένη εντολή "mytime"


Γράψτε ένα πρόγραμμα σε γλώσσα C που θα υλοποιεί ένα απλό shell (command interpreter) στο λειτουργικό σύστημα Linux. Μέσω του shell θα πρέπει να μπορείτε κατεθύνετε την είσοδο των εντολών από αρχεία και την έξοδο των εντολών σε αρχεία, και να κατευθείνετε την έξοδο μιας εντολής στην είσοδο μιας άλλης εντολής χρησιμοποιώντας pipes. Τέλος, θα υλοποιήσετε μέσα στο shell μια ενσωματωμένη εντολή που θα μετράει τον χρόνο εκτέλεσης (πραγματικό χρόνο, χρόνο εκτέλεσης στην CPU σε επίπεδο χρήστη, και χρόνο συστήματος) κάθε εντολής που εκτελείται. Σε αυτήν την άσκηση θα εξοικιωθείτε με τη δημιουργία, συγχρονισμό, και εκτέλεση διεργασιών και με τις κλήσεις συστήματος (system calls) fork(), wait() και exec(), με την επικοινωνία διεργασιών μέσω pipes, με την μέτρηση χρόνου εκτέλεσης διεργασιών, και με τον χειρισμό σημάτων (signals).

Το απλό αυτό shell θα λέγεται "mysh". Το prompt του θα είναι:
user@mysh:/dir/#
όπου αντί για user θα τυπώνει το όνομα του χρήστη που είναι logged-in στο σύστημα (δείτε την συνάρτηση βιβλιοθήκης getlogin()) και αντί για dir θα τυπώνει το τρέχον directory (δείτε την συνάρτηση βιβλιοθήκης getcwd()). Το mysh θα έχει τις παρακάτω λειτουργίες:

  1. Το shell θα διαβάζει εντολές από τον χρήστη και θα τις εκτελεί.

    Αφού διαβάσει μια γραμμή εντολών, το mysh θα χρησιμοποιεί το system call fork(), για να δημιουργήσει μια καινούρια διεργασία-παιδί η οποία θα εκτελεί την εντολή του χρήστη. Η εκτέλεση της εντολής από τη διεργασία-παιδί θα γίνεται με τη χρήση ενός από τα system calls της οικογένειας exec() (δείτε το manpage της exec()).

    Η πατρική διεργασία θα περιμένει τη διεργασία-παιδί να τελειώσει την εκτέλεσή της (χρησιμοποιώντας το system call wait()), οπότε και θα επιστρέφει στο prompt περιμένοντας τη νέα εντολή του χρήστη, εκτός αν ο χρήστης έχει πληκτρολογήσει το & στο τέλος της εντολής, οπότε η πατρική διεργασία δεν θα περιμένει αλλά θα επιστρέφει αμέσως στο prompt για νέα εντολή. Σε αυτήν την περίπτωση πριν επιστρέψει στο prompt για να περιμένει νέα εντολή θα πρέπει πρώτα να τυπώνει το όνομα και το pid της διεργασίας που τρέχει στο παρασκήνιο.

  2. Ο χρήστης του mysh θα μπορεί να κατευθύνει την έξοδο μιας εντολής σε ένα αρχείο χρησιμοποιώντας το σύμβολο > ως εξής:
    user@mysh:/dir/# command > file
    όπου command η εντολή που εκτελείται και file ένα αρχείο, π.χ., το
    user@mysh:/dir/# ls -l my_files/ > output
    θα πρέπει να γράφει τα περιεχόμενα του καταλόγου my_files στο αρχείο output. Για αυτό θα σας φανεί χρήσιμο το system call dup2().
  3. Ο χρήστης του mysh θα μπορεί να κατευθύνει ένα αρχείο στην είσοδο του προγράμματος που τρέχει το mysh χρησιμοποιοώντας το σύμβολο ως εξής:
    user@mysh:/dir/# command < file
    όπου command η εντολή που εκτελείται και file ένα αρχείο, π.χ., το
    user@mysh:/dir/# cat < output
    θα πρέπει να δίνει στο πρόγραμμα cat τα περιεχόμενα του αρχείου output για να τα εμφανίζει στην οθόνη. Για αυτό θα σας φανεί χρήσιμο το system call dup2().
  4. Επίσης, η έξοδος μιας εντολής θα μπορεί να δίνετε σαν είσοδος σε μια άλλη εντολή που υπάρχει στην ίδια γραμμή εντολών και διαχωρίζονται με το σύμβολο | μεταξύ τους. Παραδείγματα:
    user@mysh:/dir/# ps axl | grep zombie
    που βρίσκει όλες τις zombie διεργασίες.
    user@mysh:/dir/# ls -al | sort -r -k 5 | head -10
    που τυπώνει τα 10 μεγαλύτερα αρχεία στον τρέχοντα κατάλογο ταξινομημένα κατά μέγεθος. Για την υλοποίηση αυτής της λειτουργίας θα σας φανεί χρήσιμο το system call pipe().
  5. Εκτός από τις εντολές του χρήστη, το mysh θα υποστηρίζει και τις παρακάτω δικές του εντολές :

    1. cd : Η εντολή αυτή θα δέχεται ένα όρισμα που δηλώνει τον κατάλογο (directory) ο οποίος θα πρέπει να γίνει το νέο τρέχoν directory (δείτε την κλήση συστήματος chdir()).
    2. mytime : Η εντολή mytime θα δέχεται σαν όρισμα ένα πρόγραμμα (μαζί με ορίσμα που πιθανώς θα δέχεται το πρόγραμμα) το οποίο θα τρέχει κανονικά στο shell, αλλά στο τέλος της εκτέλεσής του θα εκτυπώνει επίσης στην κονσόλα (και όχι σε κάποιο αρχείο που ίσως έχει δωθεί για την έξοδο του προγράμματος) τους παρακάτω χρόνους εκτέλεσης του προγράμματος: (α) τον πραγματικό χρόνο που πέρασε (real time), (β) τον χρόνο που το πρόγραμμα έτρεξε στην CPU στο επίπεδο χρήστη (user time), και (γ) τον χρόνο που έτρεξε στην CPU το σύστημα για να εξυπηρετήσει το συγκεκριμενένο πρόγραμμα, π.χ., εκτελώντας system calls (system time). Για την μέτρηση των παραπάνω χρόνων θα σας φανούν χρήσιμα το system calls gettimeofday() και times().
    3. history : Η εντολή history θα εκτυπώνει μια λίστα με τις εντολές που έχει εκτελέσει μέχρι εκείνη την ώρα ο χρήστης με την εξής μορφή:
      (σειρά που δώθηκε η εντολή) (εντολή)

      Ένα πιθανό παράδειγμα εξόδου είναι το εξής:
      1. cd tmp/
      2. ls -l
      3. cat mysh.c | grep fork
      4. cp test1.c test2.c
      5. cat test2.c
      6. rm test2.c
      7. mkdir new
      8. cd new
      9. ls
      10. cd ..

    4. exit : Με την εντολή exit θα τερματίζεται η λειτουργία του mysh.
  6. Τέλος, τo mysh θα πρέπει να χειρίζεται κατάλληλα τα signals SIGINT και SIGTERM έτσι ώστε οταν, π.χ., ο χρήστης πατάει CTRL-C ή όταν στέλνει κάποιο από αυτά τα signals στο πρόγραμμα που τρέχει στο shell, τότε το πρόγραμμα αυτό θα πρέπει να τερματίζει και το mysh θα πρέπει να τυπώνει ότι δέχτηκε signal για διακοπή (και το είδος του signal), και να τυπώνει επίσης το όνομα και το pid της διεργασίας που τερματίστηκε με αυτό το signal. Άν ο χρήστης χρησιμοποιούσε την εντολή mytime, το mysh θα πρέπει να τυπώνει τα στατιστικά για τον χρόνο εκτέλεσης του προγράμματος που τερματίστηκε. To mysh δεν θα πρέπει να τερματίζει όταν δέχεται CTRL-C (ή το αντίστοιχο signal), π.χ., όταν δεν τρέχει κάποια εντολή. Θα πρέπει απλά να τυπώνει ότι δέχτηκε το συγκεκριμένο signal. To mysh θα τερματίζει μόνο με την εντολή exit.
Reference - man pages

Ένα man page περιγράφει τον τρόπο λειτουργίας ενός προγράμματος, ενός system call ή μιας library function. Η εμφάνιση ενός man page γίνεται με τη χρήση της εντολής:

man(1)

Για να δείτε το man page (σε Linux) που αναφέρεται στη foo(N), κάνετε:

% man -S N foo

Ο συμβολισμός foo(N) αναφέρεται στο man page που περιγράφει τη foo στη κατηγορία (section) 'Ν'.

Λίστα με χρήσιμα man pages για την Aσκηση 1

Σας παραθέτουμε man pages με system calls που μπορεί να χρειαστείτε για την υλοποίηση της άσκησης. Η παρακάτω λίστα δεν είναι δεσμευτική. Μπορείτε να χρησιμοποιήσετε και εναλλακτικούς τρόπους.

fork(2)
exec(3)
execv(3)
wait(2)
waitpid(2)
pipe(2)
dup2(2)
times(2)
time(2)
time(1)
sh(1)
bash(1)
gettimeofday(2)
signal(2)
chdir(2)
getcwd(2)
getlogin(2)
 

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

  1. Η άσκηση είναι ατομική. Τυχόν αντιγραφές μπορούν να ανιχνευθούν εύκολα από κατάλληλο πρόγραμμα και θα μηδενιστούν. Συμπεριλάβετε το όνομα σας και το λογαριασμό σας (account) σε όλα τα αρχεία.
  2. Κατασκευάστε ένα αρχείο Makefile, έτσι ώστε πληκτρολογώντας make all να γίνεται η μεταγλώττιση (compilation) του προγράμματος και να παράγεται το εκτελέσιμο αρχείο. Επίσης πληκτρολογώντας make clean να καθαρίζονται όλα τα περιττά αρχεία, και να μένουν μόνο τα αρχεία που χρειάζονται για τη μεταγλώττιση.
  3. Επιπλέον, γράψτε και ένα αρχείο readme.txt το πολύ 30 γραμμών που να περιέχει επεξηγήσεις για τον τρόπο υλοποίησης και παραδείγματα για τη χρήση του mysh που υλοποιήσατε.
  4. Τοποθετήστε σε ένα κατάλογο όλα τα αρχεία που χρειάζονται για την άσκηση 1. Παραδώστε τα παραπάνω αρχεία χρησιμοποιώντας το πρόγραμμα submit (πληκτρολογήστε submit assignment_1@hy345 directory_name από τον κατάλογο που περιέχει τον κατάλογο directory_name με τα αρχέια της άσκησης).
  5. Σε πολλές περιπτώσεις τα ονόματα των συναρτήσεων βιβλιοθήκης είναι ενδεικτικά. Μπορείτε να χρησιμοποιήσετε όποια σας βολεύουν.