Λειτουργικά Συστήματα

Σχετικά έγγραφα
Πανεπιστήμιο Θεσσαλίας Τμήμα Πληροφορικής

ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ II. Υφαντόπουλος Νικόλαος Υποψήφιος Διδάκτορας Contact:

Εργαστήριο 5 fork(), exec(), signals

Λύβας Χρήστος Αρχική επιµέλεια Πιτροπάκης Νικόλαος και Υφαντόπουλος Νικόλαος

Εργαστήριο 7 fork(), exec(), signals

Δημιουργία & Τερματισμός Διεργασιών. Προγραμματισμός II 1

Δημιουργία & Τερματισμός Διεργασιών. Προγραμματισμός II 1

Εργαστήριο ΔΙΕΡΓΑΣΙΕΣ - ΔΙΑΧΕΙΡΙΣΗ

Προγραμματισμός συστημάτων UNIX/POSIX. Διεργασίες (processes)

Λειτουργικά Συστήματα

ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ. Διεργασίες και Νήματα Εργαστηριακές Ασκήσεις

Θέτοντας και επιστρέφοντας την τιµή της προτεραιότητας διεργασίας

ΙΩΑΝΝΗΣ ΚΩΝΣΤΑΝΤΙΝΟΥ 2ο ΦΡΟΝΤΙΣΤΗΡΙΟ ΠΑΡΑΣΚΕΥΗ 26 ΟΚΤΩΒΡΙΟΥ 2012 ΑΙΘΟΥΣΑ Β4

Εικονική Μνήμη (Virtual Memory) Προγραμματισμός II 1

Προγραμματισμός συστημάτων UNIX/POSIX

Εικονική Μνήμη (Virtual Memory) Προγραμματισμός II 1

Προγραμματισμός Ι. Δυναμική Διαχείριση Μνήμης. Δημήτρης Μιχαήλ. Ακ. Έτος Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Λειτουργικά Συστήματα 7ο εξάμηνο, Ακαδημαϊκή περίοδος

Λειτουργικά Συστήματα (Λ/Σ)

Διάλεξη 14: Διεργασίες: Έλεγχος & Σήματα (Processes: Control & Signals)

Εισαγωγή εκτελέσιμου κώδικα σε διεργασίες

Δίκτυα Επικοινωνιών ΙΙ: Network Programming UDP Sockets, Signals

Παράλληλη Επεξεργασία

Προγραμματισμός συστημάτων UNIX/POSIX

Εκφωνήσεις ασκήσεων εργαστηρίου 1

ιεργασίες και νήµατα Προγραµµατισµός ΙΙΙ 1 lalis@inf.uth.gr

Προγραμματισμός Ι. Προχωρημένα Θέματα. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Προγραμματισμός συστημάτων UNIX/POSIX. Διαδιεργασιακή επικοινωνία: αγωγοί (IPC inter-process communication: pipes)

UNIX System Programming

ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ Η/Υ Ακαδημαϊκό έτος ΤΕΤΡΑΔΙΟ ΕΡΓΑΣΤΗΡΙΟΥ #4

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

Εισαγωγή στον Προγραμματισμό

Προγραμματισμός Ι. Είσοδος/Έξοδος. Δημήτρης Μιχαήλ. Ακ. Έτος Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Δομημένος Προγραμματισμός

ΠΑΝΕΠΙΣΤΗΜΙΟ ΘΕΣΣΑΛΙΑΣ ΣΧΟΛΗ ΘΕΤΙΚΩΝ ΕΠΙΣΤΗΜΩΝ ΤΜΗΜΑ ΠΛΗΡΟΦΟΡΙΚΗΣ

Διαχείριση Διεργασιών και Διαδιεργασιακή Επικοινωνία

Εισαγωγή στην Πληροφορική

Εισαγωγή στον Προγραμματισμό

Ντίρλης Νικόλαος- ΕΤΥ 2 ο Φροντιστήριο Παρασκευή, 18/10/2013 Β4. Λειτουργικά Συστήματα- Φροντιστήριο 2

Τµήµα Ηλεκτρολόγων Μηχανικών & Μηχανικών Υπολογιστών Σεπτέµβριος 2013

lab13grades Άσκηση 2 -Σωστά απελευθερώνετε ολόκληρη τη λίστα και την κεφαλή

Λειτουργικά Συστήματα

ΕΡΓΑΣΤΗΡΙΟ 9: Συμβολοσειρές και Ορίσματα Γραμμής Εντολής

Κεφάλαιο , 3.2: Συναρτήσεις II. (Διάλεξη 12)

Λειτουργικά Συστήματα 7ο εξάμηνο, Ακαδημαϊκή περίοδος

ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ II. Υφαντόπουλος Νικόλαος Υποψήφιος Διδάκτορας Contact:

ΑΣΚΗΣΗ 1: TO ΠΕΡΙΒΑΛΛΟΝ ΕΡΓΑΣΙΑΣ DEV-C++

ΕΡΓΑΣΤΗΡΙΟ 9: Συμβολοσειρές και Ορίσματα Γραμμής Εντολής

Ανάπτυξη και Σχεδίαση Λογισμικού

Μεθόδων Επίλυσης Προβλημάτων

Διαδικασιακός Προγραμματισμός

Πληροφορική & Τηλεπικοινωνίες Υλοποίηση Συστημάτων Βάσεων Δεδομένων - Χειμερινό Εξάμηνο Καθηγητής Δ. Γουνόπουλος

Πληροφορική & Τηλεπικοινωνίες K18 - Υλοποίηση Συστηµάτων Βάσεων εδοµένων Εαρινό Εξάµηνο

#include <stdlib.h> Α. [-128,127] Β. [-127,128] Γ. [-128,128]

Η γλώσσα προγραμματισμού C

Διάλεξη 18η: Διαχείρηση Αρχείων

Κεφάλαιο 8.7. Πολυδιάστατοι Πίνακες (Διάλεξη 19)

Προγραμματισμός Ι. Προεπεξεργαστής. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Προγραμματισμός Ι (ΗΥ120)

$./jms console -w <jms in> -r <jms out> -o <operations file> namedpipe. (standard input).

Κεφάλαιο , 3.2: Συναρτήσεις II. ( ιάλεξη 12) ιδάσκων: ηµήτρης Ζεϊναλιπούρ

Προγραμματισμός Ι. Εγγραφές. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

C: Από τη Θεωρία στην Εφαρμογή

Κεφάλαιο Αλφαριθμητικές Σειρές Χαρακτήρων (Strings) (Διάλεξη 20) 1) Strings στη C

Λειτουργικά Συστήματα. Τ.Ε.Ι. Ιονίων Νήσων Σχολή Διοίκησης και Οικονομίας - Λευκάδα

Εργαστήριο Λειτουργικών Συστημάτων 8o εξάμηνο, Ροή Υ, ΗΜΜΥ

1. Εισαγωγή. Λειτουργικά Συστήματα Η/Υ. Διεργασίες. Ορισμός ΚΕΦΑΛΑΙΟ 3 - ΔΙΕΡΓΑΣΙΕΣ. Κεφάλαιο 3 «Διεργασίες»

Διαχείριση Διεργασιών και Διαδιεργασιακή Επικοινωνία

ΑΣΚΗΣΗ 6: ΔΕΙΚΤΕΣ. Σκοπός της Άσκησης. 1. Εισαγωγικά στοιχεία για τους Δείκτες

Προγραμματισμός Η/Υ 1 (Εργαστήριο)

Η βασική συνάρτηση προγράμματος main()

