3. Διαμοιραζόμενες (shared) βιβλιοθήκες

Οι διαμοιραζόμενες βιβλιοθήκες είναι βιβλιοθήκες που φορτώνονται από τα προγράμματα όταν αρχίζουν. Όταν μια διαμοιραζόμενη βιβλιοθήκη εγκατασταθεί κατάλληλα, όλα τα προγράμματα που αρχίζουν κατόπιν, κάνουν αυτόματα χρήση της νέας διαμοιραζόμενης βιβλιοθήκης. Στην πραγματικά η παραπάνω περιγραφή είναι σχετικά απλή και στατική, αφού η προσέγγιση που χρησιμοποιείται από το Linux σας επιτρέπει:

3.1. Συμβάσεις

Για να μπορούν οι διαμοιραζόμενες βιβλιοθήκες να υποστηρίξουν όλες αυτές τις επιθυμητές ιδιότητες, πρέπει να γίνουν διάφορες συμβάσεις και να ακολουθηθούν κάποιες οδηγίες. Πρέπει να καταλάβετε τη διαφορά μεταξύ του ονομάτων μιας βιβλιοθήκης, και συγκεκριμένα αυτού που λέγεται το "soname" της, και του πραγματικού ονόματος της (real name), και πώς αλληλεπιδρούν. Πρέπει επίσης να καταλάβετε που πρέπει να τοποθετηθούν στο σύστημα αρχείων.

3.1.1. Ονόματα διαμοιραζόμενων βιβλιοθηκών

