Ντίρλης Νίκος- ΕΤΥ 4ο ΦΡΟΝΤΙΣΤΗΡΙΟ Παρασκευή Β4 1
Κοινή Μνήμη μεταξύ διεργασιών -> Γρηγορότερη μορφή IPC Τα δεδομένα δεν αντιγράφονται πάνω από μία φορά Server/Client model. Όταν ο Server γράφει, ο client πρέπει να διαβάσει όταν ο server έχει τελειώσει. Χρειάζεται συγχρονισμός Χρήση semaphors - 2 -
Δημιουργία κοινής μνήμης από διεργασία ◦ shmget() Προσκόλληση διεργασίας στης κοινή μνήμη ◦ shmat() Αποκόλληση διεργασία; από κοινή μνήμη ◦ shmdt() Έλεγχος κοινής μνήμης (περιλαμβάνει διαγραφή) ◦ shmctl() man (shmget) man(shmat) man(shmdt) man(shmctl) - 3 -
Με την shmget() παίρνουμε ένα shared memory identifier #include int shmget(key_t key, int size, int flag); Η shmget() επιστρέφει ένα shared memory ID if OK και -1 αν error Το «key» είναι συνήθως η σταθερά “IPC_PRIVATE”, που ζητά από τον kernel έναν non-negative integer identifier που η τιμή του αυξάνει μέχρι μια μέγιστη τιμή πριν μηδενιστεί. Το «size» είναι το μέγεθος των shared memory segment σε bytes Το «flag» μπορεί να είναι “SHM_R”, “SHM_W” ή “SHM_R | SHM_W” - 4 -
Όταν ένα shared memory segment έχει δημιουργηθεί, το process το κάνει attach στο address space του καλώντας την shmat(): void *shmat(int shmid, void* addr, int flag); Η shmat() επιστρέφει έναν pointer σε ένα shared memory segment αν OK και -1 αν error Η συνήθης προτεινόμενη προγραμματιστική τεχνική είναι να θέτονται το addr και flag στο μηδέν, πχ: char* buf = (char*)shmat(shmid,0,0); Στο UNIX οι εντολές “ipcs” και “ipcrm” χρησιμοποιούνται για να κάνουν list και remove shared memory segments στο current machine. Το default action για ένα shared memory segment είναι να παραμένει στο σύστημα ακόμα και μετά το θάνατο μιας διεργασίας. Αν θέλουμε να το αποφύγουμε αυτό (και συνήθως το θέλουμε!) χρησιμοποιούμε την shmctl()
Η shmctl() κάνει αρκετά πραγματάκια: int shmctl (int shmid, int cmd, struct shmid_ds *buf); Το «cmd» μπορεί να είναι ένα από τα IPC_STAT, IPC_SET, ή IPC_RMID. Μπορούμε εύκολα να δούμε τι κάνει το καθένα από αυτά. Με ένα googlάρισμα βρίσκουμε: – IPC_STAT fills the buf data structure – IPC_SET can change the uid, gid, and mode of the shmid – IPC_RMID sets up the shared memory segment to be removed from the system once the last process using the segment terminates or detaches from it – a process detaches from a shared memory segment using shmdt(void* addr), which is similar to free() Η shmctl() επιστρέφει 0 αν OK και -1 αν error - 6 -
shm_id = shmget(SHM_KEY, SHM_SIZE, 0600); if (shm_id < 0) { printf("Could not create shared memory!\n"); exit(1); } data = shmat(shm_id, NULL, 0); //δείκτης προς την αρχή της sm if (data == (char *)-1) { printf(“Could not attach to shared memory!\n”); exit(1); } - 7 -
char* ShareMalloc(int size) { int shmId; char* returnPtr; if ((shmId=shmget(IPC_PRIVATE, size, (SHM_R | SHM_W)) < 0) Abort(“Failure on shmget\n”); if (returnPtr=(char*)shmat(shmId,0,0)) == (void*) -1) Abort(“Failure on shmat\n”); shmctl(shmId, IPC_RMID, (struct shmid_ds *) NULL); return (returnPtr); } - 8 -
Τι θα συμβεί αν τρέξουμε το παρακάτω κομμάτι κώδικα; int main(void) { pid_t pid; if ((pid = fork()) < 0) Abort(“Fork Error”); else if (pid == 0) charatatime(“output from child\n”); else charatatime(“output from parent\n”); exit(0); } Έχουμε το φαινόμενο του «compile time non-determinism»! Διαδοχικά run του παραπάνω κώδικα δίνουν και διαφορετικά αποτελέσματα! - 9 -
Αν θέλουμε να επέμβουμε στη σειρά εκτέλεσης (γιατί για παράδειγμα η διεργασία-γονιός χρειάζεται κάποια δεδομένα από τη διεργασία παιδί) μπορούμε να κάνουμε wait. Σε αυτή την περίπτωση χρησιμοποιούμε signals. Διαφορετικά κάνουμε busy waiting κοιτώντας ένα flag, κάτι όμως που φυσικά θέλουμε να το αποφεύγουμε! Όμως για θυμηθείτε το παράδειγμα των Φιλοσόφων! Χρειαζόμαστε και έναν «διαιτητή» (ή έναν «σερβιτόρο» στην περίπτωση των φιλοσόφων). Αυτός είναι ο ρόλος του semaphor
Code that modifies shared data usually has the following parts: – Entry section: The code that requests permission to modify the shared data. – Critical Section: The code that modifies the shared variable. – Exit Section: The code that releases access to the shared data. – Remainder: The remaining code The critical section problem refers to the problem of executing critical sections in a fair, symmetric manner. Solutions to the critical section problem must satisfy each of the following: – Mutual Exclusion: At most one process is in its critical section at any time – Progress: If no process is executing its critical section, a process that wishes to enter can get in – Bounded Waiting: No process is postponed indefinitely An atomic operation is an operation that, once started, completes in a logical indivisible (=αδιαίρετος) way. Most solutions to the critical section problem rely on the existence of certain atomic operations
Ένας semaphore είναι μια ακέραια μεταβλητή με δύο atomic operations: wait and signal. Άλλες ονομασίες που θα βρείτε για αυτές είναι down, P και lock για το wait και up, V, unlock και post για το signal. Μία process που κάνει wait σε έναν semaphore S δεν μπορεί να συνεχίσει μέχρι η τιμή του S είναι θετική. Μετά μειώνει κατά ένα την τιμή του is S. Όταν γίνει «signal» η τιμή του S μεγαλώνει κατά ένα. Τι δεν θέλουμε να κάνει ένας semaphor; void wait(int *s) { while (*s <= 0); /* END WHILE*/ (*s)--; } void signal(int *s) { (*s)++; }
Το πρόβλημα με τον κώδικα αυτό είναι αρχικά το busy waiting και επίσης το γεγονός ότι οι πράξεις «- -» και «+ +» δεν είναι atomic! Αυτό που θέλουμε να κάνουμε είναι το ακόλουθο: wait(&s); /* critical section */ signal(&s); /* remainder section */ και αυτό γίνεται με τη χρήση των system calls semget() και semop()
Δημιουργία - Άνοιγμα σημαφόρου ◦ sem_open() Κλείδωμα σημαφόρου ◦ sem_wait() Απελευθέρωση σημαφόρου ◦ sem_post() Κλείσιμο σημαφόρου ◦ sem_close() Διαγραφή σημαφόρου ◦ sem_unlink()
#include int semget(key_t key, int nsems, int semflg); Δημιουργεί έναν semaphore και αρχικοποιεί κάθε στοιχείο του στο μηδέν. int semID = semget(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR); Όπως στο shared memory, έτσι και εδώ το ipcs και ipcrm μπορούν να κάνουν list και remove semaphores
int semop(int semid, struct sembuf *sops, unsigned nsops); Increment, decrement, or test semaphore elements for a zero value Από το : sops->sem_num, sops->sem_op, sops->sem_flg Αν το sem_op είναι θετικό ή semop() προσθέτει ένα στην τιμή και ξυπάνει τη διαδικασία που περιμένει. Αν το sem_op είναι αρνητικό, η semop() προσθέτει ένα στην τιμή και αν <0 ή semop() sets to 0 and blocks until it increases Αν το sem_op είναι μηδέν και το semaphore element value όχι μηδέν, η semop() blocks the calling process until the value becomes zero Αν η semop() is interrupted by a signal, it returns –1 with errno = EINTR
struct sembuf semWait[1] = {0,-1,0}; semSignal[1] = {0,1,0}; int semID; semop(semID,semSignal,1); /* init to 1 */ while((semop(semID,semWait,1) == -1) && (errno == EINTR)); { /* Critical Section */ } while((semop(semID,semSignal,1) == -1) && (errno == EINTR));