Διάλεξη 13η: Δυναμική Διαχείρηση Μνήμης, μέρος 1

Linux με τη χρήση κονσόλας

Κλήση Συναρτήσεων ΚΛΗΣΗ ΣΥΝΑΡΤΗΣΕΩΝ. Γεώργιος Παπαϊωάννου ( )

Προγραμματισμός Ι. Δείκτες. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Πληροφορική & Τηλεπικοινωνίες. K18 - Υλοποίηση Συστημάτων Βάσεων Δεδομένων Χειμερινό Εξάμηνο

Μετατροπή χαρακτήρων ASCII σε αριθμό (atoi) & διάβασμα, και αποθήκευση του περιεχομένου του στη μνήμη. (Διάλεξη. Πανεπιστήμιο Κύπρου

MIPS Interactive Learning Environment. MILE Simulator. Version 1.0. User's Manual

Προγραμματισμός Η/Υ (ΤΛ2007 )

Λύβας Χρήστος Αρχική επιµέλεια Πιτροπάκης Νικόλαος και Υφαντόπουλος Νικόλαος

ΠΑΝΕΠΙΣΤΗΜΙΟ ΠΑΤΡΩΝ ΠΟΛΥΤΕΧΝΙΚΗ ΣΧΟΛΗ ΤΜΗΜΑ ΜΗΧΑΝΙΚΩΝ ΗΛΕΚΤΡΟΝΙΚΩΝ ΥΠΟΛΟΓΙΣΤΩΝ ΚΑΙ ΠΛΗΡΟΦΟΡΙΚΗΣ

Λειτουργικά Συστήματα

Α. unsigned int Β. double. Γ. int. unsigned char x = 1; x = x + x ; x = x * x ; x = x ^ x ; printf("%u\n", x); Β. unsigned char

ΑΡ Χ Ε Ι Α Κ Ε Ι Μ Ε Ν Ο Υ (text files)

Πληροφορική & Τηλεπικοινωνίες. K18 - Υλοποίηση Συστημάτων Βάσεων Δεδομένων Εαρινό Εξάμηνο

Α Β Γ static; printf("%c\n", putchar( A +1)+2); B DB BD. int i = 0; while (++i); printf("*");

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Κλάσεις και Αντικείμενα Αναφορές

ΑΣΚΗΣΗ 2: ΧΕΙΡΙΣΜΟΣ ΜΕΤΑΒΛΗΤΩΝ ΣΤΗ C

Χρονοδρομολογητής Κυκλικής Επαναφοράς

Εισαγωγή στον Προγραμματισμό

Εργαστήριο 5. Εαρινό Εξάμηνο

Λύβας Χρήστος Αρχική επιµέλεια Πιτροπάκης Νικόλαος και Υφαντόπουλος Νικόλαος

IPC System V. Προγραμματισμός II 1

Διδάσκων: Κωνσταντίνος Κώστα Διαφάνειες: Δημήτρης Ζεϊναλιπούρ

Πίνακες: μια σύντομη εισαγωγή. Πίνακες χαρακτήρων: τα "Αλφαριθμητικά"

Λειτουργικά Συστήματα (ΗΥ321)

Εργαστήριο 9: Αρχεία

Δείκτες (Pointers) Ένας δείκτης είναι μια μεταβλητή με τιμή μια διεύθυνση μνήμης. 9.8

Πληροφορική & Τηλεπικοινωνίες. K18 - Υλοποίηση Συστημάτων Βάσεων Δεδομένων Εαρινό Εξάμηνο

HY-486 Αρχές Κατανεμημένου Υπολογισμού

Transcript:

Λειτουργικά Συστήματα Δρ. Βασίλης Ταμπακάς Δρ. Ιωάννης Ε. Λιβιέρης Τμήμα Μηχανικών Πληροφορικής Τ.Ε.

Περιεχόμενα Περιεχόμενα... 1 1. Εισαγωγή, Θεωρητική Υποδομή της Εργαστηριακής Άσκησης... 2 2. Εργαστηριακή υποδομή εργαστηριακής άσκησης... 2 2.1 Η εντολή system()... 2 2.2 Η εντολή syscall()... 3 2.3 Η εντολή fork()... 4 2.4 Η εντολή getpid()... 6 2.5 Η εντολή getppid()... 7 2.6 Η εντολή sleep()... 8 2.7 Η εντολή wait()... 9 2.8 Η εντολή exit()... 11 2.9 Η εντολή waitpid()... 12 2.10 Η εντολή exec()... 14 2.9.1 Δηλώσεις της οικογένειας κλήσεων συστήματος exec... 15 3. Επιπλέον Παραδείγματα... 18 4. Κοινή μνήμη... 21 4.1 Πρόσβαση ενός κοινού τμήματος μνήμης... 22 4.2 Έλεγχος ενός κοινού τμήματος μνήμης... 24 4.3 Σύνδεση και αποσύνδεση ενός κοινού τμήματος μνήμης... 24 5. Ασκήσεις... 27 Παράρτημα... 31 1

1. Εισαγωγή, Θεωρητική Υποδομή της Εργαστηριακής Άσκησης To αντικείμενο της εργαστηριακής άσκησης είναι η δημιουργία, η διαχείριση και η κατανόηση των συντρεχουσών (concurrent) διεργασιών στο λειτουργικό σύστημα LINUX. Επίσης, η έννοια της κρίσιμης περιοχής και η κατανόηση της ανάγκης για την υλοποίηση αμοιβαίου αποκλεισμού. Για την υλοποίηση των παραδειγμάτων και των ασκήσεων της εργαστηριακής άσκησης, οι φοιτητές θα πρέπει να εξοικειωθούν με έναν αριθμό εντολών συστήματος (κλήσεις συστήματος system calls) του λειτουργικού συστήματος όπως είναι οι fork() και exec() και με τη δημιουργία και διαχείριση διαμοιραζόμενων (shared) μεταβλητών στο Linux. Οι συναρτήσεις αυτές δίνονται και επεξηγούνται στην επόμενη ενότητα. Επίσης οι φοιτητές πριν πραγματοποιήσουν την εργαστηριακή άσκηση πρέπει να βεβαιωθούν ότι κατέχουν ικανοποιητικά τα αντίστοιχα κεφάλαια θεωρίας (Διεργασίες και Συγχρονισμός Διεργασιών). Προτείνεται να τα μελετήσουν από τα συγγράμματα που διατίθενται για το μάθημα και κυρίως από τις διαφάνειες διαλέξεων της θεωρίας (Κεφάλαιο - 2 Διεργασίες, Κεφάλαιο 4 Συγχρονισμός Διεργασιών σελ. 1-41). 2. Εργαστηριακή υποδομή εργαστηριακής άσκησης 2.1 Η εντολή system() Μπορούμε να εκτελέσουμε εντολές φλοιού (όπως και σενάρια ή συναρτήσεις φλοιού) UNIX/Linux μέσα από ένα πρόγραμμα C ακριβώς όπως και από τη γραμμή εντολών, με τη βοήθεια της συνάρτησης system(). Σημείωση: Aυτό μπορεί να μας γλιτώσει από πολύ κόπο, αφού μπορούμε πολύ εύκολα να ενσωματώσουμε ότι εντολή, σενάριο, υπηρεσία, εφαρμογή κλπ διατίθεται στη γραμμή εντολών του φλοιού UNIX/Linux. Σύνταξη: int system(char *string) όπου το string μπορεί αν ειναι το όνομα διαδρομής οποιουδήποτε εκτελέσιμου προγράμματος από τη γραμμή εντολών του φλοιού UNIX/Linux. η συνάρτηση ορίζεται στη βιβλιοθήκη stdlib.h Παράδειγμα 1: Κλήση της εντολής ls από ένα C πρόγραμμα : //system.c 2

#include <stdlib.h> printf("files in Directory are:\n"); system("ls -l"); Η συνάρτηση επιστρέφει τη κατάσταση εξόδου της εντολής που εκτελέστηκε. Σε περίπτωση επιτυχίας επιστρέφει 0, ενώ αντίθετα σε περίπτωση αποτυχίας επιστρέφει -1. 2.2 Η εντολή syscall() Η syscall() είναι μια εντολή, η οποία καλεί τη κλήση του συστήματος. Η χρήση της εντολής syscall() είναι χρήσιμη όταν για παράδειγμα, καλείτε μια κλήση του συστήματος, η οποία δεν μπορεί να υλοποιηθεί από κάποια εντολή της C. Η syscall() αποθηκεύει τους καταχωρητές της CPU πριν πραγματοποιήσει την κλήση του συστήματος, επαναφέρει τα μητρώα κατά την επιστροφή από την κλήση του συστήματος και αποθηκεύει οποιοδήποτε σφάλμα που επιστρέφεται από την κλήση του συστήματος. Αν η syscall() εκτελεστεί με επιτυχία τότε επιστρέφει μηδέν, διαφορετικά επιστρέφει -1. Σύνταξη: long syscall(long number,...); όπου το number είναι o κωδικός μίας εντολής UNIX/Linux. Η συνάρτηση ορίζεται στη βιβλιοθήκη sys/syscall.h Παράδειγμα 2 //syscall.c #include <unistd.h> #include <sys/syscall.h> #include <errno.h> int rc; rc = syscall(sys_chmod, "/etc/passwd", 0444); if (rc == -1) fprintf(stderr, "chmod failed, errno = %d\n", errno); 3

Σημείωση: Η κλήση της συνάρτησης system() περιλαμβάνει τη κλήση 3 άλλων συναρτήσεων: execl(), wait() και fork() (που ορίζονται στη βιβλιοθήκη unistd.h). 2.3 Η εντολή fork() Σε συστήματα Unix, υπάρχει ακριβώς ένας και μόνο τρόπος για να φτιάξεις μία νέα διεργασία και αυτός είναι με την εντολή fork(). Η κλήση fork()διασπά την τρέχουσα διεργασία σε δύο άλλες διεργασίες: το γονέα και το παιδί. Και οι δύο διεργασίες αποτελούνται από τον ίδιο κώδικα, τα ίδια δεδομένα και βρίσκονται στο ίδιο στάδιο της εκτέλεσής τους. Το παιδί κληρονομεί από το γονέα διάφορα στοιχεία όπως τους περιγραφείς των ανοικτών αρχείων, και το περιβάλλον εκτέλεσης. Σημείωση: Ο διαχωρισμός του γονέα από το παιδί γίνεται με βάση την τιμή που επιστρέφει η fork : Eπιστρέφει στο γονέα την ταυτότητα της διεργασίας του παιδιού. Eπιστρέφει στο παιδί την τιμή 0. Eπιστρέφει στο γονέα -1 σε περίπτωση λάθους. 4

Η γέννηση μιας διεργασίας στο UNIX είναι διαδικασία δυο βημάτων (εικόνα 1): 1. H διεργασία-πατέρας δημιουργεί μια νέα διεργασία-παιδί, πιστό αντίγραφο του εαυτού της (κλήση fork()). 2. H διεργασία-παιδί που προέκυψε αντικαθιστά το πρόγραμμα που εκτελεί (αρχικά ίδιο με του πατέρα) με το νέο πρόγραμμα. Η διαδικασία αυτή καθιστά ιδιαίτερα σαφή τη διάκριση ανάμεσα στις έννοιες πρόγραμμα και διεργασία. Προαιρετικά, ο γονέας μπορεί να περιμένει μέχρι τον τερματισμό κάποιας διεργασίας-παιδιού του (κλήση wait()). Ο οικειοθελής τερματισμός μιας διεργασίας μπορεί να γίνει με τη κλήση exit(). Εικόνα 1. Κύκλος ζωής μίας διεργασίας Τυπικά η κλήση της fork() γίνεται με βάση την παρακάτω τεχνική: //fork1.c #include <stdlib.h> int pid; pid = fork(); if (pid == 0) /* I am the child process */... 5

/* I am the parent process */... Σημείωση: Συνήθως οι διεργασίες του γονέα και του παιδιού εκτελούν διαφορετικούς ρόλους μετά τη κλήση της fork(), όπως παρουσιάστηκε στο παραπάνω κώδικα. Η χρήση της εντολής fork() πρέπει να γίνεται με ιδιαίτερη προσοχή γιατί είναι πολύ εύκολο να δημιουργηθεί χάος!!! Η δημιουργία ανεξέλεγκτα πολλών διεργασιών είναι ικανή να επιφέρει το φαινόμενο του λυγισμού (thrashing) που επεξηγείται στο κεφάλαιο 6 της θεωρίας. Στη συνέχεια δίνουμε ένα πρόγραμμα που δεν πρέπει ποτέ να εκτελέσετε. #include <stdlib.h> while (1) fork(); Θυμηθείτε πως ο αριθμός των ενεργών διεργασιών σε κάθε Λειτουργικό Σύστημα είναι πεπερασμένος και εξαρτάται από τις δυνατότητες και τη διαμόρφωση του συστήματος. Από την άλλη πλευρά, θυμηθείτε πως το PID (π.χ. του συστήματος LINUX) είναι ένας χωρίς πρόσημο ακέραιος (16-bit και επομένως μπορεί να αναπαραστήσει maximum 65536 διεργασίες). 2.4 Η εντολή getpid() Κάθε διεργασία έχει συσχετισμένο μαζί της έναν εγγυημένα μοναδικό αριθμό ταυτότητας διεργασίας (process-id, pid) που παρέχεται δυναμικά από το σύστημα. Ο αριθμός αυτός μπορεί να περαστεί σαν παράμετρος σε ορισμένες πρωτογενείς κλήσεις προκειμένου να αναφερθούμε σε κάποια διεργασία μέσα από κάποια άλλη. Μια διεργασία μπορεί να μάθει τον αριθμό ταυτότητάς της εκτελώντας την κλήση: 6

Σύνταξη: pid_t getpid(void); Παράδειγμα 3: Μία διεργασία ενημερώνεται για το id της και στη συνέχεια το εκτυπώνει. //getpid.c #include <stdlib.h> printf( My id: %i\n, getpid() ); 2.5 Η εντολή getppid() Κάθε διεργασία μπορεί να μάθει το αριθμό ταυτότητας (pid) της γονικής διεργασίας (δηλαδή της διεργασίας που τη δημιούργησε) χρησιμοποιώντας την εντολή getppid(). Σύνταξη: pid_t getppid(void); Παράδειγμα 4: Μία διεργασία ενημερώνεται για το id της και το id της γονικής διεργασίας και στη συνέχεια τα εκτυπώνει. //getppid.c #include <stdlib.h> int pid; pid = fork(); if (pid == 0) printf( My parent s id: %i\n, getppid() ); printf( I am the child (Id=%i)\n\n, getpid() ); sleep(1); 7

Υπενθύμιση: Οι διεργασίες στο UNIX είναι οργανωμένες σε ένα δένδρο διεργασιών. Όταν μια διεργασία δ1 δημιουργεί μια άλλη διεργασία δ2, η δ2 γίνεται διεργασία-παιδί της δ1 στο δένδρο, και η δ1 είναι η διεργασία-γονέας (parent process) της δ2. Στη ρίζα του δένδρου αυτού βρίσκεται η διεργασία με όνομα init, η οποία είναι ουσιαστικά η αρχική διεργασία του λειτουργικού συστήματος. Αυτό οδηγεί σε μια ιεραρχική οργάνωση του συνόλου των διεργασιών που εκτελούνται στο UNIX. Εικόνα 2. Ιεραρχική δομή διεργασιών Στην περίπτωση όπου η γονική διεργασία τερματίσει πριν ολοκληρωθεί η θυγατρική τότε τη θυγατρική διεργασία κληρονομεί η init. Γι αυτό και στο παραπάνω παράδειγμα η γονική διεργασία εκτελεί την εντολή sleep(1)(επεξηγείται στη συνέχεια) για να τερματίσει μετά τη θυγατρική. 2.6 Η εντολή sleep() H εντολή sleep εισάγει μια αναμονή στο σύστημα, για όσα δευτερόλεπτα της δώσουμε. Σύνταξη: void sleep(int); 8

Παράδειγμα 5: Ο παρακάτω κώδικας δέχεται ως όρισμα ένα θετικό αριθμό, το οποίο αντιστοιχεί στον αριθμό των δευτερολέπτων που θα περιμένει ο γονέας μέχρι να «σκοτώσει» τη θυγατρική εργασία. //sleep.c #include <stdlib.h> #define DEFAULT_TIME 0 int main (int argc, char **argv) int pid int seconds; if (argc == 1) seconds = DEFAULT_TIME; seconds = atoi (argv[1]); printf (" Here I am in the program!\n"); printf (" Seconds = %d\n", seconds); pid = fork(); if (pid > 0) printf (" I'm the parent.\n Bye now!\n\n"); sleep(seconds); printf (" I'm the child.\n Bye now!\n\n"); 2.7 Η εντολή wait() Η κλήση wait() αναστέλλει την εκτέλεση του καλούντος προγράμματος μέχρις ότου τερματισθεί (ομαλά ή ανώμαλα) η εκτέλεση κάποιας από τις διεργασίεςπαιδιά του. Η συνάρτηση wait() επιστρέφει το pid της θυγατρικής διεργασίας ή -1 για σφάλμα. Η κατάσταση εξόδου της θυγατρικής διεργασίας βρίσκεται στη μεταβλητή status. Επίσης, αν κάποια διεργασία-παιδί έχει ήδη τερματιστεί, τότε η κλήση επιστρέφει αμέσως -1. 9

Σύνταξη: int wait (int *status) Σε περίπτωση που η διεργασία-παιδί τερματίστηκε ομαλά με την κλήση exit(), τα 8 σημαντικότερα ψηφία του αριθμού αυτού παρέχουν την κατάσταση τερματισμού (exit status) της διεργασίας-παιδί: exit status = status >> 8; Σε περίπτωση πάλι που η διεργασία-παιδί τερματίστηκε ανώμαλα, τα 8 λιγότερο σημαντικά ψηφία του αριθμού δίνουν τον κωδικό του σήματος: signal_type = status & 0xFF; Κάθε διεργασία κατά τον τερματισμό της επιστρέφει έναν κωδικό εξόδου (exit code) ώστε να μπορεί να διαπιστωθεί ο λόγος και ο τρόπος τερματισμού της. Ο οικειοθελής τερματισμός μιας διεργασίας μπορεί να γίνει με την κλήση exit(), την οποία θα παρουσιάσουμε στη συνέχεια. Παράδειγμα 6: Στο παρακάτω κώδικα ελέγχουμε τις διεργασίες μέσω της τιμής της μεταβλητής pid. Λόγω της κλήσης της συνάρτησης wait() η γονική διεργασία θα εκτελείται πάντα μετά τη θυγατρική. Επίσης η σειρά εμφάνισης των αποτελεσμάτων είναι δεδομένη αφού θα εμφανιστούν πρώτα όλα τα αποτελέσματα της θυγατρικής διεργασίας. Επίσης το status λαμβάνει τιμή μέσω της wait() μετά τον τερματισμό της θυγατρικής διεργασίας. //wait.c #include <stdlib.h> #include <unistd.h> int pid, status; printf("forking process\n"); pid = fork(); wait(&status); printf("the process id is %d \n", getpid()); printf("value of pid is %d\n", pid); if (pid == 0) printf("i am the child, status is %d\n", status); 10

printf("i am the parent, status is %d\n", status); execl("/bin/ls","/bin/ls","-l", NULL); printf("this line is not printed\n"); 2.8 Η εντολή exit() H εντολή exit() τερματίζει τη διεργασία που καλεί αυτή τη συνάρτηση. Σύνταξη: void exit(int status) Όταν τερματιστεί η διεργασία επιστρέφει μία τιμή η οποία εκχωρείται στη μεταβλητή status, η οποία μπορεί να διαβαστεί τόσο από το UNIX/Linux όσο και από το πρόγραμμα, που έχει ξεκινήσει τη τερματισθείσα διεργασία με fork(). Όταν η τιμή της status είναι ίση με 0 αυτό σημαίνει κανονικό τερματισμό ενώ κάθε άλλη τιμή σημαίνει σφάλμα ή κάτι ασυνήθιστο (συνήθως στην περίπτωση αυτή η τιμή είναι -1). Πολλές συναρτήσεις της πρότυπης βιβλιοθήκης C έχουν κωδικούς σφάλματος που ορίζονται στο αρχείο κεφαλής sys/stat.h. Παράδειγμα 7: Ο παρακάτω κώδικας δίνει ένα πολύ καλό παράδειγμα της χρήσης της εντολής exit(). Μπορούμε να παρατηρήσουμε ότι το τελευταίο μήνυμα δεν τυπώνεται γιατί η διεργασία έχει τερματίσει νωρίτερα με την εκτέλεση της εντολής exit(0). //exit.c #include <stdlib.h> int main () printf("start of the program...\n"); printf("exiting the program...\n"); exit(0); printf("end of the program...\n"); 11

return(0); Σημείωση: Η κλήση exit() κλείνει αυτόματα όλα τα ανοικτά αρχεία της διεργασίας και απελευθερώνει όλα τα τμήματα μνήμης που είχε δεσμεύσει κατά τη διάρκεια εκτέλεσής της. Σημειώνουμε ωστόσο ότι όταν μια διεργασία τερματιστεί, δεν καταστρέφεται ολοκληρωτικά αλλά παραμένει στην τερματισμένη κατάσταση (zombie state). Όταν ο πατέρας εκτελέσει την κλήση wait() και συλλέξει τον κωδικό εξόδου, η διεργασία καταστρέφεται ολοκληρωτικά, ελευθερώνοντας και την εγγραφή του πίνακα διεργασιών που κατείχε. 2.9 Η εντολή waitpid() Προκαλεί την αναμονή μιας διεργασίας μέχρι το παιδί της που καθορίζεται από το pid να τερματίσει. Σύνταξη: int waitpid(pid_t pid, int *status, int options) Η τιμή pid=-1 (ή WAIT_ANY) σημαίνει ότι περιμένουμε πληροφορία για οποιοδήποτε παιδί. o Δίνεται η δυνατότητα καθορισμού επιπλέον επιλογών. Η πιο συνηθισμένη επιλογή είναι η WNOHANG, η οποία εμποδίζει τη waitpid να γίνει blocked αν δεν υπάρχουν τερματιζόμενα παιδιά. To status μπορεί να τύχει επεξεργασίας με τα ακόλουθα macros τα οποία βρίσκονται στη βιβλιοθήκη wait.h o WIFEXITED(status): Εάν δεν επιστρέψει 0 τότε το παιδί έχει τερματιστεί φυσιολογικά o WEXITSTATUS(status): Επιστρέφει το κωδικό εξόδου(exit status) της διαδικασίας παιδί. Αυτό μπορεί να υπολογιστεί μόνο στη περίπτωση που η WICFEXITED επιστρέψει μη-μηδέν o WIFSIGNALED(status): Επιστρέφει κατά πόσο η διαδικασία παιδί έχει τερματίσει μη φυσιολογικά (λόγω κάποιου σήματος) ή επιστρέφει 0. o WTERMSIG(status): Επιστρέφει το σήμα το οποίο ανάγκασε τη διαδικασία παιδί να τερματίσει. Αυτό μπορεί να υπολογιστεί μόνο στη περίπτωση που η WIFSIGNALED επιστρέψει μη-μηδέν. Παράδειγμα 8: Να δημιουργήσετε 10 διεργασίες κάθε μία από τις οποίες να περιμένει δευτερόλεπτα και να τερματίζει με exit status:, όπου, είναι ο αύξων αριθμός της διεργασίας. 12

//waitpid2.c #include <unistd.h> #include <sys/wait.h> #define N 10 pid_t pid[n]; int i; int child_status; for (i = 0; i < N; i++) pid[i] = fork(); if (pid[i] == 0) sleep(20-2*i); exit(100+i); for (i = 0; i < N; i++) pid_t wpid = waitpid(pid[i], &child_status, 0); if (WIFEXITED(child_status)) printf("child%d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); wpid); printf("child%d terminate abnormally\n", 13

2.10 Η εντολή exec() Όπως παρουσιάσαμε, με τη κλήση της fork()κλωνοποιούμε το process image (στοίβα, κώδικα, σωρό, κτλ) μιας διεργασίας. Η οικογένεια κλήσεων exec() αντικαθιστά τη διεργασία που εκτελείται με μια νέα διεργασία που βασίζεται στο πρόγραμμα που δίδεται σαν παράμετρος στην exec. Ουσιαστικά, η exec() επιτρέπει σε μία διεργασία να αλλάξει το πρόγραμμα που εκτελεί (αλλά παραμένοντας η ίδια διεργασία!) O κώδικας και τα δεδομένα της διεργασίας καταστρέφονται. Οι environment variables διατηρούνται. Οι file descriptors διατηρούνται. Η νέα διεργασία φορτώνεται και εκτελείται (από την αρχή). H εντολή execl αποτελεί σύντμηση των λέξεων execute(εκτέλεση) και leave(απομάκρυνση) που σημαίνει ότι η καλούμενη με την execl() διεργασία θα εκτελεστεί και θα τερματιστεί. Παράδειγμα 9: Να γραφεί ένα πρόγραμμα το οποίο να παρουσιάζει το περιεχόμενο του καταλόγου /home/cied, κάνοντας χρήση της execl //exec1.c #include <stdlib.h> printf("i am process %d\n", getpid()); printf("and I will execute an 'ls -l..'\n"); execl("/bin/ls", "ls", "-l", "/home/cied", NULL); 14

Ένα απλό μέλος της οικογένειας, η execlp, λαμβάνει ως παραμέτρους, το όνομα ή το μονοπάτι της εντολής, τη μηδενική παράμετρο του πίνακα argv της εντολής (τυπικά, πάλι το όνομά της) και, τις υπόλοιπες παραμέτρους της εντολής τερματισμένες με 0. Αν ως πρώτη παράμετρος δε δοθεί μονοπάτι, τότε η εντολή εντοπίζεται με βάση μονοπάτι των εντολών που έχει καθορίσει ο χρήστης στη μεταβλητή PATH. Παρατήρηση: Θα μπορούσαμε το ίδιο αποτέλεσμα να το έχουμε με τη κλήση της εντολής system("ls -l"); 2.9.1 Δηλώσεις της οικογένειας κλήσεων συστήματος exec Οι συναρτήσεις ορίζονται στη βιβλιοθήκη unistd.h και οι παράμετροι πεπερασμένες ως list είτε ως vector (array) H execl()επιστρέφει τη κατάσταση εξόδου της εντολής που εκτελέστηκε (στην επιτυχία συνήθως είναι 0). Σε περίπτωση αποτυχίας επιστρέφει -1. Η συνάρτηση execlp() επιτρέπει το σύστημα να αναζητήσει την προς εκτέλεση εντολή σε όλους τους καταλόγους που υπάρχουν στην τρέχουσα τιμή της μεταβλητής περιβάλλοντος φλοιού PATH. H execv() λειτουργεί execl() όμως είναι χρήσιμη όταν ο αριθμός των παραμέτρων δεν είναι γνωστός εκ των προτέρων. Η εισαγωγή των ορισμάτων γίνεται με τη λογική του πίνακα ορισμάτων. Επίσης η execvp()αντιστοιχεί στην execlp(). Παράμετροι πεπερασμένες ως list int execl(char *path, char *arg0, char *arg1,..., ΝULL); 15

int execlp(char *path, char *arg0, char *arg1,..., ΝULL); Παράμετροι πεπερασμένες ως vector int execv(char *path, char *const argv[]); int execvp(char *path, char *const argv[]); Παρατηρήσεις: Στις εντολές execl και execv όπου path είναι σχετικό ή απόλυτο μονοπάτι. Στις εντολές execlp και execvp στο path δεν χρειάζεται να ορίσει την τοποθεσία του εκτελέσιμου εφόσον θα γίνει χρήση της μεταβλητής περιβάλλοντος PATH για να βρεθεί το εκτελέσιμο. Αυτό είναι χρήσιμο γιατί μπορεί να μην ξέρουμε που ακριβώς βρίσκεται ένα εκτελέσιμο π.χ. sort, uniq, etc). Τα arg1... argn είναι τα ορίσματα που λαμβάνει η εντολή, ενώ το NULL σημειώνει το τέλος των ορισμάτων. Η διαφορά της execl(list) / execv(vector) είναι ότι το execl παίρνει σαν όρισμα: arg0=«όνομα εκτελέσιμου», arg1=«μεταβλητή 1»,..., argn=«μεταβλητή n», arg(n+1)=null ενώ η execv argv[0], argv[1],..., argv[n], argv[n+1]=null. H ίδια διαφορά ισχύει και για το ζεύγος execlp(list, PATH) και execvp(vector, PATH). Σημείωση: Η τελευταία παράμετρος πρέπει πάντα να είναι NULL (0, μηδέν). Αντιστοιχεί στον NULL terminator (τερματισμό). Αφού ο αριθμός των ορισμάτων στην λίστα είναι μεταβλητός πρέπει να υπάρχει ο τερματισμός NULL. Με βάση τα παραπάνω το προηγούμενο παράδειγμά μας μπορεί να γραφεί και ως execl("ls", "ls", "-l", "/home/cied", NULL); execv("/bin/ls", params); eveclp("/bin/ls", params); όπου char *params[4] = "ls", "-l", "/home/cied", NULL; Για να μπορέσει να συνεχίσει η εκτέλεση της αρχικής διεργασίας η exec συνδυάζεται συχνά με τη fork. Στοιχεία όπως σωληνώσεις (pipelines) και η αλλαγή της εισόδου και εξόδου δεν μπορούν να προσδιοριστούν στην exec μια και αυτά τα χειρίζεται ο φλοιός και όχι ο πυρήνας. Παράδειγμα 10: Στο ακόλουθο παράδειγμα παρουσιάζουμε πως αντικαθιστούμε τον κώδικα που θα εκτελέσει η θυγατρική διεργασία με την 16