Κάθε διαμοιραζόμενη βιβλιοθήκη έχει ένα ειδικό όνομα, το λεγόμενο "soname". Το soname ξεκινά με το πρόθεμα "lib", ακολουθεί το όνομα της βιβλιοθήκης, η φράση ".so" και τέλος μια ακόμα περίοδος ( τελεία - ".") ακολουθούμενη από έναν αριθμό έκδοσης που αυξάνεται όποτε το interface της βιβλιοθήκης αλλάζει (ως ειδική εξαίρεση, οι χαμηλότερου επιπέδου βιβλιοθήκες της C δεν αρχίζουν με το πρόθεμα "lib"). Ένα πλήρες και κατάλληλο ( (fully-qualified) soname περιλαμβάνει ως πρόθεμα τον κατάλογο αρχείων στον οποίον βρίσκεται μέσα στο σύστημα αρχείων - σε ένα σύστημα που λειτουργεί, ένα πλήρως-κατάλληλο soname είναι απλά ένας συμβολικός σύνδεσμος (symbolic link) που δείχνει στο "πραγματικό όνομα" της διαμοιραζόμενης βιβλιοθήκης.

Κάθε διαμοιραζόμενη βιβλιοθήκη έχει επίσης ένα "πραγματικό όνομα", το οποίο είναι το όνομα αρχείου που περιέχει τον πραγματικό κώδικα βιβλιοθήκης. Το πραγματικό όνομα προσθέτει στο soname μια επιπλέον περίοδο, ένα δευτερεύων αριθμό (minor number), μια ακόμα περίοδο, και τον αριθμός έκδοσης (release number). Η τελευταία περίοδος και ο αριθμός έκδοσης είναι προαιρετικό επίθεμα. Ο minor και ο release αριθμός επιτρέπουν τον έλεγχο της διαμόρφωσης (configuration control ), ενημερώνοντας σας ακριβώς ποια έκδοση ή ποιες εκδόσεις της βιβλιοθήκης έχουν εγκατασταθεί. Σημειώστε ότι αυτοί οι αριθμοί μπορεί να μην είναι οι ίδιοι με τους αριθμούς που χρησιμοποιήθηκαν για να περιγράψουν τη βιβλιοθήκη στην τεκμηρίωση, αν και κάτι τέτοιο καθιστά τα πράγματα ευκολότερα.

Επιπλέον, υπάρχει το όνομα που ο μεταγλωττιστής χρησιμοποιεί κατά τον αίτηση μιας βιβλιοθήκης, (Θα το αποκαλώ "linker name"), που είναι απλά το soname χωρίς οποιοδήποτε αριθμό έκδοσης.

Το κλειδί για την κατανόηση και διαχείριση των διαμοιραζόμενων βιβλιοθηκών είναι ο διαχωρισμός αυτών των ονομάτων. Τα προγράμματα, όταν φτιάχνουν εσωτερικά μια λίστα με τις διαμοιραζόμενες βιβλιοθήκες που χρειάζονται, πρέπει να εμφανίσουν στη λίστα μόνο τα αντίστοιχα soname. Αντιθέτως, όταν δημιουργείτε μια κοινή βιβλιοθήκη, δημιουργείτε τη βιβλιοθήκη μόνο με ένα συγκεκριμένο όνομα αρχείου (με λεπτομερείς πληροφορίες έκδοσης). Όταν εγκαθιστάτε μια νέα έκδοση μιας βιβλιοθήκης, την τοποθετείτε σε έναν από μερικούς ειδικούς καταλόγους αρχείων και τρέχετε έπειτα το πρόγραμμα ldconfig(8). Το ldconfig εξετάζει τα υπάρχοντα αρχεία και δημιουργεί τα sonames ως συμβολικές συνδέσεις στα πραγματικά ονόματα, καθώς επίσης δημιουργεί ή ανανεώνει το αρχείο cache /etc/ld.so.cache (που θα περιγραφεί σε λίγο).

Το ldconfig δεν οργανώνει τα linker names ( για το οποία μιλήσαμε παραπάνω) - τυπικά αυτό γίνεται κατά τη διάρκεια της εγκατάστασης βιβλιοθηκών, και το linker name δημιουργείται απλά ως ένα symbolic link στο "πιο πρόσφατο" soname ή το πιο πρόσφατο πραγματικό όνομα. Θα σύστηνα να έχετε το linker name σαν ένα συμβολικό σύνδεσμο στο soname, αφού στις περισσότερες περιπτώσεις εάν ανανεώσετε τη βιβλιοθήκη θα επιθυμείτε να τη χρησιμοποιήσετε αυτόματα κατά τη σύνδεση (linking). Ρώτησα τον H. J. Lu γιατί το ldconfig δεν οργανώνει αυτόματα τα linker names. Η εξήγησή του ήταν βασικά ότι ίσως να θελήσετε να τρέξετε κώδικα χρησιμοποιώντας την πιο πρόσφατη έκδοση μιας βιβλιοθήκης, αλλά αντ' αυτού να θελήσετε κατ την ανάπτυξη να συνδέσετε το πρόγραμμα σας με μια παλαιά (ενδεχομένως ασυμβίβαστη) βιβλιοθήκη. Επομένως, αφού το ldconfig δεν κάνει καμία υπόθεση για το με ποια βιβλιοθήκη θέλετε να συνδέσετε τα προγράμματα σας, κατά την εγκατάσταση θα πρέπει να γίνουν συμβολικές συνδέσεις για να ενημερωθεί ο linker σχετικά με το ποία βιβλιοθήκη θα χρησιμοποιήσει.

Κατά συνέπεια, το /usr/lib/libreadline.so.3 είναι ένα πλήρως κατάλληλο soname, το οποίο το ldconfig θα έθετε σαν ένα συμβολικό σύνδεσμο σε κάποιο πραγματικό όνομα (realname) όπως το /usr/lib/libreadline.so.3.0. Πρέπει επίσης να υπάρξει ένα όνομα για τον linker, /usr/lib/libreadline.so το οποίο θα μπορούσε να είναι ένας συμβολικός σύνδεσμος που δείχνει στo /usr/lib/libreadline.so.3.

3.1.2. Τοποθέτηση στο Σύστημα Αρχείων

Οι διαμοιραζόμενες βιβλιοθήκες πρέπει να τοποθετηθούν κάπου στο σύστημα αρχείων (filesystem). Το ανοικτού κώδικα λογισμικό (open source software) τείνει να ακολουθεί τα πρότυπα του GNU - για περισσότερες πληροφορίες δείτε τη σχετική τεκμηρίωση στο info:standards#Directory_Variables. Τα πρότυπα του GNU συστήνουν, σαν προκαθορισμένη επιλογή, την εγκατάσταση όλων των βιβλιοθηκών στον κατάλογο /usr/local/lib όταν διανέμεται ο πηγαίος κώδικας (και όλες οι εντολές πρέπει να μπουν στο /usr/local/bin). Καθορίζουν επίσης έναν συμφωνημένο τρόπο για παράκαμψη αυτών των προκαθορισμένων επιλογών και για την κλήση των ρουτινών εγκατάστασης.

Τα Πρότυπα Ιεραρχίας Συστήματος Αρχείων (Filesystem Ierarchy Standards - FHS) συζητούν το τι πρέπει να πάει που σε μια διανομή (βλ. http://www.pathname.com/fhs). Σύμφωνα με το FHS, οι περισσότερες βιβλιοθήκες πρέπει να είναι εγκατεστημένες στο /usr/lib, αλλά οι βιβλιοθήκες που απαιτούνται για το ξεκίνημα του συστήματος θα πρέπει να είναι στο /lib και οι βιβλιοθήκες που δεν είναι μέρος του συστήματος πρέπει να εγκατασταθούν στο /usr/local/lib.

Δεν υπάρχει πραγματικά κάποια σύγκρουση μεταξύ αυτών των δύο εγγράφων - τα πρότυπα του GNU συστήνουν τις προκαθορισμένες επιλογές για τους υπεύθυνους για την ανάπτυξη του πηγαίου κώδικα, ενώ το FHS αντίστοιχα για τους διανομείς (που επιλεκτικά υπερπηδούν τις προκαθορισμένες επιλογές για τον πηγαίο κώδικα, συνήθως μέσω του συστήματος διαχείρισης πακέτων του συστήματος). Στην πράξη αυτό δουλεύει πολύ όμορφα: η πλέον πρόσφατη (ενδεχομένως με λάθη - bugs) έκδοση του πηγαίου κώδικα που "κατεβάζετε", αυτόματα εγκαθίσταται στον "local" (τοπικό) κατάλογο αρχείων (/usr/local), και μόλις "ωριμάσει" εκείνος ο κώδικας, οι διαχειριστές πακέτων της διανομής (package managers) μπορούν εύκολα να παρακάμψουν τις προκαθορισμένες επιλογές για να τοποθετήσουν τον κώδικα στην πρότυπη θέση για τη διανομή. Σημειώστε ότι εάν η βιβλιοθήκη σας καλεί προγράμματα που μπορούν να κληθούν μόνον μέσω βιβλιοθηκών, πρέπει να τοποθετήσετε εκείνά τα προγράμματα στον φάκελο /usr/local/libexec (που σε μια διανομή μετατρέπεται σε /usr/libexec). Μια περιπλοκή είναι ότι τα Red-Hat-"οειδή" συστήματα δεν περιλάβουν τον κατάλογο /usr/local/lib "εξ' ορισμού" στην αναζήτησή τους για βιβλιοθήκες - δείτε σχετικά τη συζήτηση για το /etc/ld.so.conf παρακάτω. Άλλες πρότυπες (standard) θέσεις βιβλιοθηκών περιλαμβάνουν τον κατάλογο /usr/X11R6/lib για τα Χ-Windows, κ.τ.λ.. Σημειώστε ότι το /lib/security χρησιμοποιείται για τα PAM modules, αλλά αυτά φορτώνονται συνήθως ως δυναμικές βιβλιοθήκες (που συζητούνται επίσης κατωτέρω).

3.2. Πως χρησιμοποιούνται οι βιβλιοθήκες

Στα GNU συστήματα που βασίζονται στη glibc, συμπεριλαμβανομένων όλων των συστημάτων Linux, το ξεκίνημα ενός ELF εκτελέσιμου αναγκάζει αυτόματα τον program loader (φορτωτή προγράμματος) να φορτωθεί και να τρέξει. Στα συστήματα Linux, αυτός ο φορτωτής ονομάζεται /lib/ld-linux.so.X (όπου το Χ είναι ένας αριθμός έκδοσης). Αυτός ο φορτωτής, με τη σειρά του, βρίσκει και φορτώνει όλες τις άλλες διαμοιραζόμενες βιβλιοθήκες που χρησιμοποιούνται από το πρόγραμμα.

Η λίστα των καταλόγων αρχείων που ελέγχονται είναι αποθηκευμένη στο αρχείο /etc/ld.so.conf. Πολλές διανομές που έχουν σαν "πηγή" τους RedHat συστήματα, κατά κανόνα, δεν συμπεριλαμβάνουν τον κατάλογο /usr/local/lib στο αρχείο /etc/ld.so.conf. Το τελευταίο το θεωρώ bug (σφάλμα), και η προσθήκη του /usr/local/lib στο /etc/ld.so.conf είναι μια συνήθης "επιδιόρθωση" απαραίτητη για την εκτέλεση πολλών προγραμμάτων σε RedHat-derived συστήματα. Εάν θέλετε απλά να παρακάμψετε μερικές λειτουργίες σε μια βιβλιοθήκη, αλλά να κρατήσετε το υπόλοιπο της βιβλιοθήκης, μπορείτε να εισαγάγετε τα ονόματα των βιβλιοθηκών που θα αντικαταστήσουν τις αρχικές στο αρχείο /etc/ld.so.conf - αυτές οι βιβλιοθήκες θα έχουν προτεραιότητα έναντι αυτών του πρότυπου συνόλου (standard set), αφού φορτώνουν εκ των προτέρων (preloading). Αυτή η μέθοδος βέβαια, μέσω, χρησιμοποιείται κυρίως για "μπαλώματα" (patches) έκτακτης ανάγκης - μια διανομή σπάνια θα περιλάβει ένα τέτοιο αρχείο σε μια σταθερή έκδοση.

Εάμ θέλετε απλά να παρακάμψετε μερικές συναρτήσεις στη βιβλιοθήκη, μα να κρατήσετε την υπόλοιπη βιβλιοθήκη, μπορείτε να εισάγετε τα ονόματα των παρακαμπτήριων βιβλιοθηκών (.o αρχεία) στο /etc/ld.so.preload; αυτές οι βιβλιοθήκες "προ-φόρτωσης" θα λάβουν προτεραίοτητα έναντι αυτών του βασικού συνόλου. Αυτό το αρχείο προφόρτωσης χρησιμοποείται κατά κανόνα για έκτακτα patches - μια διανομή συνήθως δε θα συμπεριλάβει ένα τέτοιο αρχείο κατά τη δίαθεση της.

Η έρευνα όλων αυτών των καταλόγων αρχείων στο ξεκίνημα κάθε προγράμματος θα ήταν συνολικά μη λειτουργική, έτσι στην πραγματικότητα χρησιμοποιείται μια μέθοδος caching. Το πρόγραμμα ldconfig (8) εξ ορισμού, διαβάζει το αρχείο /etc/ld.conf, οργανώνει (sets up) τους κατάλληλους συμβολικούς συνδέσμους στους καταλόγους αρχείων με τους δυναμικούς συνδέσμους (dynamic link directoriew), έτσι ώστε να ακολουθούν τις πρότυπες συμβάσεις (standard conventions), και κρατά τις απαραίτητες πληροφορίες "συνοπτικά" στο /etc/ld.so.cache, που μπορεί να χρησιμοποιηθεί στη συνέχεια από άλλα προγράμματα. Αυτό επιταχύνει πολύ την πρόσβαση στις βιβλιοθήκες. Βέβαια η παραπάνω τακτική παρουσιάζει και μια "επιπλοκή" : το μειονέκτημα να είναι απαραίτητη η εκτέλεση του ldconfig κάθε φορά που ένα DLL προστίθεται, αφαιρείται, ή όταν το σύνολο καταλόγων αρχείων DLL αλλάζει - το τρέξιμο του ldconfig είναι συχνά ένα από τα βήματα που εκτελούνται από τους διαχειριστές πακέτων κατά την εγκατάσταση μιας βιβλιοθήκης. Κατόπιν, στο ξεκίνημα, ο dynamic loader χρησιμοποιεί πράγματι το αρχείο /etc/ld.so.cache και φορτώνει έπειτα τις βιβλιοθήκες που χρειάζεται.

Παρεμπιπτόντως, το FreeBSD χρησιμοποιεί ελαφρώς διαφορετικά ονόματα αρχείου για αυτήν την cache. Σε FreeBSD, η ELF cache είναι το αρχείο /var/ld- elf.so.hints και η a.out cache το /var/run/ld.so.hints. Αυτά ενημερώνονται πάντα από το ldconfig (8), έτσι αυτή η διαφορά στη θέση πρέπει μόνο να πειράξει σε μερικές εξαιρετικές καταστάσεις.

3.3. Μεταβλητές περιβάλλοντος

Διάφορες μεταβλητές περιβάλλοντος μεταβλητών περιβάλλοντος μπορούν να ελέγξουν αυτήν την διαδικασία, ενώ υπάρχουν και μεταβλητές περιβάλλοντος που σας επιτρέπουν την παρακάμψετε.

3.3.1. LD_LIBRARY_PATH

Μπορείτε προσωρινά να αντικαταστήσετε μια βιβλιοθήκη με κάποια άλλη, για μια συγκεκριμένη εκτέλεση. Στο Linux, η μεταβλητή περιβάλλοντος LD_LIBRARY_PATH περιέχει μια σειρά από καταλόγους αρχείων, χωρισμένα μεταξύ τους με άνω και κάτω τελεία, όπου πρέπει να αναζητηθούν πρώτα οι βιβλιοθήκες , πριν αναζητηθούν στο πρότυπο (default) σύνολο καταλόγων αρχείων. Αυτό είναι χρήσιμο κατά την αποσφαλμάτωση (debugging) μιας νέας βιβλιοθήκης ή τη χρησιμοποίηση της "νέας" βιβλιοθήκης για ασυνήθιστους λόγους. Η μεταβλητή περιβάλλοντος LD_PRELOAD περιέχει μια λίστα με βιβλιοθήκες που έχουν λειτουργίες που "υπερισχύουν" αυτών του πρότυπο συνόλου, ακριβώς όπως γίνεται με το /etc/ld.so.preload. Αυτές οι μεταβλητές τίθενται από τον /lib/ld-linux.so loader. Πρέπει να σημειώσω εδώ πως, ενώ η LD_LIBRARY_PATH δουλεύει για πολλά συστήματα Unix, δε λειτουργεί σε όλα - παραδείγματος χάριν, η ίδια λειτουργία είναι διαθέσιμη σε HP- UX αλλά η μεταβλητή περιβάλλοντος είναι αντίστοιχα η SHLIB_PATH, και στο ΑΙΧ, αυτή η λειτουργικότητα παρέχεται μέσω της μεταβλητής περιβάλλοντος LIBPATH (με την ίδια ακριβώς σύνταξη, μια λίστα από ονόματα καταλόγων αρχείων χωρισμένα με άνω και κάτω τελεία).

Η LD_LIBRARY_PATH είναι πρακτική για την ανάπτυξη και δοκιμή κώδικα, αλλά δε θα έπρέπε να τροποποιηθεί ποτέ κατά τη διαδικασία εγκατάστασης ενός προγράμματος για συνήθη χρήση από τους απλούς χρήστες - μπορείτε να δείτε περισσότερα σχετικά με το γιατί στο "Why LD_LIBRARY_PATH is bad", στο http://www.visi.com/~barr/ldpath.html Ωστόσο παραμένει χρήσιμη για την ανάπτυξη ή δοκιμή κώδικα, και για το ψάξιμο λύσεων πάνω σε προβλήματα που δεν μπορούν εύκολα να επιλυθούν ειδάλλως. Εάν δεν θέλετε να θέσετε τη μεταβλητή περιβάλλοντος LD_LIBRARY_PATH, στο Linux μπορείτε ακόμη και να καλέσετε το φορτωτή προγράμματος (program loader) άμεσα και να του περάσετε ορίσματα. Για παράδειγμα, με την ακόλουθη κλήση θα χρησιμοποιηθεί το ΜΟΝΟΠΑΤΙ που δίνεται σαν όρισμα αντί του περιεχομένου της μεταβλητής περιβάλλοντος LD_LIBRARY_PATH, για να τρέξουμε το συγκεκριμένο εκτελέσιμο:
  /lib/ld-linux.so.2 --library-path PATH EXECUTABLE
Εκτελώντας απλά το ld-linux.so χωρίς ορίσματα, θα πάρετε οδηγίες για τη χρηση του, αλλά και πάλι, μη χρησιμοποιείτε αυτή τη μέθοδο για τη συνήθη χρήση - τέτοιες ευκολίες έχουν επινοηθεί κυρίως για την αποσφαλμάτωση προγραμμάτων.

3.3.2. LD_DEBUG

Μια άλλη χρήσιμη μεταβλητή περιβάλλοντος στον GNU C loader είναι η LD_DEBUG. Αυτή "πειράζει" τις λειτουργίες dl ώστε να δίνουν μπόλικες προφορίες για αυτό που κάνουν. Παραδείγματος χάριν με το:
  export LD_DEBUG=files
  command_to_run
παρουσιάζεται η επεξεργασία αρχείων και βιβλιοθηκών κατά το χειρισμό των βιβλιοθηκών, λέγοντας σας ποιες εξαρτήσεις (dependencies) ανιχνεύονται, ποίες SOs φορτώνονται και με ποια σειρά. Η θέση της LD_DEBUG στη τιμή "bindings", έχει σαν αποτέλεσμα την προβολή πληροφοριών για το συσχετισμό συμβόλων (symbol binding), στην τιμή "libs" την προβολή των μονοπατιών όπου γίνεται αναζήτηση βιβλιοθηκών (library search paths), και στην τιμή "versions", την προβολή των εξαρτήσεων της έκδοσης (version dependencies).

Αν θέσει κανείς στη μεταβλητή LD_DEBUG την τιμή "help" και προσπαθήσει να τρέξει κάποιο πρόγραμμα, θα πάρει μια λίστα με της πιθανές επιλογές. Ισχύει πάντα βέβαια, πως και η LD_DEBUG δεν προορίζεται για κανονική χρήση, αλλά μπορεί να φανεί χρήσιμη σε περιπτώσεις debugging και testing.

3.3.3. Άλλες μεταβλητές περιβάλλοντος

Υπάρχουν στην πραγματικότητα ένας αριθμός από κάποιες μεταβλητές περιβάλλοντος ακόμα, που ελέγχουν τη διαδικασία φόρτωσης (loading process) - τα ονόματά τους αρχίζουν με το LD _ ή RTLD _. Οι περισσότερες από τις υπόλοιπες είναι για χαμηλού επιπέδου debugging της διεργασίας φόρτωσης (loader process) ή για την εφαρμογή προχωρημένων δυνατοτήτων . Οι περισσότερες απ' αυτές δεν είναι καλά τεκμηριωμένες- εάν πρέπει ντε και καλά να βρείτε πληροφορίες γι' αυτές, ο καλύτερος τρόπος να μάθετε είναι να διαβάσετε τον πηγαίο κώδικα του loader (μέρος του GCC).

Το να επιτρέπεται στον χρήστη να έχει έλεγχο πάνω στις δυναμικά συνδεμένες βιβλιοθήκες (DLLs) θα ήταν καταστροφικό για τα setuid/setgid προγράμματα (δηλαδή εκείνα που έχουν τα αντίστοιχα permissions set), εάν δεν λαμβάνονταν ειδικά μέτρα. Επομένως, στον GNU loader (που φορτώνει το υπόλοιπο του προγράμματος κατά την εκκίνηση του), εάν το πρόγραμμα είναι setuid ή setgid, αυτές οι μεταβλητές (και άλλες παρόμοιες μεταβλητές) αγνοούνται ή περιορίζονται πολύ σε αυτό που μπορούν να κάνουν. Ο loader καθορίζει εάν ένα πρόγραμμα είναι setuid ή setgid με τον έλεγχο των credentials ("πιστοποιητικών") του προγράμματος - εάν τα uid και euid διαφέρουν, ή τα gid και egid διαφέρουν, ο φορτωτής θεωρεί ότι το πρόγραμμα είναι setuid/setgid (ή έχει προκύψει από κάποιο που ήταν) και επομένως περιορίζει πολύ τις δυνατότητές του να ελέγξει τη διαδικασία σύνδεσης (linking). Εάν διαβάσετε τον πηγαίο των GNU glibc βιβλιοθηκών, μπορείτε να το διαπιστώσετε - δείτε ειδικά τα αρχεία /elf/rtld.c και sysdeps/generic/dl-sysdep.c. Αυτό σημαίνει ότι εάν αναγκάσετε το uid και το gid να ταυτίζονται με τα euid και egid, και καλέσετε έπειτα ένα πρόγραμμα, αυτές οι μεταβλητές θα έχουν την πλήρη επίδραση τους. Άλλα Unix-οειδή συστήματα χειρίζονται την παραπάνω κατάσταση διαφορετικά αλλά για τον ίδιο λόγο: ένα setuid/setgid πρόγραμμα δεν πρέπει να επηρεαστεί αδικαιολόγητα από τις μεταβλητές περιβάλλοντος που έχουν τεθεί.

3.4. Δημιουργώντας μιας διαμοιραζόμενη βιβλιοθήκη

Η δημιουργία μιας διαμοιραζόμενης βιβλιοθήκης είναι εύκολη υπόθεση. Κατ' αρχάς, δημιουργήστε τα object files που θα πάνε στη διαμοιραζόμενη βιβλιοθήκη χρησιμοποιώντας το GCC με τις παραμέτρους (flags) -fPIC ή -fpic. Οι προαιρετικές παράμετροι -fPIC και -fpic ενεργοποιούν τη δημιουργία "ανεξαρτήτου θέσεως κώδικα" (position independent code), που είναι μια βασική απαίτηση για τις διαμοιραζόμενες βιβλιοθήκες - οι διαφορές αναλύονται παρακάτω. Το soname το περνάτε με την προαιρετική παράμετρο -Wl του gcc. Η παράμετρος -Wl περνάει περνά τις επιλογές που ακολουθούν στον linker (σε αυτήν την περίπτωση την παράμετρο -soname με το όρισμα της) - τα κόμματα μετά από το -Wl δεν είναι τυπικά, ούτε τυπογραφικό λάθος, όπως επίσης προσέξτε να μην συμπεριλάβετε κενά (white spaces) στο όρισμα. Κατόπιν δημιουργήστε την κοινή βιβλιοθήκη χρησιμοποιώντας μια εντολή του παρακάτω τύπου:

gcc -shared -Wl,-soname,your_soname \
    -o library_name file_list library_list

Ακολουθεί ένα παράδειγμα, όπου δημιουργούνται δύο object files (a.o και b.o) και έπειτα μια κοινή βιβλιοθήκη που περιέχει και τα δύο. Σημειώστε ότι αυτή η μεταγλώττιση περιλαμβάνει τις πληροφορίες debugging (-g - θα μπορούσε να είναι -ggdb για να συμπεριληφθούν επιπλέον debugging πληροφορίες για χρήση με τον gdb) και θα παραγάγει και προειδοποιήσεις (warnings - προαιρετική παράμετρος - Wall), που δεν απαιτούνται, φυσικά, για τις κοινές βιβλιοθήκες αλλά συστήνονται. Η μεταγλώττιση (compilation) παράγει τα object filesυ (χρησιμοποιείται η παράμετρος -c), και περιλαμβάνει και την απαιτούμενη επιλογή fPIC:

gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c
gcc -shared -Wl,-soname,libmystuff.so.1 \
    -o libmystuff.so.1.0.1 a.o b.o -lc

Μερικά σημεία άξια περισσότερης προσοχής:

Κατά τη διάρκεια της ανάπτυξης κώδικα, υπάρχει το πιθανό πρόβλημα της τροποποίησης μιας βιβλιοθήκης που χρησιμοποιείται επίσης κι από πολλά άλλα προγράμματα - και εσείς δεν θα θέλατε για να χρησιμοποιήσουν τη βιβλιοθήκη που βρίσκεται υπό ανάπτυξη τα άλλα προγράμματα, παρά μόνο μια συγκεκριμένη εφαρμογή που εξετάζετε ως προς τη συμπεριφορά της απέναντι στη νέα βιβλιοθήκη. Μια προαιρετική δυνατότητα για τη σύνδεση (linking option) που μπορείτε να χρησιμοποιήσετε είναι η "rpath" του ld, που προσδιορίζει το μονοπάτι αναζήτησης runtime (χρόνου εκτέλεσης) βιβλιοθηκών του του συγκεκριμένου εκείνου προγράμματος που μεταγλωττίζεται. Από το gcc, μπορείτε να θέσετε την επιλογή rpath με τον παρακάτω τρόπο :
 -Wl,-rpath,$(DEFAULT_LIB_INSTALL_PATH)
Εάν χρησιμοποιείτε αυτήν την προαιρετική δυνατότητα κατά την κατασκευή του client προγράμματος, δεν πρέπει να πειράξετε τη μεταβλητή περιβάλλοντος LD_LIBRARY_PATH (που περιγράφεται παρακάτω) για κάποιον άλλο λόγο εκτός από το να εξασφαλίσετε ότι δεν δημιουργούνται συνθήκες συγκρούσεων, ή ότι δε χρησιμοποιούνται άλλες τεχνικές απόκρυψης της βιβλιοθήκης.

3.5. Εγκατάσταση και χρήση διαμοιραζόμενης βιβλιοθήκης

Μόλις δημιουργήσετε μια διαμοιραζόμενη βιβλιοθήκη, θα θελήσετε να την εγκαταστήσετε. Η απλή προσέγγιση είναι απλά να αντιγραφεί η βιβλιοθήκη σε έναν από τους πρότυπους καταλόγους αρχείων (π.χ. /usr/lib) και να τρέξετε το ldconfig(8).

Κατ' αρχάς, θα πρέπει κάπου να δημιουργήσετε τις κοινές βιβλιοθήκες . Κατόπιν, θα πρέπει να ετοιμάσετε τους απαραίτητους συμβολικούς συνδέσμους, και ειδικότερα ένα link από ένα soname σε ένα realname (καθώς επίσης και από ένα versionless soname, δηλαδή ένα soname που έχει επίθεμα μόνον το ".so", για τους χρήστες που δεν προσδιορίζουν καθόλου μια έκδοση). Η απλούστερη προσέγγιση είναι να τρέξετε:
 ldconfig -n directory_with_shared_libraries

Τέλος, όταν μεταγλωττίζετε τα προγράμματά σας, θα πρέπει να πείτε στον linker ότι για τις στατικές και διαμοιραζόμενες βιβλιοθήκες που χρησιμοποιείτε. Χρησιμοποιήστε τις προαιρετικές παραμέτρους -l και -L για να το κάνετε.

Εάν δεν μπορείτε ή δεν θέλετε να εγκαταστήσετε μια βιβλιοθήκη σε μια standard θέση (π.χ. δεν έχετε τα δικαιώματα να τροποποιήσετε τον κατάλογο /usr/lib), θα πρέπει να αλλάξετε την προσέγγισή σας. Σε αυτή την περίπτωση, θα πρέπει να εγκαταστήσετε τις βιβλιοθήκες κάπου, και να δώσετε έπειτα στο πρόγραμμά σας αρκετές πληροφορίες έτσι ώστε να μπορεί να βρεί τη βιβλιοθήκη... και υπάρχουν διάφοροι τρόποι να γίνει αυτό. Μπορείτε να χρησιμοποιήσετε τη σημαία (flag) -L του gcc στις απλούστερες των περιπτώσεων. Μπορείτε να χρησιμοποιήσετε και την "προσέγγιση rpath" (που περιεγράφη ανωτέρω), ιδιαίτερα εάν θέλετε μόνο ένα συγκεκριμένο πρόγραμμα να χρησιμοποιήσει τη βιβλιοθήκη που τοποθετείτε σε έναν κατάλογο εκτός των standard. Μπορείτε επίσης να χρησιμοποιήσετε τις μεταβλητές περιβάλλοντος για να ελέγξετε τα πράγματα. Ειδικότερα, μπορείτε να θέσετε την LD_LIBRARY_PATH, που είναι μια λίστα από ονόματα καταλόγων χωρισμένα με άνω και κάτω τελεία τα οποία ερευνώνται για ύπαρξη διαμοιραζόμενων βιβλιοθηκών πριν από την αναζήτηση στις συνηθισμένες θέσεις. Εάν χρησιμοποιείτε bash, θα μπορούσατε να καλέσετε το my_program με αυτόν τον τρόπο χρησιμοποιώντας:

LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH  my_program

Εάν θέλετε να παρακάμψετε απλά μερικές επιλεγμένες λειτουργίες/συναρτήσεις, μπορείτε να το κάνετε δημιουργώντας ένα "παρακαμπτήριο" object file και θέτοντας τη μεταβλητή LD_PRELOAD - οι συναρτήσεις σε αυτό το αρχείο αντικειμένου θα υπερισχύσουν ακριβώς εκείνων των λειτουργιών που πρέπει (αφήνοντας τις άλλες όπως είχαν).

Συνήθως μπορείτε να ενημερώσετε (update) βιβλιοθήκες χωρίς κάποια ιδιαίτερη ανησυχία - εάν υπήρξε αλλαγή στη διεπαφή (API), ο δημιουργός βιβλιοθηκών είναι θεωρητικά υποχρεωμένος να αλλάξει το soname. Με αυτό τον τρόπο μπορούν σε ένα σύστημα να συνυπάρχουν πολλαπλές βιβλιοθήκες, και η κατάλληλη να επιλέγεται για κάθε πρόγραμμα. Εντούτοις, εάν ένα πρόγραμμα δε λειτουργεί λόγω της ενημέρωσης μιας βιβλιοθήκης που κράτησε το ίδιο soname, μπορείτε να το αναγκάσετε να χρησιμοποιήσει την παλαιότερη έκδοση βιβλιοθηκών με το να αντιγράψετε την παλιά βιβλιοθήκη κάπου, να μετονομάσετε το πρόγραμμα (π.χ, δώστε στο παλαιό το ίδιο όνομα προσθέτοντας το επίθεμα ".orig"), και να δημιουργήσετε έπειτα ένα μικρό wrapper script που ρυθμίζει ξανά τη βιβλιοθήκη που χρησιμοποιείται και καλεί το πραγματικό (μετονομασμένο) πρόγραμμα. Θα μπορούσατε να τοποθετήσετε την παλαιά βιβλιοθήκη σε μια ειδική, "προσωπική" περιοχή, εάν θέλετε, αν και είναι δυνατόν να υπάρχουν πολλαπλές εκδόσεις της ίδιας βιβλιοθήκης στον ίδιο κατάλογο, χάρη στην αριθμοδότηση που επιβάλλεται στην ονομασία των διαφόρων εκδόσεων βιβλιοθηκών. Το wrapper script θα μπορούσε να ναι κάπως έτσι:
  #!/bin/sh
  export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH
  exec /usr/bin/my_program.orig $*
Πάντως θα σας παρακαλούσα αν βασιστείτε στις παραπάνω "διευκολύνσεις" κατά τη συγγραφή των προγραμμάτων σας- προσπαθήστε να σιγουρευτείτε καλύτερα ότι οι βιβλιοθήκες σας είναι είτε προς-τα-πίσω-συμβατές (backwards-compatible) είτε ότι έχετε αυξήσει τον αριθμό έκδοσης στο soname κάθε φορά που κάνετε μια αλλαγή που δεν είναι συμβατή με τα έως τώρα. Τα ανωτέρω είναι μια "προσέγγιση έκτακτου ανάγκης" για την αντιμετώπιση προβλημάτων "της κακίας ώρας".

Μπορείτε να δείτε τον κατάλογο των διαμοιραζόμενων βιβλιοθηκών που χρησιμοποιούνται από ένα πρόγραμμα χρησιμοποιώντας την ldd (1). Έτσι, παραδείγματος χάριν, μπορείτε να δείτε τις κοινές βιβλιοθήκες που χρησιμοποιούνται απο το ls πληκτρολογώντας:
  ldd /bin/ls
Βασικά, θα δείτε μια λίστα από sonames από τα οποία εξαρτάται το πρόγραμμα (η ls στη συγκεκριμένη περίπτωση), μαζί με τον κατάλογο αρχείων στον οποίο αυτά βρίσκονται. Πάντως σχεδόν σε κάθε περίπτωση θα έχετε τουλάχιστον δύο dependencies (εξαρτήσεις):

Προσοχή: μην τρέξετε το ldd σε προγράμματα που δεν εμπιστεύεστε. Όπως δηλώνεται σαφώς στο εγχειρίδιο του ldd (1), η ldd δουλεύει (σε ορισμένες περιπτώσεις) με το να θέτει μιας ειδική μεταβλητή περιβάλλοντος (για τα αντικείμενα ELF, την LD_TRACE_LOADED_OBJECTS) και εκτελώντας έπειτα το πρόγραμμα. Μπορεί να είναι δυνατό για ένα μη αξιόπιστο πρόγραμμα να αναγκάσει τον χρήστη της ldd να τρέξει αυθαίρετο και πιθανότατα επιβλαβή κώδικα (αντί απλά να εμφανίσει πληροφορίες της ldd). Έτσι, για λόγους ασφαλείας, μην χρησιμοποιείτε ldd στα προγράμματα που δεν εμπιστεύεστε προς εκτέλεση.

3.6. Ασυμβίβαστες μεταξύ τους βιβλιοθήκες

Όταν η νέα έκδοση μια βιβλιοθήκης είναι binary incompatible ("δυαδικά- ασυμβίβαστη") με την παλαιότερη έκδοση της, το soname πρέπει να αλλάξει. Στην C, υπάρχουν τέσσερις βασικοί λόγοι για τους οποίους μια βιβλιοθήκη θα γινόταν binary-incompatible με το σύστημα:

  1. Η συμπεριφορά μιας λειτουργίας (συνάρτησης) αλλάζει έτσι ώστε δεν ανταποκρίνεται πλέον στις αρχικές προδιαγραφές της,

  2. Τα αντικείμενα που "εξάγονται" (exported data items) αλλάζουν (εξαίρεση: η προσθήκη προαιρετικών αντικειμένων στις άκρες των δομών είναι εντάξει, εφ' όσον εκείνες οι δομές δεσμεύονται μόνο μέσα στη βιβλιοθήκη).

  3. Μια εξαγόμενη συνάρτηση (exported function) αφαιρείται.

  4. Το interface μιας εξαγόμενης λειτουργίας αλλάζει.

Εάν μπορείτε να αποφύγετε τους παραπάνω λόγους, μπορείτε να κρατήσετε τις βιβλιοθήκες σας binary-compatible με το σύστημα. Για να το πούμε και κάπως διαφορετικά, μπορείτε να κρατήσετε τo Application Binary Interface (ABI, κάτι σαν "Δυαδική Διεπαφή Εφαρμογών") συμβατό, εάν αποφεύγετε τέτοιες αλλαγές. Παραδείγματος χάριν, μπορεί να θελήσετε να προσθέσετε νέες λειτουργίες/συναρτήσεις αλλά να μην διαγράψετε τις παλιές. Μπορείτε να προσθέσετε τα νέα αντικείμενα στις δομές σας, αλλά μόνο εάν μπορείτε να σιγουρευτείτε ότι τα παλιά προγράμματα δεν θα είναι "ευαίσθητα" σε τέτοιες αλλαγές, κάνοντας την προσθήκη των αντικειμένων μόνο στο τέλος της δομής, επιτρέποντας μόνο στη βιβλιοθήκη (και όχι την εφαρμογή) να δεσμεύει τη δομή, καθιστώντας τα πρόσθετα αντικείμενα προαιρετικά (ή θέτοντας τη βιβλιοθήκη να τα συμπληρώνει), και τα λοιπά. Προσέξτε: δεν μπορείτε πιθανώς να επεκτείνετε τις δομές εάν οι χρήστες τις χρησιμοποιούν σε πίνακες (arrays).

Σχετικά με την C++ (και άλλες γλώσσες που υποστηρίζουν compiled-in templates και/ή compiled dispatched μεθόδους), η κατάσταση είναι πιο περίπλοκη. Όλα τα ανωτέρω ζητήματα ισχύουν, συν πολλά ακόμα. Ο λόγος είναι ότι κάποιες πληροφορίες υλοποιούνται κάπως "κρυφά" στο μεταγλωττισμένο κώδικα, με συνέπεια εξαρτήσεις που πιθανότατα να μην είναι προφανείς εάν δεν ξέρετε πώς υλοποιείται τυπικά η C++. Για να κυριολεκτήσουμε, δεν είναι "καινούρια ζητήματα", είναι ακριβώς ότι ο μεταγλωττισμένος κώδικας C++ επηρεάζει τέτοιες ευαίσθητες πληροφορίες με πολύ πρωτότυπους τρόπους που πιθανόν να μην πάει ο νους σας. Ο ακόλουθος είναι ένας (πιθανώς ελλιπής) κατάλογος πραγμάτων που δεν μπορείτε να κάνετε σε C++ και να διατηρήσετε binary-compatibilty ("δυαδική συμβατότητα"), όπως αναφέρεται από το Troll Tech's Technical FAQ:

  1. να προσθέσετε εκ νέου υλοποιημένες εικονικές συναρτήσεις (εκτός αν είναι ασφαλές για παλαιότερα binaries να καλούν την αρχική εφαρμογή), επειδή ο μεταγλωττιστής "αξιολογεί" (evaluates) τις κλήσεις SuperClass::virtualFunction() κατά τη μεταγλώττιση (compile-time, όχι link-time).

  2. να προσθέσετε ή αφαιρέσετε virtual member functions (λειτουργίες εικονικών μελών), επειδή αυτό θα άλλαζε το μέγεθος και το layout του vtbl κάθε υποκλάσης.

  3. να αλλάξετε τον τύπο οποιωνδήποτε data members (μελών δεδομένων) ή να μετακινήσετε τέτοια, ενώ μπορούν να προσεγγιστούν μέσω inline member functions.

  4. να αλλάξετε την class ierarchy (ιεραρχία κλάσης), εκτός από το για να προστεθούν νέα φύλλα.

  5. να προσθέσετε ή ν' απομακρύνετε private data members (μέλη ιδιωτικών στοιχείων), επειδή αυτό θα άλλαζε το μέγεθος και το layout κάθε υποκατηγορίας.

  6. να αφαιρέστε public ή protected member functions εκτός αν είναι inline.

  7. να καταστήστε μια public ή protected member function σε inline.

  8. να αλλάξετε αυτό που κάνει μια inline συνάρτηση, εκτός αν η παλαιά έκδοση συνεχίζει να λειτουργεί.

  9. να αλλάξετε τα δικαιώματα πρόσβασης (ήτοι public, protected ή private) μιας member function σε ένα φορητό (portable) πρόγραμμα, επειδή κάποιοι μεταγλωττιστές αντιστοιχίζουν τα δικαιώματα πρόσβασης στο όνομα της συνάρτησης.

Λαμβάνοντας υπόψη αυτόν τον μεγάλο κατάλογο, οι υπεύθυνοι για την ανάπτυξη βιβλιοθηκών σε C++ ειδικότερα, πρέπει να προσχεδιάζουν ιδιαίτερα τακτικά updates αν είναι να καταστρατηγούν τη δυαδική συμβατότητα κατά την ανανέωση μιας βιβλιοθήκης. Ευτυχώς, στα Unix-οειδή συστήματα (συμπεριλαμβανομένου του Linux) μπορείτε να έχετε πολλαπλές εκδόσεις μιας βιβλιοθήκης να φορτώνονται συγχρόνως, έτσι με το μικρό κόστος της απώλειας (μικρού) διαστήματος στο δίσκο, οι χρήστες να μπορούν ακόμα να τρέξουν "παλιά" προγράμματα, που χρειάζονται τις παλαιές βιβλιοθήκες.