ΕΘΝΙΚΟ ΚΑΙ ΚΑΠΟΔΙΣΤΡΙΑΚΟ ΠΑΝΕΠΙΣΤΗΜΙΟ ΑΘΗΝΩΝ ΤΜΗΜΑ ΠΛΗΡΟΦΟΡΙΚΗΣ & ΤΗΛΕΠΙΚΟΙΝΩΝΙΩΝ ΜΑΘΗΜΑ: ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ Αρχεία και Μεταδεδομένα ΣΗΜΕΙΩΣΕΙΣ Κάθε αρχείο χαρακτηρίζεται και αναφέρεται από ένα αριθμό ο οποίος ονομάζεται αριθμός inode. Ένα inode είναι ένα φυσικό αντικείμενο που βρίσκεται στο δίσκο αλλά και μια εννοιολογική αναπαράσταση ενός αρχείου στον πυρήνα του Λειτουργικού Συστήματος. Το inode αποθηκεύει μεταδεδομένα που σχετίζονται με ένα αρχείο: άδειες πρόσβασης, μέγεθος, χρόνος τελευταίας προσπέλασης, ιδιοκτήτης, κ.λπ. Μπορούμε να προσπελάσουμε τον αριθμό inode με την εντολή ls i. Για να προσπελάσουμε τα μεταδεδομένα των αρχείων προγραμματιστικά μπορούμε να χρησιμοποιήσουμε την οικογένεια συναρτήσεων stat. Η stat() επιστρέφει πληροφορίες για ένα αρχείο που ορίζεται με το path, η fstat() επιστρέφει πληροφορίες για ένα αρχείο που ορίζεται με ένα περιγραφέα αρχείου fd ενώ η lstat() κάνει τα ίδια πράγματα με την stat() αλλά στην περίπτωση ενός συμβολικού δεσμού η lstat() επιστρέφει πληροφορίες για το συμβολικό δεσμό και όχι για το αρχείο. Κάθε μια από τις παραπάνω συναρτήσεις αποθηκεύει τις σχετικές πληροφορίες σε μια δομή stat που ορίζεται στη βιβλιοθήκη <bits/stat.h> και περιλαμβάνεται στην <sys/stat.h>. Σε περίπτωση επιτυχίας, οι συναρτήσεις επιστρέφουν το 0,ενώ σε αποτυχία επιστρέφουν το -1 και θέτουν το errno σε ένα από τα ακόλουθα:
Code 1 stat() example #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> int main (int argc, char *argv[]) struct stat sb; int ret; if (argc < 2) fprintf (stderr,"usage: %s <file>\n", argv[0]); ret = stat (argv[1], &sb); if (ret) perror ("stat"); printf ("%s is %ld bytes\n",argv[1], sb.st_size); return 0; Το ακόλουθο τμήμα κώδικα χρησιμοποιεί την fstat() ώστε να διαπιστώσει αν ένα αρχείο βρίσκεται σε μια φυσική συσκευή (και όχι σε μια συσκευή δικτύου): /* * is_on_physical_device returns a positive * integer if 'fd' resides on a physical device, * 0 if the file resides on a nonphysical or * virtual device (e.g., on an NFS mount), and * -1 on error.
*/ int is_on_physical_device (int fd) struct stat sb; int ret; ret = fstat (fd, &sb); if (ret) perror ("fstat"); return -1; return gnu_dev_major (sb.st_dev); Μπορούμε επίσης να προχωρήσουμε σε αλλαγή των αδειών πρόσβασης σε ένα αρχείο προγραμματιστικά μέσω των ακόλουθων συναρτήσεων: Στην chmod ορίζουμε μια διαδρομή προς το αρχείο ενώ με την fchmod χρησιμοποιούμε ένα περιγραφέα αρχείου. Στο mode εφαρμόζεται μια πράξη OR σε όσα από τα ακόλουθα επιθυμεί ο προγραμματιστής. Τα λάθη που μπορεί να επιστρέψουν οι συναρτήσεις παρουσιάζονται στον ακόλουθο πίνακα:
Παραδείγματα χρήσης: Τέλος, οι συναρτήσεις chown, lchown και fchown επιτρέπουν την αλλαγή του ιδιοκτήτη και της ομάδας ενός αρχείου.
Τα λάθη που μπορεί να επιστραφούν εμφανίζονται στον ακόλουθο πίνακα: Όταν το owner ή το group είναι ίσο με -1, τότε η αντίστοιχη παράμετρος δεν τίθεται στο αρχείο. Ο root χρήστης μπορεί να αλλάξει τον ιδιοκτήτη ενός αρχείου ενώ ο ιδιοκτήτης μπορεί να αλλάξει την ομάδα του αρχείου. Παραδείγματα χρήσης:
Προχωρημένη Διαχείριση Αρχείων Η ομαδοποιημένη διαχείριση αρχείων (scatter / gather I/O) είναι μια μέθοδος για τη διαχείριση εισόδου / εξόδου όπου το σύστημα γράφει σε ένα διάνυσμα buffers παίρνοντας δεδομένα από μια ροή δεδομένων ή διαβάζει σε ένα διάνυσμα buffers από μια ροή δεδομένων. Η ονομασία της μεθόδου προέρχεται από την ομαδοποίηση από / σε ένα διάνυσμα buffers. Η συνάρτηση readv διαβάζει ένα σύνολο τμημάτων (count) από ένα περιγραφέα αρχείου σε ένα σύνολο buffers όπως αυτοί καθορίζονται από την παράμετρο iov. Η συνάρτηση writev εγγράφει το πολύ count τμήματα από τους buffers που καθορίζονται με την παράμετρο iov στο αρχείο που ορίζεται με το περιγραφέα fd. Η συμπεριφορά των δύο συναρτήσεων είναι ακριβώς η ίδια με τις read & write αλλά αφορούν σε ένα πλήθος buffers. Η δομή iovec που χρησιμοποιείται στις παραπάνω συναρτήσεις ορίζεται ως εξής: Ένα σύνολο τμημάτων ονομάζεται διάνυσμα (vector). Κάθε τμήμα περιγράφει τη διεύθυνση και το μήκος ενός buffer στη μνήμη στο / από το οποίο τα δεδομένα θα γραφούν / διαβαστούν. Η readv γεμίζει κάθε buffer με iov_len bytes πριν προχωρήσει στον επόμενο. Η ίδια συμπεριφορά συναντάται και στην writev. Σε περίπτωση επιτυχίας, οι συναρτήσεις επιστρέφουν τον αριθμό των bytes που διαβάστηκαν / γράφτηκαν. Σε περίπτωση σφάλματος και οι δύο συναρτήσεις επιστρέφουν -1. Όμως, άλλα δύο είδη σφαλμάτων ορίζονται ως εξής:
Λόγω του ότι ο τύπος επιστροφής είναι ssize_t, εφόσον το άθροισμα όλων των iov_len είναι μεγαλύτερο από το SSIZE_MAX, δεν θα μεταφερθούν καθόλου δεδομένα, θα επιστραφεί -1 και το errno θα τεθεί ίσο με EINVAL. Το πρότυπο POSIX ορίζει πως το count πρέπει να είναι μεγαλύτερο του 0 και μικρότερο από το IOV_MAX που ορίζεται στη βιβλιοθήκη <limits.h>. Όταν το count είναι μεγαλύτερο από το IOV_MAX, δεν θα μεταφερθούν καθόλου δεδομένα, θα επιστραφεί -1 και το errno θα τεθεί ίσο με EINVAL. Code 2 writev example #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <sys/uio.h> int main ( ) struct iovec iov[3]; ssize_t nr; int fd, i; char *buf[] = "University of Thessaly.\n", "Department of Computer Science.\n", "Lamia, 35100, Greece.\n" ; fd = open ("lab10_test.txt", O_WRONLY O_CREAT O_TRUNC); if (fd == -1) perror ("open"); /* fill out three iovec structures */ for (i = 0; i < 3; i++) iov[i].iov_base = buf[i]; iov[i].iov_len = strlen (buf[i]); /* with a single call, write them all out */ nr = writev (fd, iov, 3); if (nr == -1) perror ("writev"); printf ("wrote %d bytes\n", nr); if (close (fd)) perror ("close"); return 0; Code 3 readv example
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/uio.h> int main ( ) char foo[48], bar[51], baz[49]; struct iovec iov[3]; ssize_t nr; int fd, i; fd = open ("lab_test.txt", O_RDONLY); if (fd == -1) perror ("open"); /* set up our iovec structures */ iov[0].iov_base = foo; iov[0].iov_len = sizeof (foo); iov[1].iov_base = bar; iov[1].iov_len = sizeof (bar); iov[2].iov_base = baz; iov[2].iov_len = sizeof (baz); /* read into the structures with a single call */ nr = readv (fd, iov, 3); if (nr == -1) perror ("readv"); for (i = 0; i < 3; i++) printf ("%d: %s", i, (char *) iov[i].iov_base); if (close (fd)) perror ("close"); return 0; Αντιστοίχιση Αρχείων στη Μνήμη Εναλλακτικά της standard διαχείρισης αρχείων, ο πυρήνας προσφέρει μια διεπαφή που επιτρέπει στην αντιστοίχιση αρχείων στη μνήμη. Μέσω αυτής της διεπαφής, δημιουργείται μια ένα προς ένα αντιστοίχιση μεταξύ των διευθύνσεων της μνήμης και των περιεχομένων των αρχείων οπότε οι προγραμματιστές έχουν τη δυνατότητα να προσπελαύνουν απ ευθείας τα περιεχόμενα των αρχείων στη μνήμη. Η συνάρτηση mmap() καθοδηγεί τον πυρήνα να αντιστοιχίσει len bytes από το αντικείμενο που αναπαριστάται από τον περιγραφητή αρχείου fd ξεκινώντας από το offset bytes στη μνήμη. Αν περιληφθεί και η addr (οι περισσότεροι προγραμματιστές περνούν το 0), θα δείξει μια προτίμηση θέσης μνήμης από την οποία θα ξεκινήσει η αντιστοίχιση. Οι άδειες πρόσβασης αναπαριστώνται από το prot ενώ τα flags υποδηλώνουν επιπρόσθετες συμπεριφορές.
Η παράμετρος prot περιγράφει την επιθυμητή μέθοδο προστασίας της μνήμης. Οι διαθέσιμες επιλογές είναι οι ακόλουθες (σε πολλαπλές επιλογές εφαρμόζεται ο τελεστής OR): PROT_READ: οι σελίδες μπορούν να διαβαστούν PROT_WRITE:οι σελίδες μπορούν να γραφούν PROT_EXEC: οι σελίδες μπορούν να εκτελεστούν PROT_NONE: δεν μπορεί να γίνει καμία ενέργεια πάνω στις σελίδες Τα flags έχουν ως ακολούθως: Παράδειγμα εκτέλεσης:
Τα πιθανά λάθη που μπορεί να επιστρέψει η mmap() έχουν ως ακολούθως: Code 4 mmap() example #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h>
#include <errno.h> int main(int argc, char *argv[]) int fd, offset; char *data; struct stat sbuf; if (argc!= 2) fprintf(stderr, "usage: mmapdemo offset\n"); exit(1); if ((fd = open("lab10_test.txt", O_RDONLY)) == -1) perror("open"); exit(1); if (stat("lab10_test.txt", &sbuf) == -1) perror("stat"); exit(1); offset = atoi(argv[1]); if (offset < 0 offset > sbuf.st_size-1) fprintf(stderr, "mmapdemo: offset must be in the range 0-%d\n", (int)(sbuf.st_size-1)); exit(1); if ((data = mmap((caddr_t)0, sbuf.st_size, PROT_READ, MAP_SHARED, fd, 0)) == (caddr_t)(-1)) perror("mmap"); exit(1); printf("byte at offset %d is '%c'\n", offset, data[offset]); return 0; Η συνάρτηση mmap() λειτουργεί πάνω στις σελίδες όπως αυτές ορίζονται από το Λειτουργικό Σύστημα (ΛΣ). Τα offset και addr θα πρέπει να είναι σύμφωνα με τα χαρακτηριστικά των σελίδων που ορίζει το ΛΣ. Έτσι, θα πρέπει να είναι πολλαπλάσια του μεγέθους σελίδας. Όταν το len δεν είναι ευθυγραμμισμένο με το μέγεθος της σελίδας (π.χ. επειδή το μέγεθος τους αρχείου μπορεί να είναι μεγαλύτερο από το μέγεθος μιας σελίδας), τότε η αντιστοίχιση γίνεται στο αμέσως μεγαλύτερο πολλαπλάσιο μεγέθους σελίδας. Όσα bytes περισσεύουν, γεμίζουν με 0. Για να πάρουμε το μέγεθος της σελίδας μπορούμε να καλέσουμε τη συνάρτηση sysconf. Οι κλήσεις στην sysconf θα επιστρέψουν την τιμή της παραμέτρου name. Παράδειγμα:
όπου το _SC_PAGESIZE (ή _SC_PAGE_SIZE) έχει καθοριστεί από το POSIX να αντιστοιχεί στο μέγεθος σελίδας (σε bytes). Το μέγεθος σελίδας μπορούμε επίσης να το πάρουμε μέσω της συνάρτησης getpagesize. Παράδειγμα: Τελευταία επιλογή για να προσπελάσουμε το μέγεθος της σελίδας είναι να χρησιμοποιήσουμε την μακροεντολή PAGE_SIZE που ορίζεται στη βιβλιοθήκη <asm/page.h>. Παράδειγμα: Code 5 An example for getting the page size #include <stdio.h> #include <unistd.h> int main() printf("%i \n", getpagesize()); return 0; Διαγραφή Αντιστοίχισης Αρχείων στη Μνήμη Για τη διαγραφή των αντιστοιχίσεων αρχείων στη μνήμη, χρησιμοποιείται η συνάρτηση munmap(). Η συνάρτηση διαγράφει οποιαδήποτε αντιστοίχιση σε σελίδες όπως αυτές εμφανίζονται στο χώρο διευθύνσεων της διεργασίας που ξεκινούν από το addr και συνεχίζουν για len bytes. Σε περίπτωση επιτυχίας, η συνάρτηση επιστρέφει το 0 ενώ σε αποτυχία επιστρέφεται το 1. Code 6 A complete example of mmap and munmap #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> int main (int argc, char *argv[]) struct stat sb; off_t len; char *p;
int fd; if (argc < 2) fprintf (stderr, "usage: %s <file>\n", argv[0]); fd = open (argv[1], O_RDONLY); if (fd == -1) perror ("open"); if (fstat (fd, &sb) == -1) perror ("fstat"); if (!S_ISREG (sb.st_mode)) fprintf (stderr, "%s is not a file\n", argv[1]); p = mmap (0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) perror ("mmap"); if (close (fd) == -1) perror ("close"); for (len = 0; len < sb.st_size; len++) putchar (p[len]); if (munmap (p, sb.st_size) == -1) perror ("munmap"); return 0; Αλλαγή Αντιστοίχισης Αρχείων στη Μνήμη Μπορούμε να αλλάξουμε το μέγεθος μιας αντιστοίχισης στη μνήμη μέσω της συνάρτησης mremap() (η συνάρτηση είναι Linux-specific). Μέσω της συνάρτησης μπορούμε να αυξήσουμε ή να μειώσουμε το μέγεθος της αντιστοίχισης. Αλλαγή Προστασίας Αντιστοίχισης στη Μνήμη Η συνάρτηση mprotect() μπορεί να χρησιμοποιηθεί ώστε να αλλάξουμε την προστασία μιας αντιστοίχισης στη μνήμη.
Η κλήση της συνάρτησης θα προκαλέσει αλλαγή της προστασίας για την περιοχή [addr, addr+len]. Το prot αποτελεί ένα σύνολο παραμέτρων όπως ακριβώς και στην mmap(): PRTO_NONE, PROT_READ, PROT_WRITE, PROT_EXEC. Code 7 mprotect example #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> static int alloc_size; static char* memory; void segv_handler (int signal_number) printf ("memory accessed!\n"); mprotect (memory, alloc_size, PROT_READ PROT_WRITE); int main () int fd; struct sigaction sa; /* Install segv_handler as the handler for SIGSEGV. */ memset (&sa, 0, sizeof (sa)); sa.sa_handler = &segv_handler; sigaction (SIGSEGV, &sa, NULL); /* Allocate one page of memory by mapping /dev/zero. Map the memory as write-only, initially. */ alloc_size = getpagesize (); fd = open ("/dev/zero", O_RDONLY); memory = mmap (NULL, alloc_size, PROT_WRITE, MAP_PRIVATE, fd, 0); close (fd); /* Write to the page to obtain a private copy. */ memory[0] = 0; /* Make the memory unwritable. */ mprotect (memory, alloc_size, PROT_NONE); /* Write to the allocated memory region. */ memory[0] = 1;
/* All done; unmap the memory. */ printf ("all done\n"); munmap (memory, alloc_size); return 0; Συγχρονισμός Αρχείων με Αντιστοιχίσεις Μνήμης Η συνάρτηση msync χρησιμοποιείται για να συγχρονίσουμε τα περιεχόμενα ενός αρχείου με μια αντιστοίχιση στη μνήμη. Η κλήση στη συνάρτηση προκαλεί flushing στο δίσκο όταν προκύπτουν αλλαγές σε ένα αρχείο που έχει αντιστοιχηθεί με την mmap. Με αυτό τον τρόπο επιτυγχάνεται συγχρονισμός μεταξύ της μνήμης και του δίσκου. Χωρίς κάποιο συγχρονισμό, δεν είναι εξασφαλισμένο πως οι αλλαγές στη μνήμη θα περάσουν και στο δίσκο. Τα flags ως ακολούθως: Από αυτά τα flags, τα MS_ASYNC και MS_SYNC δεν μπορούν να οριστούν ταυτόχρονα. Το ακόλουθο τμήμα κώδικα είναι ένα παράδειγμα χρήσης της msync. Code 8 msync example #include <unistd.h> #include <stdio.h> #include <sys/mman.h> #include <fcntl.h> #include <stdlib.h> typedef struct int integer; char string[24]; RECORD; #define NRECORDS (100) int main()
RECORD record, *mapped; int i, f; FILE *fp; fp = fopen("records.dat","w+"); for(i=0; i<nrecords; i++) record.integer = i; sprintf(record.string,"record-%d",i); fwrite(&record,sizeof(record),1,fp); fclose(fp); fp = fopen("records.dat","r+"); fseek(fp,43*sizeof(record),seek_set); fread(&record,sizeof(record),1,fp); record.integer = 143; sprintf(record.string,"record-%d",record.integer); fseek(fp,43*sizeof(record),seek_set); fwrite(&record,sizeof(record),1,fp); fclose(fp); f = open("records.dat",o_rdwr); mapped = (RECORD *)mmap(0, NRECORDS*sizeof(record),PROT_READ PROT_WRITE, MAP_SHARED, f, 0); mapped[43].integer = 243; sprintf(mapped[43].string,"record-%d",mapped[43].integer); msync((void *)mapped, NRECORDS*sizeof(record), MS_ASYNC); munmap((void *)mapped, NRECORDS*sizeof(record)); close(f); exit(0); Διαχείριση Αρχείων Σε πολλές περιπτώσεις χρειάζεται να προσπελάσουμε αρχεία με βάση κάποια χαρακτηριστικά τους ώστε να γίνει καλύτερη η διαχείρισή τους. Με το στόχο της πιο φιλικής αναζήτησης, μπορούμε να υιοθετήσουμε μια από τις επόμενες επιλογές: Ταξινόμηση με βάση τη διαδρομή Ταξινόμηση με βάση τον αριθμό inode. Τα inodes περιλαμβάνουν μεταδεδομένα των αρχείων και μας επιτρέπουν να προσπελάσουμε πληροφορίες για: το μέγεθος του αρχείου, τις άδειες προσπέλασης, τον ιδιοκτήτη, κ.λπ. Όπως γίνεται αντιληπτό, η ταξινόμηση με βάση το inode είναι πολύ πιο εύκολη αφού αυτή αφορά ακέραιους αριθμούς. Code 9 sort by inode example #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> /* * get_inode - returns the inode of the file associated * with the given file descriptor, or -1 on failure */
int get_inode (int fd) struct stat buf; int ret; ret = fstat (fd, &buf); if (ret < 0) perror ("fstat"); return -1; return buf.st_ino; int main (int argc, char *argv[]) int fd, inode; if (argc < 2) fprintf (stderr, "usage: %s <file>\n", argv[0]); fd = open (argv[1], O_RDONLY); if (fd < 0) perror ("open"); inode = get_inode (fd); printf ("%d\n", inode); return 0; Ταξινόμηση με βάση τα φυσικά blocks του δίσκου. Πρόκειται ίσως για την καλύτερη προσέγγιση. Κάθε αρχείο έχει χωριστεί σε ένα σύνολο λογικών blocks που αντιστοιχούν στις μικρότερες αποθηκευτικές μονάδες ενός αρχείου. Το μέγεθος κάθε block εξαρτάται από το σύστημα αρχείων που υιοθετεί το ΛΣ. Η συνάρτηση ioctl() μας δίνει το φυσικό block από ένα αριθμό λογικού block ενός αρχείου. Code 10 sort by physical blocks example #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <linux/fs.h> /* * get_block - for the file associated with the given fd, returns * the physical block mapping to logical_block */ int get_block (int fd, int logical_block) int ret; ret = ioctl (fd, FIBMAP, &logical_block);
if (ret < 0) perror ("ioctl"); return -1; return logical_block; /* * get_nr_blocks - returns the number of logical blocks * consumed by the file associated with fd */ int get_nr_blocks (int fd) struct stat buf; int ret; ret = fstat (fd, &buf); if (ret < 0) perror ("fstat"); return -1; return buf.st_blocks; /* * print_blocks - for each logical block consumed by the file * associated with fd, prints to standard out the tuple * "(logical block, physical block)" */ void print_blocks (int fd) int nr_blocks, i; nr_blocks = get_nr_blocks (fd); if (nr_blocks < 0) fprintf (stderr, "get_nr_blocks failed!\n"); return; if (nr_blocks == 0) printf ("no allocated blocks\n"); return; else if (nr_blocks == 1) printf ("1 block\n\n"); else printf ("%d blocks\n\n", nr_blocks); for (i = 0; i < nr_blocks; i++) int phys_block; phys_block = get_block (fd, i); if (phys_block < 0) fprintf (stderr, "get_block failed!\n"); return; if (!phys_block) continue; printf ("(%u, %u) ", i, phys_block); putchar ('\n');
int main (int argc, char *argv[]) int fd; if (argc < 2) fprintf (stderr, "usage: %s <file>\n", argv[0]); fd = open (argv[1], O_RDONLY); if (fd < 0) perror ("open"); print_blocks (fd); return 0;