κλήση συστήματος execv(). Πιο συγκεκριμένα, η διεργασία παιδί εκτελεί την εντολή: ls -la //exec2.c #include <unistd.h> int main () char *arg[] = "/bin/ls", "-la", NULL; /* fork, and exec within child process */ if (fork() == 0) printf("in child process:\n"); execv(arg[0], arg); printf("i will never be called\n"); printf("execution continues in parent process\n"); Παράδειγμα 11: Δημιουργήστε μια διεργασία παιδί η οποία εκτελεί την date. Μόλις ολοκληρώσει το παιδί ο πατέρας συνεχίζει την εκτέλεση του. //exec3.c #include <stdlib.h> int pid; int status; char *argv[2]; pid = fork(); if (pid == 0) /* Child process */ argv[0] = "date"; argv[1] = NULL; printf("i am child process %d\n"); printf("and I will replace myself by 'date'\n", getpid()); 17

if (execvp("date", argv) == -1 ) perror("execvp"); return(-1); /* Parent process */ if (wait(&status)!= pid) perror("wait"); return(-1); printf("i am parent process %d\n", getpid()); printf("child terminated with exit code %d\n", status >> 8); return(0); 3. Επιπλέον Παραδείγματα Παράδειγμα 12: Με τη χρήση της εντολής fork να υλοποιήσετε ένα κώδικα σε C, στον οποίο ο γονέας να τυπώνει το id του και το id του παιδιού και το παιδί να τυπώνει το id του και το id του γονέα. //fork3.c #include <stdlib.h> int pid; pid = fork(); if (pid == 0) printf( I am the child (Id=%i)\n\n, getpid() ); printf( My parent s id: %i\n, getppid() ); printf( I am the parent (Id=%i)\n, getpid() ); printf( My child s id: %i\n, pid ); 18

