Προγραμματισμός Συστήματος - Εργασία 1
Κορακίτης Άγγελος - 1115201900087
Η παρακάτω εργασία υλοποιεί πλήρως όλα τα ζητούμενα της 1ης εργασίας και περνάει όλα τα test cases που μας δώθηκαν.
-Μεταγλώττιση προγράμματος:
Στον κατάλογο src/ εκτελούμε την εντολή:
make
-Εκτέλεση προγράμματος:
./mysh
-Κατάλογος αρχείων εργασίας:
.
├── README.md
├── include
│ ├── builtins.hpp
│ ├── parser.hpp
│ └── utils.hpp
└── src
├── Makefile
├── builtins.cpp
├── main.cpp
├── parser.cpp
└── utils.cpp
-Επεξήγηση αρχείων-συναρτήσεων:
- builtins.cpp/hpp
Περιέχονται οι υλοποιήσεις μερικών builtin συναρτήσεων που χρειάστηκαν να γραφτούν από εμάς τους ίδιους. Τέτοιες είναι οι: εντολή cd - cd_builtin Η συμπεριφορά είναι ακριβώς όπως την γνωρίζουμε από την κανονική cd εντολή createalias/ destroyalias - ένα από τα ζητούμενα της εργασίας. Χρησιμοποιούμε μια δομή map της cpp προκειμένου να υλοποιήσουμε αποδοτικά την συγκεκριμένη εντολή. Υποστηρίζει μόνο μια εντολή ανά alias χωρίς να περιέχονται χαρακτήρες όπως ';', '|'. εντολή echo - echo_builtin: έχουν υλοποιηθεί τόσο τα environment variables όσο και το $? για να ξέρουμε αν τερμάτισε επιτυχώς η τελευταία εντολή. Δεν υποστηρίζει ειδικούς χαρακτήρες όπως ';'. εντολή history_builtin - επίσης, ζητούμενο της εργασίας, καλείται ως history αν θέλουμε να δούμε μέχρι τις τελευταίες 20 εντολές αλλίως history καλείται συγκεκριμένη εντολή από το ιστορικό.
- parser.cpp/hpp
Μερικές δομές που χρησιμοποιούνται:
typedef std::string String;
typedef std::vector<String> StringVector;
typedef std::vector<Token> TokenVector;
struct Token{
TokenType type;
String value;
};
Περιέχονται οι υλοποιήσεις του lexer() και του parser(). Πιο συγκεκριμένα:
-
void lexer(String input, TokenVector &tokens); Ουσιαστικά κάνει ένα αρχικό πέρασμα στην συμβολοσειρά και την χωρίζει σε tokens
-
void parser(TokenVector &tokens, std::vector &commands, std::vector<std::vector > &command_groups, std::vector &is_background, int &fdin, int &fdout); Ουσιαστικά η συγκεκριμένη εντολή παίρνει τα tokens και γκρουπάρει τις εντολές ανάλογα με το αν χωρίζονται με ;, ή αν έχουν | (pipes) που πρέπει να εκτελεστούν. Επίσης,καθορίζονται τα fdin και fdout σε περίπτωση που περιέχονται ανακατευθύνσεις στην εντολή. πχ ls | sort; ls -l Τότε έχουμε δύο command_groups ls | sort και ls -l.
-
utils.cpp/hpp
-
bool wildcard_expansion(std::vector<char*> c_args,std::vector<char*> &glob_args); Η συνάρτηση που κάνει expand τα wildcards. Χρησιμοποιεί την βιβλιοθήκη glob.h και καλεί την glob συνάρτηση σε κάθε σύμβολο η οποία κάνει expand τους χαρακτήρες ? και *.
-
void execute_command(String input_str,TokenVector &tokens); Η βασική συνάρτηση που υλοποιεί την εκτέλεση των εντολών. Μέσα σε εκείνη γίνεται η ενημέρωση των alias_map και του history_vector που διατηρούν τα aliases και το history αντίστοιχα. Αφού καλέσουμε τον parser(), για κάθε vector από commands στα command_groups εκτελούμε τις εντολές μια μια. Αν αυτές περιέχουν pipes τότε γίνονται οι ανάλογες ενέργειες με την κλήση της pipe(). Επίσης στην συνάρτηση γίνεται και ο κατάλληλος έλεγχος για την κλήση της kill για παιδιά το οποία τρέχουν στο background και δεν έχουν τερματίσει.
-Γενική περιγραφή κώδικα:
Αρχικά διαβάσουμε μια γραμμή-συμβολοσειρά που μας δίνει ο χρήστης. Στην συνέχεια καλούμε την execute_command() στην οποία η συμβολοσειρά γίνεται parsing από τον lexer και parser όπου τελικά την χωρίζουμε σύμφωνα με τις διαφορετικές εντολές που υπάρχουν μέσα σε εκείνη, δηλαδή στα ';' καθώς και σε όταν βρίσκουν '|' σωληνώσεις. Στην συνέχεια με τους κατάλληλους ελέγχους ότι δεν υπάρχει κάποιο alias στην εντολή ή οτι δεν είναι κάποια builtin συνάρτηση την οποία έχουμε υλοποιήσει, αφού ενημερώσουμε το ιστορικό, κάνουμε fork() και καλούμε την execvp() για εκτέλεση της εντολής που έχουμε δώσει. Τέλος, μεριμνούμε για την σωστή διαχείριση συναρτήσεων που εκτελούνται στο background και μπορεί να μην έχουν ολοκληρωθεί σκοτώνοντας τα συγκεκριμένα processes μέσω της cleanup_processes().
Περαιτέρω εξηγήσεις περιέχονται και στον κώδικα με μορφή σχολίων.