Κλείδωμα αρχείων (file locking) Προγραμματισμός II 1 lalis@inf.uth.gr
Κλείδωμα αρχείων Οι διεργασίες που προσπελάζουν ένα αρχείο μέσω ξεχωριστών περιγραφέων, μπορούν να γράψουν / διαβάσουν τα περιεχόμενα του αρχείου άμεσα, χωρίς κάποιο συγχρονισμό μεταξύ τους Αυτό δεν εξασφαλίζει απαραίτητα την επιθυμητή συνέπεια σε (πιο ψηλό) επίπεδο εφαρμογής Για παράδειγμα μια διεργασία μπορεί να διαβάζει το αρχείο την ίδια στιγμή που μια άλλη διεργασία αλλάζει τα περιεχόμενα του Σε κάποιες περιπτώσεις χρειάζεται συγχρονισμένη πρόσβαση των διεργασιών στο ίδιο αρχείο Προγραμματισμός II 2 lalis@inf.uth.gr
Αρχεία «λουκέτα» (lock files) Όταν μια διεργασία επιθυμεί να χρησιμοποιήσει το αρχείο <fname> κατ αποκλειστικότητα, επιχειρεί προηγουμένως να δημιουργήσει ένα ξεχωριστό αρχείο-λουκέτο (συνήθως <fname>.lck) Το αρχείο-λουκέτο δημιουργείται με O_CREAT O_EXCL Αν το υπάρχει ήδη, η open θα αποτύχει (αυτό σημαίνει πως το αρχείο <fname> χρησιμοποιείται ήδη από μια άλλη διεργασία), και η προσπάθεια επαναλαμβάνεται Όταν μια διεργασία ολοκληρώσει την εργασία της με το αρχείο fname, «απελευθερώνει» την πρόσβαση, απομακρύνοντας το αρχείο λουκέτο fname.lck Προγραμματισμός II 3 lalis@inf.uth.gr
int main(int argc, char *argv[]) { int fd,fdlck,i,pid; pid = fork(); for (i = 0; i < N; i++) { while (1) { printf("%d: create lock file\n",getpid()); fdlck = open(argv[2],o_creat O_EXCL,S_IRWXU); if (fdlck > 0) { break; if (errno!= EEXIST) { perror("open"); return(1); printf("%d: retrying\n",getpid()); sleep(1); // retry in 1 sec printf("%d: success %d\n",getpid(),i); close(fdlck); fd = open(argv[1],o_rdwr O_CREAT,S_IRWXU); sleep(5); // do something with the file close(fd); printf("%d: unlocking\n",getpid()); unlink(argv[2]); // remove lock file sleep(1); // give other party a chance to lock if (pid > 0) { waitpid(pid,null,0); return(0); Προγραμματισμός ΙΙΙ 4 lalis@inf.uth.gr
Μειονεκτήματα Οι διεργασίες πραγματοποιούν ενεργή αναμονή Υπάρχει περίπτωση λιμοκτονίας μια διεργασία μπορεί να «προσπερνιέται» επ αόριστο Το αρχείο «κλειδώνεται» ολόκληρο Δεν γίνεται διαχωρισμός σχετικά με το αν μια διεργασία επιθυμεί να διαβάσει ή να γράψει Το αρχείο-λουκέτο μπορεί να μην σβηστεί ποτέ λάθος του προγραμματιστή, τερματισμός διεργασίας Η χρήση ενός αρχείου-λουκέτου αποτελεί μια σύμβαση καθαρά σε επίπεδο εφαρμογής ένα πρόγραμμα μπορεί να προσπελάσει ένα αρχείο, χωρίς να ελέγξει την ύπαρξη του αρχείου λουκέτου Προγραμματισμός II 5 lalis@inf.uth.gr
Κλείδωμα αρχείων μέσω του λειτουργικού Γίνεται διαφοροποίηση ανάμεσα σε κλείδωμα για ανάγνωση και κλείδωμα για γράψιμο πολλές διεργασίες μπορούν να διαβάζουν ταυτόχρονα μόνο μια διεργασία μπορεί να γράφει Το κλείδωμα μπορεί να γίνει τμηματικά γίνεται αυτόματη «συγχώνευση» ή «σπάσιμο» των τμημάτων που κλειδώνονται από την ίδια διεργασία Το κλείδωμα είναι «συμβουλευτικό» (σύμβαση) οποιοδήποτε πρόγραμμα/διεργασία μπορεί να παρακάμψει το κλείδωμα, απλά μη-κλειδώνοντας το αρχείο Υποστηρίζεται μέσω fcntl (αλλά και flock,lockf) Προγραμματισμός II 6 lalis@inf.uth.gr
Κλείδωμα με fcntl int fcntl(int fd, int op, ); Η op καθορίζει την επιθυμητή λειτουργία στον fd, από την οποία εξαρτάται ποιες επιπλέον παράμετροι πρέπει να περαστούν στην κλήση της fcntl F_SETLK: εκτέλεση της λειτουργίας (κλείδωμα/ξεκλείδωμα), με άμεση επιστροφή -1 (errno ίσο με EACCES/EAGAIN) αν η λειτουργία (κλείδωμα) δεν μπορεί να εκτελεσθεί άμεσα F_SETLKW: όπως F_SETLK, αλλά μπλοκάρει την διεργασία μέχρι να επιτευχθεί η ζητούμενη λειτουργία (κλείδωμα) F_GETLK: λήψη στοιχείων του πρώτου κλειδώματος που θα μπορούσε να προκαλέσει μπλοκάρισμα στην καθορισμένη λειτουργία (κλείδωμα) βλέπε εγχειρίδια Επιπλέον παράμετρος: δείκτης σε struct flock που περιέχει την επιθυμητή λειτουργία και τις παραμέτρους Προγραμματισμός II 7 lalis@inf.uth.gr
Δομή struct flock struct flock { short l_type; short l_whence; off_t l_start; off_t l_len; pid_t l_pid; ; Το l_type καθορίζει την ζητούμενη λειτουργία F_RDLCK (μόνο διάβασμα), F_WRLCK (γράψιμο και διάβασμα), F_UNLCK (ξεκλείδωμα) Το l_whence καθορίζει την ερμηνεία του l_start SEEK_SET, SEEK_CUR, SEEK_END (όπως στην lseek) Το l_start καθορίζει την αρχή του τμήματος για κλείδωμα/ξεκλείδωμα), και το l_len το μήκος του Προγραμματισμός II 8 lalis@inf.uth.gr
int main(int argc, char *argv[]) { int fd,i,pid; struct flock lck; lck.l_whence = SEEK_SET; lck.l_start=0; lck.l_len=0; pid = fork(); fd = open(argv[1],o_rdwr O_CREAT,S_IRWXU); for (i = 0; i < N; i++) { printf("%d: trying to lock\n",getpid()); lck.l_type=f_wrlck; fcntl(fd,f_setlkw,&lck); printf("%d: success %d\n",getpid(),i); sleep(5); // do something with the file printf("%d: unlocking\n",getpid()); lck.l_type = F_UNLCK; fcntl(fd,f_setlk,&lck); sleep(1); // give other party a chance to lock if (pid > 0) { waitpid(pid,null,0); return(0); Προγραμματισμός ΙΙΙ 9 lalis@inf.uth.gr
Αντιστοίχιση αρχείων στην μνήμη (memory-mapped files) Προγραμματισμός II 10 lalis@inf.uth.gr
Πρόσβαση σε αρχεία μέσω της μνήμης Η πρόσβαση των περιεχομένων ενός αρχείου μέσω περιγραφέων γίνεται «σειριακά» με read, write Μπορεί να γίνουν και «άλματα», με lseek Αυτά γίνονται με το κόστος μιας κλήσης συστήματος Εναλλακτικά, το αρχείο (ή ένας μέρος του) μπορεί να αντιστοιχηθεί στην μνήμη μιας διεργασίας Η πρόσβαση στα περιεχόμενα του αρχείου μπορεί να γίνει διαβάζοντας/γράφοντας τις αντίστοιχες θέσεις μνήμης της διεργασίας Χωρίς να πραγματοποιούνται κλήσεις συστήματος Προγραμματισμός ΙΙ 11 lalis@inf.uth.gr
Βασικές λειτουργίες void *mmap(void *p, long len, int prot, int flags, int fd, long off); Αντιστοιχίζει στην διεύθυνση p τα περιεχόμενα του αρχείου που αντιστοιχεί στον περιγραφέα fd από την θέση offset μέχρι τη θέση offset+len προστασία πρόσβασης μέσω prot, ρυθμίσεις μέσω flags int msync(void *p, long len, int flags); Ενημερώνει το αρχείο (στον δίσκο) μονιμοποίηση αλλαγών που έγιναν μέσω μνήμης int munmap(void *p, long len); Ακυρώνει την αντιστοίχιση πάνω στην διεύθυνση p γίνεται αυτόματα όταν τερματίζεται η διεργασία Προγραμματισμός ΙΙ 12 lalis@inf.uth.gr
memory P1 file memory mapped area Προγραμματισμός ΙΙ 13 lalis@inf.uth.gr
int main(int argc, char *argv[]) { int fd,n,i,offset; char *p; struct stat sb; fd = open(argv[1],o_rdwr); offset = atoi(argv[2]); fstat(fd,&sb); n = sb.st_size; p = mmap(null,n,prot_read PROT_WRITE,MAP_SHARED,fd,0); if (p == ((void *)-1)) { perror("mmap"); return(1); close(fd); // file descriptor no longer required // read file contents, via memory for (i=0; i < n; i++) { printf("%c",p[i]); printf("\n"); // modify file contents, via memory for (i=0; i < n; i++) { p[i] = p[i] + offset; msync(p,n,ms_sync); // write changes on disk munmap(p,n); return(0); Προγραμματισμός ΙΙ 14 lalis@inf.uth.gr
Κοινή μνήμη μέσω memory mapped files Αν στα flags συμπεριληφθεί η τιμή MAP_SHARED Οι αλλαγές στην μνήμη περνιούνται στο αρχείο Είναι ορατές σε άλλες διεργασίες που έχουν αντιστοιχίσει το αρχείο στην μνήμη τους στο πνεύμα του shared memory Είναι ορατές σε όλους τους περιγραφείς που έχουν ανοιχτεί για το αρχείο (και το αντίστροφο) χωρίς να καλείται msync ή αντίστοιχα fsync Αν στα flags συμπεριληφθεί η τιμή MAP_PRIVATE Οι αλλαγές που γίνονται δεν είναι ορατές σε άλλες διεργασίες, ούτε περνιούνται στο ίδιο το αρχείο Προγραμματισμός ΙΙ 15 lalis@inf.uth.gr
ιδιωτική μνήμη κοινή μνήμη ιδιωτική μνήμη P1 P2 file memory mapped area Προγραμματισμός ΙΙ 16 lalis@inf.uth.gr
int main(int argc, char *argv[]) { int fd,n,i,offset; char *p; struct stat sb; fd = open(argv[1],o_rdwr); fstat(fd,&sb); n = sb.st_size; p = mmap(null,n,prot_read PROT_WRITE,MAP_SHARED,fd,0); close(fd); if (!fork()) { offset = atoi(argv[2]); for (i=0; i < n; i++) { p[i] = p[i] + offset; munmap(p,n); return(0); wait(null); // wait for child to write for (i=0; i < n; i++) { printf("%c",p[i]); printf("\n"); munmap(p,n); return(0); Προγραμματισμός ΙΙ 17 lalis@inf.uth.gr
int main(int argc, char *argv[]) { int fd,n,i,offset; char *p,s[n]; struct stat sb; fd = open(argv[1],o_rdwr); if (!fork()) { offset = atoi(argv[2]); fstat(fd,&sb); n = sb.st_size; p = mmap(null,n,prot_read PROT_WRITE,MAP_SHARED,fd,0); close(fd); for (i=0; i < n; i++) { p[i] = p[i] + offset; munmap(p,n); return(0); wait(null); // wait for child to write n = read(fd,s,n); s[n] = '\0'; printf("%s\n",s); return(0); Προγραμματισμός ΙΙ 18 lalis@inf.uth.gr
int main(int argc, char *argv[]) { int fd,n,i,offset; char *p,s[n]; struct stat sb; fd = open(argv[1],o_rdwr); if (!fork()) { sleep(1); // wait for parent to mmap offset = atoi(argv[2]); n = read(fd,s,n); for (i=0; i < n; i++) { s[i] = s[i] + offset; lseek(fd,0,seek_set); write(fd,s,n); close(fd); return(0); fstat(fd,&sb); n = sb.st_size; p = mmap(null,n,prot_read PROT_WRITE,MAP_SHARED,fd,0); wait(null); // wait for child to write for (i=0; i < n; i++) { printf("%c",p[i]); printf("%s\n",s); munmap(p,n); return(0); Προγραμματισμός ΙΙ 19 lalis@inf.uth.gr