Παράδειγμα 13: Υλοποιήστε δύο διεργασίες που εκτελούνται παράλληλα τυπώνοντας διαφορετικά στοιχεία (π.χ. όλους τους ακεραίους από το διάστημα 1-100). Παρατηρήστε το αποτέλεσμα του χρονοπρογραμματισμού στη διαδοχή των εκτυπώσεων. //fork4.c #include <stdlib.h> int i; int pid; pid = fork(); for (i=1; i<=100; i++) if (pid > 0) printf("%3i (parent)\n", i); printf("%3i (child)\n", i); Παράδειγμα 14: Στο επόμενο παράδειγμα η γονική διεργασία τερματίζει πριν τη διεργασία παιδί. Η διεργασία παιδί που έχει δημιουργηθεί, κληρονομείται από την αρχική διεργασία (init) του λειτουργικού συστήματος Linux με process id 1. //fork6.c #include <unistd.h> int i, pid; pid = fork(); if (pid > 0) 19

sleep(2); return(0); for (i=0; i<3; i++) printf("my parent is %d\n", getppid()); sleep(1); Σημείωση: Στο συγκεκριμένο παράδειγμα, η πατρική διεργασία καλεί τη fork(), περιμένει δύο δευτερόλεπτα (καλώντας τη sleep(2)) και στη συνέχεια τερματίζει. H διεργασία παιδί συνεχίζει εμφανίζοντας το process id της γονικής της διεργασίας για 3 δευτερόλεπτα. Κατά την εκτέλεση, παρατηρείστε ότι η ταυτότητα της γονικής διεργασίας (ppid) της διεργασίας αλλάζει σε 1 μετά τον τερματισμό της πατρικής διεργασίας (μετά την ολοκλήρωση της πατρικής διεργασίας ο έλεγχος επιστρέφει στο κέλυφος του λειτουργικού συστήματος αφού το παιδί εκτελείται στο παρασκήνιο). Παράδειγμα 15: Ο παρακάτω κώδικας δέχεται ως όρισμα ένα θετικό αριθμό, το οποίο αντιστοιχεί στον αριθμό των δευτερολέπτων που θα περιμένει το παιδί μέχρι να τερματίσει. Στη συνέχεια τερματίζει και ο γονέας. Και ο γονέας και το παιδί τυπώνουν τις πληροφορίες για τις διεργασίες που υπάρχουν τη τρέχουσα στιγμή στο σύστημα //fork7.c #include <stdlib.h> #include <sys/wait.h> #include <sys/types.h> #define DEFAULT_TIME 3 int main (int argc, char **argv) int pid, seconds, status; pid_t whodied; if (argc == 1) seconds = DEFAULT_TIME; seconds = atoi (argv[1]); printf ("Here I am in the program!\n ); printf ( Seconds = %d\n", seconds); 20

pid = fork(); if (pid > 0) printf ("I'm the parent (Id=%i)\n", getpid() ); printf ("Here's a process list:\n"); system ("ps -l"); whodied = wait (&status); printf ("Child %d exited ", whodied); if (! WIFEXITED(status)) printf ("abnormally!\n"); printf ("with status %d.\n", WEXITSTATUS(status)); printf ("Here's another process list:\n"); system ("ps -l"); sleep(seconds); printf ("I'm the child (Id=%i)\n", getpid() ); printf ("Here's a process list:\n"); system ("ps -l"); printf ("Bye now!\n"); 4. Κοινή μνήμη Η κοινή μνήμη είναι ένας αποδοτικός τρόπος για να μεταφέρει κανείς δεδομένα μεταξύ των προγραμμάτων. Ένα πρόγραμμα θα δημιουργήσει ένα κομμάτι μνήμης όπου άλλες διεργασίες (εάν επιτρέπεται) μπορούν να έχουν πρόσβαση. Μια διεργασία δημιουργεί ένα κοινό τμήμα μνήμης χρησιμοποιώντας την κλήση shmget(). Ο αρχικός ιδιοκτήτης ενός κοινού τμήματος μνήμης μπορεί 21

να μεταφέρει την ιδιοκτησία σε έναν άλλο χρήστη με την shmctl(). Μπορεί επίσης να ανακαλέσει αυτήν την ανάθεση. Άλλες διεργασίες με την κατάλληλη άδεια μπορούν να εκτελέσουν τις διάφορες λειτουργίες ελέγχου στο κοινό τμήμα μνήμης χρησιμοποιώντας την shmctl(). Μόλις δημιουργηθεί, ένα κοινό τμήμα μπορεί να συνδεθεί με τον χώρο διευθύνσεων (address space) μιας διεργασίας χρησιμοποιώντας την shmat() και στη συνέχεια μπορεί να αποσυνδεθεί από τον χώρο διευθύνσεων χρησιμοποιώντας την shmdt(). Η διεργασία που επιθυμεί να χρησιμοποιήσει το κοινό τμήμα πρέπει να έχει τις κατάλληλες άδειες για το shmat(). Μόλις συνδεθεί, η διεργασία μπορεί να διαβάσει ή να γράψει στο τμήμα, όπως επιτρέπεται από την άδεια που ζητήθηκε κατά την διαδικασία σύνδεσης του κοινού τμήματος μνήμης με τον χώρο διευθύνσεων της διεργασίας. Ένα κοινό τμήμα μπορεί να συνδεθεί με την ίδια διεργασία πολλές φορές. Ένα κοινό τμήμα μνήμης περιγράφεται από μια δομή ελέγχου με μια μοναδική ταυτότητα (ID) που δείχνει σε έναν συγκεκριμένο χώρο της φυσικής μνήμης. Η ταυτότητα του τμήματος καλείται shmid. Ο καθορισμός δομών για τις κοινές δομές ελέγχου του κοινού τμήματος μνήμης μπορούν να βρεθούν στο <sys/shm.h>. 4.1 Πρόσβαση ενός κοινού τμήματος μνήμης Η shmget() χρησιμοποιείται για την πρόσβαση σε ένα κοινό τμήμα μνήμης. Σύνταξη: int shmget(key_t key, size_t size, int shmflg); Το όρισμα key είναι μια τιμή πρόσβασης που σχετίζεται με το ID του σημαφόρου. Το όρισμα size είναι το μέγεθος σε bytes της ζητούμενης κοινής μνήμης. Το όρισμα shmflg διευκρινίζει τις αρχικές άδειες πρόσβασης και τα flags ελέγχου. Σημείωση: Οι μηχανισμοί διαδιεργασιακής επικοινωνίας απαιτούν τη δημιουργία ενός «μοναδικού κλειδιού». Διαλέγουμε έναν περίεργο αριθμό που θεωρούμε ότι δεν τον χρησιμοποιούν άλλες διεργασίες Μοναδικά κλειδιά (τύπου key_t) μπορούν να παραχθούν καλώντας τη συνάρτηση Σύνταξη: key_t ftok(const char *pathname, int proj_id) όπου pathname είναι μία υπάρχουσα διαδρομή αρχείου και proj_id είναι ένας ακέραιος αριθμός. Έτσι το σύστημα εξασφαλίζει τη μοναδικότητα του κλειδιού. 22

Σημείωση: Όταν η κλήση της συνάρτησης shmget() είναι επιτυχής, επιστρέφεται το ID της κοινής μνήμης. Αυτή η κλήση χρησιμοποιείται επίσης για να πάρει κανείς το ID ενός υπάρχοντος κοινού τμήματος. Παράδειγμα 16: Στη συνέχεια, παρουσιάζουμε ένα παράδειγμα της χρήσης της shmget() //shmget.c #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> key_t key; /* shared memory key */ int shmid; /* return value from shmget() */ /******************************************************** / /* Initialize a shared variable in shared memory */ /* valid directory name and a number */ key = ftok ("/dev/null", 7); shmid = shmget (key, sizeof (int), 0644 IPC_CREAT); /* shared memory error check */ if (shmid < 0) perror ("shmget: shmget failed\n"); exit(1); printf("shmget: shmget returned %d\n", shmid); exit(0); 23

4.2 Έλεγχος ενός κοινού τμήματος μνήμης Η shmctl() χρησιμοποιείται για να αλλάξει τις άδειες χρήσης και άλλα χαρακτηριστικά ενός κοινού τμήματος μνήμης. Σύνταξη: int shmctl(int shmid, int cmd, struct shmid_ds *buf); Η διεργασία πρέπει να έχει το κατάλληλο shmid (ID) του ιδιοκτήτη ή του δημιουργού του κοινού τμήματος για να εκτελέσει αυτήν την εντολή. Το όρισμα cmd είναι μία από τις παρακάτω εντολές ελέγχου: SHM_LOCK : Κλειδώστε το συγκεκριμένο κοινό τμήμα μνήμης στη μνήμη. SHM_UNLOCK : Ξεκλειδώστε το κοινό τμήμα μνήμης. IPC_STAT : Επιστρέψτε τις πληροφορίες κατάστασης που περιλαμβάνονται στη δομή ελέγχου και τις τοποθετήστε τις στον bufferμε δείκτη buf. Η διεργασία πρέπει να έχει άδεια ανάγνωσης στο τμήμα για να εκτελεσθεί αυτή η εντολή. IPC_SET :Αρχικοποίηση των αδειών χρήσης και πρόσβασης του κοινού τμήματος μνήμης. IPC_RMID :Αφαιρέστε το κοινό τμήμα μνήμης Ο buffer με δείκτη buf είναι μία δομή του τύπου struct shmid_ds που ορίζεται στο <sys/shm.h> 4.3 Σύνδεση και αποσύνδεση ενός κοινού τμήματος μνήμης Οι shmat() και shmdt() χρησιμοποιούνται για να συνδέσουν και να αποσυνδέσουν τα κοινά τμήματα μνήμης. Σύνταξη: void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr); Η shmat() επιστρέφει έναν δείκτη, shmaddr, στην αρχή του κοινού τμήματος που συνδέεται με ένα έγκυρο shmid. Η shmdt() αποσυνδέει το κοινό τμήμα μνήμης που βρίσκεται στη διεύθυνση που υποδεικνύεται από το shmaddr. Ο ακόλουθος κώδικας επεξηγεί την χρήση των shmat() και shmdt(): 24

Παράδειγμα 17: Στο παρακάτω κώδικα ορίζεται μία κοινή μεταβλητή με αρχική τιμή μηδέν, την οποία αυξάνει το παιδί κατά μία μονάδα και η γονική διεργασία τυπώνει τη νέα τιμή της κοινά διαμοιραζόμενης μεταβλητής. /* printf() */ #include <stdlib.h> /* exit(), malloc(), free() */ #include <sys/types.h> /* key_t, sem_t, pid_t */ #include <sys/shm.h> /* shmat(), IPC_RMID */ #include <errno.h> /* errno, ECHILD */ #include <semaphore.h> /* sem_open(), sem_destroy(), sem_wait().. */ #include <fcntl.h> /* O_CREAT, O_EXEC */ int main () key_t shmkey; /* shared memory key */ int shmid; /* shared memory id */ pid_t pid; /* fork pid */ int *p; /* shared variable */ /*shared */ /********************************************************/ /* Initialize a shared variable in shared memory */ /* valid directory name and a number */ shmkey = ftok ("/dev/null", 5); shmid = shmget (shmkey, sizeof (int), 0644 IPC_CREAT); /* shared memory error check */ if (shmid < 0) perror ("shmget\n"); exit (1); /********************************************************/ /********************************************************/ /* attach p to shared memory */ p = (int *) shmat (shmid, NULL, 0); *p = 0; 25

printf(" Initial value of shared variable: %d\n\n", *p); /********************************************************/ pid = fork (); if (pid < 0) printf ("Fork error.\n"); return (-1); /******************************************************/ /****************** PARENT PROCESS ****************/ /******************************************************/ if (pid > 0) /* wait for all children to exit */ while (pid = waitpid (-1, NULL, 0)) if (errno == ECHILD) break; printf(" Parent: Value of shared variable = %d\n", *p); /* shared memory detach */ shmdt (p); shmctl (shmid, IPC_RMID, 0); /******************************************************/ /****************** CHILD PROCESS *****************/ /******************************************************/ printf (" Child increases value of shared variable.\n\n"); printf (" Child: New value of shared variable = %d.\n", ++(*p)); exit (0); 26

5. Ασκήσεις Άσκηση 1: Με τη χρήση των εντολών που έχετε διδαχθεί, να υλοποιήσετε με δυο συντρέχουσες διεργασίες το παράλληλο πρόγραμμα που δίνεται στη σελίδα 8 των διαφανειών του κεφαλαίου 4 (Συγχρονισμός). Εκτελέστε μερικές φορές το πρόγραμμα και διατυπώστε τις παρατηρήσεις σας. Άσκηση 2: Με τη χρήση των εντολών που έχετε διδαχθεί, να υλοποιήσετε με δυο συντρέχουσες διεργασίες το παράλληλο πρόγραμμα που δίνεται στη σελίδα 10 των διαφανειών του κεφαλαίου 4 (Συγχρονισμός). Εκτελέστε μερικές φορές το πρόγραμμα και διατυπώστε τις παρατηρήσεις σας. Άσκηση 3: Υλοποιείστε τον κατάλληλο συγχρονισμό για την άσκηση1, χρησιμοποιώντας τον αλγόριθμο της σελίδας 33. των διαφανειών του κεφαλαίου 4 (Συγχρονισμός). Εκτελέστε μερικές φορές το πρόγραμμα και διατυπώστε τις παρατηρήσεις σας. Άσκηση 4: Να γράψετε ένα πρόγραμμα στο οποίο θα διαβάζει από το πληκτρολόγιο μία εντολή φλοιού και θα την εκτελεί. Άσκηση 5: Πόσες διεργασίες θα δημιουργηθούν από την εκτέλεση του παρακάτω κώδικα; #include <stdlib.h> int i; for (i=0; i<4; i++) fork(); Άσκηση 6: Εξηγήστε προσεκτικά τι κάνει το παρακάτω πρόγραμμα. Πόσες διεργασίες υπάρχουν σε κατάσταση sleeping 5 δευτερόλεπτα μετά την έναρξή του. #include <stdlib.h> 27

int i, pid; for (i=0; i<4; i++) pid = fork(); if (pid < 0) printf( Could not create any child\n ); printf( Fork ok\n ); sleep(10); Άσκηση 7: Εξηγήστε προσεκτικά τι κάνει το παρακάτω πρόγραμμα. #include <stdlib.h> int pid; int x,y; x = 10; y = 10; pid = fork(); if (pid!= 0) x++; y--; printf("x = %i y = %i\n",x,y); pid = fork(); if (pid!= 0) x++; y--; printf("x = %i y = %i\n",x,y); 28

Άσκηση 8: Δημιουργείστε ένα πρόγραμμα στο οποίο μία διεργασία στο Unix παράγει άλλες 4 θυγατρικές της. Άσκηση 9: Να δημιουργήσετε ένα πρόγραμμα στο οποίο να δημιουργούνται 5 διεργασίες (αλυσίδα κάθε μία να είναι παιδί της άλλης). Κάθε διεργασία να τυπώνει, το id του πατέρα της, το id της και το id του μοναδικού παιδιού που δημιουργεί. Άσκηση 10: Εξηγήστε προσεκτικά τι κάνει το παρακάτω πρόγραμμα. i. Πόσες διεργασίες υπάρχουν σε κατάσταση sleeping 10 δευτερόλεπτα μετά την έναρξή του. ii. Τροποποιήστε κατάλληλα το κώδικα έτσι ώστε κάθε διεργασία να τυπώνει το id της και το id του γονέα της πριν βρεθεί σε κατάσταση sleeping. #include <stdlib.h> int pid1; int pid2; pid1 = fork(); if (pid1 < 0) printf( Could not create any child\n ); pid2 = fork(); if (pid2 < 0) printf( Could not create any child\n ); if ( (pid1 < 0) && (pid2 < 0) kill(pid1,9); sleep(20); Άσκηση 11: Να δημιουργήσετε το πρόγραμμα το οποίο: 1. Θα ορίσετε μια συνάρτηση nothing() η οποία θα ορίζει μια μεταβλητή int x=0 και θα κάνει την πράξη x=x+1. Αυτή η 29

συνάρτηση δεν κάνει τίποτα χρήσιμο, αλλά απλώς υπάρχει για να μας βοηθήσει στη μέτρηση. 2. Μέσα στο main() θα εκτελεί μια φορά τη συνάρτηση time() με τις κατάλληλες παραμέτρους, θα αποθηκεύεται ο αριθμός των δευτερολέπτων στη μεταβλητή start και αμέσως μετά θα εκτυπώνεται το μήνυμα Αρχική τιμή δευτερολέπτων ακολουθούμενο από τη start. 3. Στη συνέχεια θα υπάρχει ένας βρόχος while() ο οποίος θα δημιουργεί 100 διεργασίες (δοκιμάστε και για 5000, 10000) με τη fork() οι οποίες όλες θα εκτελούν τη nothing(). Προσοχή, ο πατέρας θα δημιουργήσει όλες τις διεργασίες και όχι η κάθε διεργασία θα δημιουργεί άλλη διεργασία. 4. Μόλις δημιουργηθούν οι 100 διεργασίες και μόνο τότε θα εκτελεί ο πατέρας 100 φορές τη waitpid() ώστε να περιμένει την επιτυχή ολοκλήρωση όλων των θυγατρικών. 5. Στη συνέχεια θα καλείται η time(), θα αποθηκεύεται ο αριθμός των δευτερολέπτων στη μεταβλητή end και αμέσως μετά θα εκτυπώνεται το μήνυμα Τελική τιμή δευτερολέπτων ακολουθούμενη από την end. Θα γίνεται η πράξη end-start, θα εκτυπώνεται το αποτέλεσμα ενώ θα εκτυπώνεται και το αποτέλεσμα «(end-start)/100» για να εμφανιστεί ο μέσος χρόνος δημιουργίας, εκτέλεσης και τερματισμού των 100 διεργασιών. Πόσος είναι ο συνολικός χρόνος και ο μέσος χρόνος δημιουργίας εκτέλεσης και τερματισμού των 100 διεργασιών στο σύστημα σας (μη ξεχάσετε τη μονάδα μέτρησης); 30

Παράρτημα Υποδείξεις: Ο πηγαίος κώδικας από όλα τα παραδείγματα που χρησιμοποιούνται σε αυτό το κείμενο βρίσκεται στο φάκελο Έγγραφα\Source Code, στο δικτυακό τόπο του μαθήματος στο eclass. Η μεταγλώττιση ενός προγράμματος στο Linux γίνεται με τη χρήση της εντολής: gcc o <όνομα εκτελέσιμου> <όνομα αρχείου που περιέχει τον πηγαίο κώδικα>. Η εντολή αυτή θα πρέπει να δοθεί από τερματικό ενώ ο τρέχων κατάλογος εργασίας θα πρέπει να είναι αυτός στον οποίο βρίσκεται αποθηκευμένος ο πηγαίος κώδικας του προγράμματος. (Η εντολή με την οποία μπορούμε να βρούμε τον τρέχοντα κατάλογο είναι η pwd ενώ μετακινούμαστε μεταξύ καταλόγων με την εντολή cd. Τα περιεχόμενα των καταλόγων βρίσκονται με την εντολή ls. Περισσότερες πληροφορίες για τη λειτουργικότητα και τις παραμέτρους των εντολών (αλλά και των κλήσεων συστήματος) μπορούν να βρεθούν μέσω της βοήθειας του Linux που δίνεται αν πληκτρολογήσουμε man <όνομα εντολής>. Η εκτέλεση ενός προγράμματος γίνεται πληκτρολογώντας:./<όνομα εκτελέσιμου> Η εκτέλεση ενός προγράμματος στο παρασκήνιο γίνεται πληκτρολογώντας:./<όνομα εκτελέσιμου> & 31