HOWTO για Βιβλιοθήκες Προγραμμάτων - Program Library HOWTO David A. Wheeler έκδοση 1.20, 11 Απριλίου 2003 Αυτό το HOWTO για προγραμματιστές, πραγματεύεται θέματα γύρω από τη δημιουργία και χρήση βιβλιοθηκών προγραμμάτων στο Linux. Στις τελευταίες συμπεριλαμβάνονται οι στατικές (static), οι διαμοιραζόμενες (shared) και οι δυναμικές ( dynamically loaded ) βιβλιοθήκες. _________________________________________________________ Πίνακας Περιεχομένων 1. Εισαγωγή 2. Στατικές (static) βιβλιοθήκες 3. Διαμοιραζόμενες (shared) βιβλιοθήκες 3.1. Συμβάσεις 3.1.1. Ονόματα διαμοιραζόμενων βιβλιοθηκών 3.1.2. Τοποθέτηση στο Σύστημα Αρχείων 3.2. Πως χρησιμοποιούνται οι βιβλιοθήκες 3.3. Μεταβλητές περιβάλλοντος 3.3.1. LD_LIBRARY_PATH 3.3.2. LD_DEBUG 3.3.3. Άλλες μεταβλητές περιβάλλοντος 3.4. Δημιουργώντας μιας διαμοιραζόμενη βιβλιοθήκη 3.5. Εγκατάσταση και χρήση διαμοιραζόμενης βιβλιοθήκης 3.6. Ασυμβίβαστες μεταξύ τους βιβλιοθήκες 4. Δυναμικές (Dynamically Loaded - DL) βιβλιοθήκες 4.1. dlopen() 4.2. dlerror() 4.3. dlsym() 4.4. dlclose() 4.5. Παράδειγμα δυναμικής βιβλιοθήκης 5. Διάφορα 5.1. Η εντολή nm 5.2. Συναρτήσεις κατασκευαστών (constructor) και καταστροφέων (destructor) βιβλιοθήκης 5.2.1. Ειδικές συναρτήσεις _init και _fini (ΠΑΡΩΧΗΜΕΝΟ/ΕΠΙΚΙΝΔΥΝΟ) 5.3. Οι διαμοιραζόμενες βιβλιοθήκες μπορεί να είναι αρχεία εντολών (scripts ) 5.4. Εκδόσεις συμβόλων (Symbol versioning) και Αρχείων εντολών έκδοσης (Version scripts) 5.5. GNU libtool 5.6. Αφαίρεση συμβόλων για εξοικονόμηση χώρου 5.7. Εξαιρετικά μικρά εκτελέσιμα 5.8. C++ εναντίον C 5.9. Κάνοντας ταχύτερη την αρχικοποίηση στη C++ 5.10. Linux Standard Base (LSB - Βάση Πρότυπων του Linux) 5.11. Συγχωνεύοντας βιβλιοθήκες σε μεγαλύτερες διαμοιραζόμενες (shared) βιβλιοθήκες 6. Περισσότερα παραδείγματα 6.1. Αρχείο libhello.c 6.2. Αρχείο libhello.h 6.3. Αρχείο demo_use.c 6.4. Αρχείο script_static 6.5. Αρχείο script_shared 6.6. Αρχείο demo_dynamic.c 6.7. Αρχείο script_dynamic 7. Αλλες πηγές πληροφοριών 8. Άδεια και Πνευματικά Δικαιώματα 1. Εισαγωγή Αυτό το HOWTO για προγραμματιστές πραγματεύεται θέματα σχετικά με το πώς μπορεί κανείς να δημιουργήσει και να χρησιμοποιήσει βιβλιοθήκες προγραμμάτων στο Linux, χρησιμοποιώντας το σύνολο εργαλείων GNU. Μια "βιβλιοθήκη προγράμματος'' είναι απλά ένα αρχείο που περιέχει το μεταγλωττισμένο (compiled) κώδικα (και τα δεδομένα), που πρόκειται να ενσωματωθούν αργότερα σε ένα πρόγραμμα. Οι βιβλιοθήκες προγραμμάτων επιτρέπουν στα προγράμματα να είναι πιο παραμετροποιήσιμα, γρηγορότερα στη διαδικασία επαναμεταγλώτισσης (recompilation), και ευκολότερα στη συντήρηση και την ενημέρωση (update). Οι βιβλιοθήκες προγράμματος μπορούν να διαιρεθούν σε τρεις τύπους: στατικές βιβλιοθήκες (static), διαμοιραζόμενες βιβλιοθήκες (shared), και δυναμικά φορτωμένες βιβλιοθήκες (dynamically loaded - DL). Αυτό το άρθρο αναφέρεται αρχικά στις στατικές βιβλιοθήκες, οι οποίες εγκαθίστανται σε ένα εκτελέσιμο (executable) προτού εκτελεστεί το αντίστοιχο πρόγραμμα. Συζητά έπειτα τις διαμοιραζόμενες βιβλιοθήκες, οι οποίες φορτώνονται στο ξεκίνημα του προγράμματος και μοιράζονται μεταξύ των προγραμμάτων. Τέλος, συζητά τις δυναμικές βιβλιοθήκες, οι οποίες μπορούν να φορτωθούν και να χρησιμοποιηθούν οποιαδήποτε στιγμή ενώ ένα πρόγραμμα τρέχει. Οι δυναμικές βιβλιοθήκες δεν είναι πραγματικά ένα διαφορετικό είδος μορφοποίησης (format) βιβλιοθηκών (τόσο οι στατικές όσο και διαμοιραζόμενες βιβλιοθήκες μπορούν να χρησιμοποιηθούν ως δυναμικές) αντ' αυτού, η διαφορά έγκειται στον τρόπο με τον οποίο οι δυναμικές βιβλιοθήκες χρησιμοποιούνται από τους προγραμματιστές. Αυτό το HOWTO κλείνει με ένα κεφάλαιο με περισσότερα παραδείγματα και ένα ακόμα με αναφορές σε άλλες πηγές πληροφοριών. Οι περισσότεροι υπεύθυνοι σε ανάπτυξη προγραμμάτων (developers) που αναπτύσσουν βιβλιοθήκες, οφείλουν να δημιουργήσουν διαμοιραζόμενες βιβλιοθήκες, δεδομένου ότι αυτές επιτρέπουν στους χρήστες να ενημερώσουν (update) τις βιβλιοθήκες τους χωριστά από τις εφαρμογές που τις χρησιμοποιούν. Οι δυναμικές βιβλιοθήκες είναι χρήσιμες, αλλά απαιτούν λίγο περισσότερη δουλειά για να καταστεί δυνατόν να χρησιμοποιηθούν και πολλά προγράμματα δεν χρειάζονται την ευελιξία που προσφέρουν. Απ' την άλλη πάλι, οι στατικές, κάνουν την αναβάθμιση (upgrading) των βιβλιοθηκών πολύ πιο προβληματική, οπότε δύσκολα θα τις συνιστούσε κανείς για γενική χρήση. Ωστόσο, κάθε μία έχει τα πλεονεκτήματά της, και τα πλεονεκτήματα του κάθε τύπου περιγράφονται στην αντίστοιχη παράγραφο του άρθρου. Οι προγραμματιστές που χρησιμοποιούν C++ και δυναμικές βιβλιοθήκες, θα πρέπει επίσης να συμβουλευθούν το "C++ mini- howto". Δεν έχει σημασία που μερικοί άνθρωποι χρησιμοποιούν τον όρο δυναμικά συνδεδεμένες βιβλιοθήκες (dynamically linked libraries - DLLs) για να αναφερθούν στις διαμοιραζόμενες (shared) βιβλιοθήκες,μερικοί άλλοι τον όρο DLL για να σημάνουν οποιαδήποτε βιβλιοθήκη που χρησιμοποιείται ως δυναμική βιβλιοθήκη (DL), και μερικοί ακόμα που χρησιμοποιούν τον όρο DLL για να αναφερθούν σε οποιαδήποτε από τις παραπάνω περιπτώσεις. Οποιοδήποτε σημασιολογικό ορισμό και να επιλέξετε, αυτό το HOWTO καλύπτει τις DLLs σε Linux. Σε αυτό το HOWTO ασχολούμαστε μόνο τη σύγχρονη και καθιερωμένη μορφοποίηση εκτελέσιμων και βιβλιοθηκών σχεδόν σε κάθε σύστημα/διανομή GNU/Linux : τη μορφοποίηση ELF (Executable and Linking Format). Το σύνολο εργαλείων GCC του GNU (GNU GCC toolset) μπορεί στην πραγματικότητα να χειριστεί βιβλιοθήκες με μορφοποίηση και διαφορετική της ELF - ειδικότερα, οι περισσότερες διανομές Linux μπορούν ακόμα να υποστηρίξουν την ξεπερασμένη μορφή a.out ( η υποστήριξη συνεχίζεται και στον 2.6 πυρήνα ). Εντούτοις, τέτοιες μορφοποιήσεις δε θα καλυφθούν εδώ. Εάν χτίζετε μια εφαρμογή που θα θέλατε να μπορεί να μεταφερθεί σε πολλά συστήματα, ίσως θα έπρεπε να σκεφτείτε να χρησιμοποιήσετε το GNU libtool για να χτίσετε και να εγκαταστήσετε βιβλιοθήκες, αντί να χρησιμοποιήσει τα εργαλεία του Linux αυτά καθ\ ευατά. Το GNU Libtool είναι ένα γενικό script (αρχείο εντολών) υποστήριξης βιβλιοθηκών, που κρύβει την πολυπλοκότητα χρήσης των διαμοιραζόμενων βιβλιοθηκών (π.χ., δημιουργώντας και εγκαθιστώντας τες) πίσω από μια και συμπαγής και φορητή, μεταξύ συστημάτων, διεπαφή. Στο Linux, το GNU Libtool χτίζεται πάνω από τα εργαλεία και τις συμβάσεις που περιγράφονται σε αυτό το HOWTO. Για μια φορητή διεπαφή (portable interface) στις δυναμικές βιβλιοθήκες, μπορείτε να χρησιμοποιήσετε τα διάφορα portability wrappers (περιτυλίγματα φορητότητας). Το GNU Libtool περιλαμβάνει ένα τέτοιο, αποκαλούμενο "libltdl". Εναλλακτικά, θα μπορούσατε να χρησιμοποιήσετε την glib βιβλιοθήκη (μην τη μπερδεύετε με την glibc) με τη φορητή υποστήριξή της για dynamic module loading (δυναμική φόρτωση τμημάτων). Μπορείτε να μάθετε περισσότερα σχετικά με τη glib στο http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-o f-modules.html. Και πάλι πάντως, στο Linux αυτή η λειτουργικότητα υλοποιείται χρησιμοποιώντας τα βασικά εργαλεία που περιγράφονται σε αυτό το HOWTO. Εάν πράγματι αναπτύσσετε ή διορθώνετε κώδικα σε Linux, πιθανώς οι πληροφορίες που υπάρχουν σε αυτό το HOWTO θα εξακολουθούν να σας είναι χρήσιμες. Η Αγγλική (και "αυθεντική" έκδοση) αυτού του HOWTO βρίσκεται στο http://www.dwheeler.com/program-library, and it has been και έχει συμπεριληφθεί στο Πρόγραμμα Τεκμηρίωσης του Linux (http://www.tldp.org). Τα πνευματικά δικαιώματα (C) 2000 ανήκουν στον David A. Wheeler και διανέμεται μέσω της άδειας GPL (Gerneral Public License) - δείτε την αντίστοιχη παράγραφο στο τέλος του άρθρου για περισσότερες πληροφορίες. _________________________________________________________ 2. Στατικές (static) βιβλιοθήκες Οι στατικές βιβλιοθήκες είναι απλά μια συλλογή από τα συνηθισμένα object files (αρχεία αντικειμένου) - συνήθως, οι στατικές βιβλιοθήκες τελειώνουν με ένα επίθημα ".a". Αυτή η συλλογή δημιουργείται χρησιμοποιώντας το πρόγραμμα ar (archiver). Οι στατικές βιβλιοθήκες δεν χρησιμοποιούνται πια τόσο συχνά όσο κάποτε, λόγω των πλεονεκτημάτων των διαμοιραζόμενων βιβλιοθηκών (που περιγράφονται παρακάτω). Ωστόσο, συνεχίζουν να επιλέγονται μερικές φορές, εμφανίστηκαν πρώτες ιστορικά, και είναι απλούστερες στην εξήγηση. Οι στατικές βιβλιοθήκες επιτρέπουν στους χρήστες να τις συνδέσουν με τα προγράμματα τους (link to programs), χωρίς να πρέπει να γίνει αναγκαστικά recompilation του κώδικα του προγράμματος, κερδίζοντας έτσι αυτό το χρόνο της επαναμεταγλώττισης. Βέβαια ο χρόνος για τη διαδικασία επαναμεταγλώττισης δεν είναι τη σήμερον ημέρα τόσο σπουδαίος, δεδομένων των σημερινών ταχύτατων μεταγλωττιστών, οπότε το πλεονέκτημα αυτό έχει εξασθενίσει σε σχέση με παλιότερα. Οι στατικές βιβλιοθήκες είναι συχνά χρήσιμες για τους developers εάν επιθυμούν να επιτρέψουν στους προγραμματιστές να συνδέσουν τα προγράμματα τους με τη βιβλιοθήκη των πρώτων, αλλά δεν θέλουν να δώσουν τον πηγαίο κώδικα της (που είναι ένα πλεονέκτημα για τον κατασκευαστή βιβλιοθηκών βέβαια, αλλά προφανώς δεν είναι τέτοιο για τον προγραμματιστή που προσπαθεί να χρησιμοποιήσει τη βιβλιοθήκη). Θεωρητικά, ο κώδικας στις στατικές βιβλιοθήκες ELF που συνδέεται με έναν εκτελέσιμο πρέπει να τρέξει ελαφρώς γρηγορότερα (από 1-5%) από ότι αν ο ίδιος κώδικας παρεχόταν απο μια διαμοιραζόμενη ή μια δυναμική βιβλιοθήκη, αλλά στην πράξη αυτό φαίνεται σπάνια να συμβαίνει εξαιτίας άλλων, αλληλοαναιρούμενων παραγόντων. Για να δημιουργήσετε μια στατική βιβλιοθήκη, ή να προσθέσετε τα πρόσθετα κι άλλα object files σε μια υπάρχουσα, χρησιμοποιήστε μια εντολή σαν την παρακάτω: ar rcs my_library.a file1.o file2.o Η εντολή στο παραπάνω παράδειγμα προσθέτει τα object files: file1.o και file2.ο στη στατική βιβλιοθήκη my_library.a, δημιουργόντας τη βιβλιοθήκη my_library.a, εάν δεν υπάρχει ήδη. Για περισσότερες πληροφορίες για τη δημιουργία των στατικών βιβλιοθηκών, δείτε το ar(1). Μόλις δημιουργήσετε μια στατική βιβλιοθήκη, θα θελήσετε, φυσικά, να τη χρησιμοποιήσετε. Μπορείτε να χρησιμοποιήσετε μια στατική βιβλιοθήκη καλώντας τη κατά τη διαδικασία της μεταγλώττισης και σύνδεσης (compilation and linking process) για τη δημιουργία ενός εκτελέσιμου προγράμματος . Εάν χρησιμοποιείτε το gcc(1) για να παραγάγετε το εκτελέσιμό σας, μπορείτε να χρησιμοποιήσετε την (προαιρετική) παράμετρο -l και να προσδιορίσετε τη βιβλιοθήκη. Περισσότερες πληροφορίες σχετικά μπορείτε να δείτε χρησιμοποιώντας το info (info:gcc). Να είστε προσεκτικοί στη σειρά των παραμέτρων που χρησιμοποιείτε με το GCC - η προαιρετική παράμετρος -l είναι μια προαιρετική δυνατότητα για τον linker (το τμήμα που είναι υπεύθυνο για τη σύνδεση π.χ. της βιβλιοθήκης με το κυρίως πρόγραμμα), και πρέπει γι' αυτό να τοποθετηθεί ΜΕΤΑ το όνομα του αρχείου που μεταγλωττίζεται. Αυτό είναι αρκετά διαφορετικό από τη συνήθη σύνταξη των προαιρετικών παραμέτρων. Εάν τοποθετήσετε την προαιρετική παράμετρο -l πριν από το όνομα αρχείου, μπορεί να αποτύχει πλήρως το linkning, και να καταλήξετε με ένα σωρό από μυστήρια σφάλματα. Μπορείτε επίσης να χρησιμοποιήσετε τον linker ld(1) άμεσα, χρησιμοποιώντας τις παραμέτρους του -l και -L, εντούτοις, στις περισσότερες περιπτώσεις είναι καλύτερο να χρησιμοποιηθεί ο GCC (1) δεδομένου ότι η διεπαφή του LD (1) είναι πιθανότερο να αλλάξει. _________________________________________________________ 3. Διαμοιραζόμενες (shared) βιβλιοθήκες Οι διαμοιραζόμενες βιβλιοθήκες είναι βιβλιοθήκες που φορτώνονται από τα προγράμματα όταν αρχίζουν. Όταν μια διαμοιραζόμενη βιβλιοθήκη εγκατασταθεί κατάλληλα, όλα τα προγράμματα που αρχίζουν κατόπιν, κάνουν αυτόματα χρήση της νέας διαμοιραζόμενης βιβλιοθήκης. Στην πραγματικά η παραπάνω περιγραφή είναι σχετικά απλή και στατική, αφού η προσέγγιση που χρησιμοποιείται από το Linux σας επιτρέπει: * να ενημερώσετε (update) βιβλιοθήκες, διατηρώντας την υποστήριξη για τα προγράμματα που πρέπει να χρησιμοποιούν παλιότερες εκδόσεις, ακόμα και εάν οι νέες βιβλιοθήκες δεν είναι προς-τα-πίσω συμβατές * να παρακάμψετε συγκεκριμένες βιβλιοθήκες ή ακόμα και συγκεκριμένες λειτουργίες μιας βιβλιοθήκης, κατά τον εκτέλεση ενός συγκεκριμένου προγράμματος. * να κάνετε όλα τα παραπάνω ενώ τα προγράμματα τρέχουν χρησιμοποιώντας τις υπάρχουσες βιβλιοθήκες _________________________________________________________ 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 Μερικά σημεία άξια περισσότερης προσοχής: * Μη "γδύσετε" (strip) την προκύπτουσα βιβλιοθήκη, και μην χρησιμοποιήστε την παράμετρο -fomit-frame-pointer στον compiler, εκτός και πραγματικά, δε γίνεται διαφορετικά. Η προκύπτουσα βιβλιοθήκη θα λειτουργήσει, αλλά αυτές οι ενέργειες καθιστούν τους debuggers (προγράμματα αποσφαλμάτωσης) συνήθως άχρηστους. * Χρησιμοποιήστε τις παραμέτρους -fPIC ή -fpic για να παραγάγετε τον κώδικα. Η χρήση -fPIC ή - fpic για την παραγωγή κώδικα εξαρτάται από τον στόχο σας. Η επιλογή -fPIC λειτουργεί πάντα, αλλά μπορεί να έχει σαν αποτέλεσμα μεγαλύτερο κώδικα από την -fpic (ένας απλός τρόπος για να θυμάστε την παραπάνω παρατήρηση είναι να έχετε κατά νου πως τα κεφαλαία, μεγάλα γράμματα PIC στην παράμετρο -fPIC θα παράγουν μεγαλύτερο σε μέγεθος κώδικα, απ' ότι μάλλον θα γίνει κάνοντας χρήση της -fpic). Χρήση της παραμέτρου -fpic παράγει συνήθως μικρότερο και γρηγορότερο κώδικα, αλλά θα έχει τους περιορισμούς εξαρτώμενους από το σύστημα που χτίστηκε η βιβλιοθήκη (platform-dependent), όπως ο αριθμός των μεταβλητών που είναι καθολικές (globally visible symbols), ή το μέγεθος του κώδικα. Ο linker θα σας πει εάν "του κάνει" (η επιλογή σας δε δημιουργεί προβλήματα) όταν δημιουργείτε τη διαμοιραζόμενη βιβλιοθήκη. Σε περίπτωση αμφιβολίας, επιλέξτε -fPIC - λειτουργεί πάντα. * Σε μερικές περιπτώσεις, η κλήση στον μεταγλωττιστή gcc για δημιουργία αρχείου αντικειμένου (object file) θα πρέπει επίσης να συμπεριλαμβάνει την προαιρετική παράμετρο "- Wl, -export-dynamic". Κανονικά, ο δυναμικός πίνακας συμβόλων (dynamic symbol table) περιέχει μόνο τα σύμβολα που χρησιμοποιούνται από ένα δυναμικό αντικείμενο. Αυτή η επιλογή (κατά το δημιουργία ενός αρχείου σε ELF) προσθέτει όλα τα σύμβολα στο δυναμικό πίνακα συμβόλων ((βλ. ld(1) για περισσότερες πληροφορίες). Πρέπει να χρησιμοποιήσετε αυτήν την προαιρετική δυνατότητα όταν υπάρχουν "αντίστροφες εξαρτήσεις", ήτοι, μια βιβλιοθήκη DL έχει απροσδιόριστα-εκκρεμή σύμβολα που πρέπει να καθοριστούν στα προγράμματα που σκοπεύουν να τη φορτώσουν (σ.τ.μ. για να γίνει πιο κατανοητό σε τι ακριβώς αναφερόμαστε λέγοντας "σύμβολο" - για τη μετάφραση χρησιμοποιήθηκε ακριβώς ο ελληνικός όρος του symbol, αναφέρουμε πως, όπως φαίνεται π.χ. στο παράδειγμα της παραγράφου 4.5, η cos είναι symbol της math βιβλιοθήκης). Για να δουλέψουν οι "αντίστροφες εξαρτήσεις", το κύριο πρόγραμμα πρέπει να καταστήσει τα σύμβολά του διαθέσιμα δυναμικά. Σημειώστε ότι θα μπορούσατε να πείτε "- rdynamic" αντί "- Wl, -export-dynamic", εάν εργάζεστε μόνο με συστήματα Linux, αλλά σύμφωνα με την τεκμηρίωση ELF (ELF documentation), η επιλογή "- rdynamic" δεν λειτουργεί πάντα για το gcc στα μη-linux συστήματα. Κατά τη διάρκεια της ανάπτυξης κώδικα, υπάρχει το πιθανό πρόβλημα της τροποποίησης μιας βιβλιοθήκης που χρησιμοποιείται επίσης κι από πολλά άλλα προγράμματα - και εσείς δεν θα θέλατε για να χρησιμοποιήσουν τη βιβλιοθήκη που βρίσκεται υπό ανάπτυξη τα άλλα προγράμματα, παρά μόνο μια συγκεκριμένη εφαρμογή που εξετάζετε ως προς τη συμπεριφορά της απέναντι στη νέα βιβλιοθήκη. Μια προαιρετική δυνατότητα για τη σύνδεση (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 (εξαρτήσεις): * /lib/ld-linux.so.Ν (όπου το N είναι μεγαλύτερο ή ίσον του 1, συνήθως τουλάχιστον 2). Αυτό είναι η βιβλιοθήκη που φορτώνει όλες τις άλλες βιβλιοθήκες. * libc.so.Ν (όπου το N είναι μεγαλύτερο ή ίσον του 6). Αυτό είναι η βιβλιοθήκη της C. Ακόμη και άλλες γλώσσες τείνουν να χρησιμοποιούν τη βιβλιοθήκη C (τουλάχιστον για να υλοποιήσουν τις δικές τους βιβλιοθήκες), οπότε τα περισσότερα προγράμματα περιλαμβάνουν τουλάχιστον και αυτήν εδώ. Προσοχή: μην τρέξετε το 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) μπορείτε να έχετε πολλαπλές εκδόσεις μιας βιβλιοθήκης να φορτώνονται συγχρόνως, έτσι με το μικρό κόστος της απώλειας (μικρού) διαστήματος στο δίσκο, οι χρήστες να μπορούν ακόμα να τρέξουν "παλιά" προγράμματα, που χρειάζονται τις παλαιές βιβλιοθήκες. _________________________________________________________ 4. Δυναμικές (Dynamically Loaded - DL) βιβλιοθήκες Οι δυναμικές (ή, "δυναμικά φορτωμένες") βιβλιοθήκες (DL) είναι βιβλιοθήκες που φορτώνονται σε στιγμές άλλες εκτός από (κατά) το ξεκίνημα ενός προγράμματος. Είναι ιδιαίτερα χρήσιμες για υλοποίηση plugins ή modules, επειδή επιτρέπουν αναμονή έτσι ώστε να φορτωθεί το plugin μόνο όταν απαιτείται. Παραδείγματος χάριν, το σύστημα Pluggable Authentication Modules (PAM), χρησιμοποιεί τις DL βιβλιοθήκες για να επιτρέψει στους διαχειριστές του συστήματος να "πειράξουν" (configure) ξανά και ξανά τη διαδικασία πιστοποίησης ταυτότητας. Είναι επίσης χρήσιμοι για την υλοποίηση διερμηνευτών (interpreters) που επιθυμούν περιστασιακά να μπορούν να μεταγλωττίσουν τον κώδικά τους σε κώδικα μηχανής και να χρησιμοποιήσουν τη μεταγλωττισμένη έκδοση για λόγους αποδοτικότητας, και όλα τα παραπάνω χωρίς να διακόψουν τη λειτουργία τους. Παραδείγματος χάριν, αυτή η προσέγγιση μπορεί να είναι χρήσιμη στην εφαρμογή ενός just-in-time compiler ή ενός multi-user dungeon (MUD). Στο Linux, οι DL βιβλιοθήκες δεν είναι πραγματικά "ξεχωριστές" από άποψη μορφοποίησης (format) τουλάχιστον - δημιουργούνται όπως τα συνήθη object files ή οι διαμοιραζόμενες βιβλιοθήκες, όπως συζητήθηκαν παραπάνω. Η βασική διαφορά είναι ότι οι βιβλιοθήκες δεν φορτώνονται αυτόματα κατά το link-time ή κατά το ξεκίνημα ενός προγράμματος - αντ' αυτού, υπάρχει ένα API (Application Programming Interface - σύστημα διεπαφής προγραμματιστικών εφαρμογών) για άνοιγμα μιας βιβλιοθήκης, αναφορά σε "σύμβολα" της, διαχείριση σφαλμάτων, και κλείσιμο της. Οι χρήστες της C θα πρέπει να συμπεριλάβουν το αρχείο επικεφαλίδων για να χρησιμοποιήσουν αυτό το ΑΡΙ. Η διεπαφή που χρησιμοποιείται από το Linux είναι ουσιαστικά η ίδια με αυτήν που χρησιμοποιείται στο Solaris και την οποία θα αποκαλώ "dlopen()" API. Εντούτοις, αυτή η διεπαφή δεν υποστηρίζεται από όλες τις πλατφόρμες - το HP- UX χρησιμοποιεί το διαφορετικό μηχανισμό shl_load (), και τα Windows χρησιμοποιούν DLLs, που έχουν και απολύτως διαφορετικό interface. Εάν ο στόχος σας είναι το πρόγραμμα σας να είναι φορητό τόσο που να μπορεί να χρησιμοποιηθεί σε όλα αυτά τα διαφορετικά συστήματα, οφείλετε μάλλον να σκεφτείτε σοβαρά να χρησιμοποιήσετε κάποια wrapping library που να κρύβει τις διαφορές μεταξύ των πλατφόρμων. Μια προσέγγιση εδώ είναι η βιβλιοθήκη glib με την υποστήριξή της για τη δυναμική φόρτωση modules (DLM, Dynamic Loading of Modules) - χρησιμοποιεί τις χαμηλότερου επιπέδου δυναμικές ρουτίνες φόρτωσης της πλατφόρμας για να δημιουργήσει μια φορητή διεπαφή σε αυτές τις συναρτήσεις. Μπορείτε να μάθετε περισσότερων για την glib στο http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-o f-modules.html. Δεδομένου ότι η glib διεπαφή έχει πολύ καλή τεκμηρίωσή (documentation), δεν θα ασχοληθώ άλλο μ' αυτή εδώ. Μια άλλη προσέγγιση είναι να χρησιμοποιήσετε την libltdl, που είναι και μέρος του GNU libtool. Εάν θέλετε ακόμα περισσότερη λειτουργικότητα από αυτή που σας παρέχουν οι παραπάνω επιλογές, ίσως θα πρέπει να ρίξετε μια ματιά σε ένα CORBA Object Request Broker (ORB). Εάν πάλι σας αρκεί η απευθείας χρήση του API που υποστηρίζεται από το Linux και το Solaris, συνεχίστε το διάβασμα και παρακάτω. Οι developers που χρησιμοποιούν C++ και δυναμικές βιβλιοθήκες (DL), πρέπει επίσης να συμβουλευθούν το "C++ dlopen mini-HOWTO". _________________________________________________________ 4.1. dlopen() Η συνάρτηση dlopen(3) ανοίγει μια βιβλιοθήκη και την προετοιμάζει για τη χρήση. Στο C το πρωτότυπό της είναι ως εξής: void * dlopen(const char *filename, int flag); Εάν το όνομα του αρχείου ξεκινάει με "/" ( δίνεται το πλήρες μονοπάτι), η dlopen() θα προσπαθήσει απλά να το χρησιμοποιήσει (δεν θα ψάξει για μια βιβλιοθήκη). Διαφορετικά, θα ψάξει για τη βιβλιοθήκη ακολουθώντας την εξής σειρά βημάτων: 1. Θα την αναζητήσει στους καταλόγους που περιέχονται ( χωρισμένοι με άνω και κάτω τελεία) στη μεταβλητή περιβάλλοντος LD_LIBRARY_PATH του χρήστη. 2. Θα ψάξει εάν είναι ανάμεσα στις βιβλιοθήκες που προσδιορίζονται από το αρχείο /etc/ld.so.cache (που δημιουργείται από το ld.so.conf). 3. Θα ελέγξει στον κατάλογο /usr/lib. Συγκρατήστε για λίγο τη σειρά που ακολουθήθηκε εδώ: είναι η αντίστροφη από τη σειρά που ακολουθούσε ο παλιός a.out loader. Ο παλιός loader, κατά τη φόρτωση ενός προγράμματος, έψαχνε αρχικά στον κατάλογο /usr/lib, έπειτα στον /lib (βλ. man page ld.so(8)). Αυτό κανονικά δε θα 'πρεπε να πειράζει , δεδομένου ότι μια βιβλιοθήκη πρέπει να βρίσκεται σε έναν μόνο από τους δυο καταλόγους αρχείων (ποτέ και στους δύο), και διαφορετικές βιβλιοθήκες με το ίδιο όνομα απλά είναι η καταστροφή που πάντα περιμένει στη γωνία. Στην dlopen(), η τιμή της παραμέτρου flag πρέπει να είναι είτε RTLD_LAZY, ήτοι "να προσδιοριστούν τα απροσδιόριστα σύμβολα (unresolved symbols) καθώς ο κώδικας από τη δυναμική βιβλιοθήκη εκτελείται", είτε RTLD_NOW, δηλαδή "να προσδιοριστούν προτού η dlopen() επιστρέψει και, εάν αυτό δεν είναι δυνατόν, να θεωρηθεί πως η συνάρτηση απέτυχε". Στην τιμή RTLD_GLOBAL πάλι, μπορεί προαιρετικά να εφαρμοστεί ο λογικός τελεστής OR με οποιαδήποτε τιμή της flag, σημαίνοντας ότι τα εξωτερικά σύμβολα (external symbols) που καθορίζονται στη βιβλιοθήκη θα τεθούν στην διάθεση βιβλιοθηκών που θα φορτωθούν στη συνέχεια. Κατά το debugging, θα θελήσετε πιθανώς να χρησιμοποιήσετε την RTLD_NOW - η χρήση της RTLD_LAZY μπορεί να δημιουργήσει σφάλματα που δεν μπορούν να εντοπιστούν εάν υπάρχουν εκκρεμείς αναφορές (unresolved references). Η χρήση της RTLD_NOW κάνει το άνοιγμα της βιβλιοθήκης ελαφρώς πιο αργό (αλλά επιταχύνει τις αναζητήσεις αργότερα) - εάν αυτό σας προκαλεί πρόβλημα, μπορείτε να αλλάξετε πάλι σε RTLD_LAZY αργότερα. Εάν οι βιβλιοθήκες εξαρτώνται η μια από την άλλη (π.χ. η Χ εξαρτάται από την Υ), πρέπει να φορτώσετε τις εξαρτημένες βιβλιοθήκες δεύτερες στη σειρά (σε αυτό το παράδειγμα, το Υ πρώτα, και έπειτα X). Η τιμή που επιστρέφει η dlopen() είναι ένα "handle" που για να χρησιμοποιηθεί από ρουτίνες άλλων DL βιβλιοθηκών πρέπει να θεωρηθεί "αδιαφανής τιμή" (opaque value). Η dlopen() θα επιστρέψει NULL εάν η προσπάθεια της να φορτώσει δεν πετύχει, πράγμα το οποίο πρέπει να ελέγξετε. Εάν η ίδια βιβλιοθήκη γίνει απόπειρα να φορτωθεί περισσότερες από μία φορά με την dlopen(), επιστρέφεται το ίδιο file handle. Στα παλαιότερα συστήματα, εάν η βιβλιοθήκη μοιράζεται ("εξάγει" - exports) μια ρουτίνα με την ονομασία _init, ο κώδικας της εκτελείται πριν επιστρέψει η dlopen(). Μπορείτε να χρησιμοποιήσετε αυτό το γεγονός στις βιβλιοθήκες σας για να υλοποιήσετε ρουτίνες αρχικοποίησης. Ωστόσο, οι βιβλιοθήκες δεν πρέπει να "εξάγουν" τις ρουτίνες με την όνομα _init ή _fini. Αυτοί οι μηχανισμοί είναι ξεπερασμένοι, και μπορεί να οδηγήσουν σε ανεπιθύμητη συμπεριφορά. Αντ' αυτού, οι βιβλιοθήκες πρέπει να εξαγάγουν τις ρουτίνες χρησιμοποιώντας τις ιδιότητες συναρτήσεων __attribute__((constructor)) και __attribute__((destructor)) (δεδομένου ότι χρησιμοποιείται τον gcc). Δείτε την παράγραφο Τμήμα 5.2 για περισσότερες πληροφορίες. _________________________________________________________ 4.2. dlerror() Για την αναφορά σφαλμάτων μπορεί να χρησιμοποιηθεί η dlerror (), που επιστρέφει μια συμβολοσειρά η οποία περιγράφει το σφάλμα από την τελευταία κλήση κάποιας εκ των dlopen(), dlsym (), ή dlclose (). Είναι αξιοπρόσεκτο το γεγονός πως μελλοντικές κλήσεις (μετά από κάποια επιτυχή) στην dlerror() θα επιστρέφουν NULL, έως ότου εμφανιστεί ένα άλλο σφάλμα. _________________________________________________________ 4.3. dlsym() Το να φορτώσει κανείς μια δυναμική βιβλιοθήκη δε θα είχε καμία σημασία εάν δεν μπορούσε να κάνει χρήση αυτής της συνάρτησης. Η βασική ρουτίνα για τη χρησιμοποίηση μιας DL βιβλιοθήκης είναι η dlsym (3), όποια αναζητά την τιμή ενός συμβόλου σε μια δεδομένη (ανοιγμένη) βιβλιοθήκη. Αυτή η συνάρτηση ορίζεται ως εξής: void * dlsym(void *handle, char *symbol); όπου το handle είναι η τιμή που επιστρέφει η dlopen(), και το symbol είναι μια συμβολοσειρά ( NIL-terminated - τελειώνει με NIL). Εάν μπορείτε να το κάνετε, αποφύγετε να καταχωρήστε την τιμή που επιστρέφει η dlsym() σε δείκτη void *, επειδή έπειτα θα πρέπει να κάνετε casting κάθε φορά που τη χρησιμοποιείτε (και θα δώσετε λιγότερες πληροφορίες σε άλλους ανθρώπους που προσπαθούν να συντηρήσουν το πρόγραμμα). Η dlsym() θα επιστρέψει NULL εάν το σύμβολο δεν βρέθηκε. Εάν ξέρετε ότι το σύμβολο δεν θα μπορούσε ποτέ να έχει την τιμή NULL ή μηδέν, το αποτέλεσμα αυτό δεν μας ενοχλεί, μα διαφορετικά μπορεί να δημιουργήσει μπέρδεμα: εάν επεστράφη μηδέν, αυτό σημαίνει πως δεν υπάρχει κανένα τέτοιο σύμβολο, ή πως η τιμή του είναι μηδέν; Η πρότυπη λύση είναι να κληθεί η dlerror() πρώτα (για να καθαρίσει οποιοδήποτε σφάλμα είχε ίσως νωρίτερα καταγραφεί), κατόπιν να κληθεί η dlsym() για να ζητήσει ένα σύμβολο, και τέλος πάλι η dlerror() για να ελεγχθεί πως δεν προέκυψε κάποιο σφάλμα. Τα παραπάνω σε κώδικα θα ήταν κάπως έτσι: dlerror(); /* clear error code */ s = (actual_type) dlsym(handle, symbol_being_searched_for); if ((err = dlerror()) != NULL) { /* handle error, the symbol wasn't found */ } else { /* symbol found, its value is in s */ } _________________________________________________________ 4.4. dlclose() Tο αντίστροφο της dlopen() είναι η dlclose(), η όποια κλείνει μια δυναμική βιβλιοθήκη. Η δυναμική βιβλιοθήκη συγκρατεί έναν αριθμό συνδέσεων για τα δυναμικά file handles, έτσι δεν απελευθερώνεται (deallocated) πραγματικά έως ότου η dlclose() κληθεί τόσες φορές όσες και η dlopen() για τη συγκεκριμένη βιβλιοθήκη. Κατά συνέπεια, δεν είναι πρόβλημα για το ίδιο πρόγραμμα να φορτωθούν οι ίδιες βιβλιοθήκες πολλές φορές. Όταν μια βιβλιοθήκη απελευθερώνεται, καλείται η συνάρτηση της _fini (εάν υπάρχει) στις παλιότερες βιβλιοθήκες, αλλά ο παραπάνω είναι ένας ξεπερασμένος μηχανισμός και δεν θα πρέπει να βασιστείτε σε αυτόν. Αντ' αυτού, οι βιβλιοθήκες πρέπει να εξαγάγουν ρουτίνες (export routines) χρησιμοποιώντας τις ιδιότητες συναρτήσεων (function attributes) __attribute__((constructor)) και __attribute__((destructor)). Δείτε την παράγραφο Τμήμα 5.2 για περισσότερες πληροφορίες. Σημείωση: Η dlclose() επιστρέφει 0 αν ολοκληρώσει επιτυχώς, και κάποια τιμή διάφορη του μηδενός σε αντίθετη περίπτωση, πράγμα το οποίο δεν αναφέρεται σε μερικές man pages του Linux. _________________________________________________________ 4.5. Παράδειγμα δυναμικής βιβλιοθήκης Το ακόλουθο παράδειγμα υπάρχει και στην man page της dlopen(3). Σε αυτό το παράδειγμα φορτώνεται η βιβλιοθήκη math, τυπώνεται το συνημίτονο του 2.0, και γίνεται ελέγχος για σφάλματα σε κάθε βήμα (κάτι το οποίο συστήνεται ανεπιφύλακτα): #include #include #include int main(int argc, char **argv) { void *handle; double (*cosine)(double); char *error; handle = dlopen ("/lib/libm.so.6", RTLD_LAZY); if (!handle) { fputs (dlerror(), stderr); exit(1); } cosine = dlsym(handle, "cos"); if ((error = dlerror()) != NULL) { fputs(error, stderr); exit(1); } printf ("%f\n", (*cosine)(2.0)); dlclose(handle); } Εάν το παραπάνω πρόγραμμα ήταν σε ένα αρχείο με όνομα "foo.c", θα κάνατε build με την ακόλουθη εντολή: gcc -o foo foo.c -ldl _________________________________________________________ 5. Διάφορα 5.1. Η εντολή nm Η εντολή nm(1) μπορεί να παραθέσει τη λίστα συμβόλων σε μια δεδομένη βιβλιοθήκη. Λειτουργεί τόσο για τις στατικές όσο και για τις διαμοιραζόμενες βιβλιοθήκες. Για μια δεδομένη βιβλιοθήκη η nm(1) μπορεί να εμφανίσει μια λίστα με τα ονόματα των συμβόλων που έχουν οριστεί, την τιμή κάθε συμβόλου, και τον τύπο του. Μπορεί επίσης να προσδιορίσει το σημείο στον πηγαίο κώδικα της βιβλιοθήκης όπου το σύμβολο ορίστηκε(με βάση όνομα αρχείου και αριθμό γραμμής), εάν οι αντίστοιχες πληροφορίες έχουν ενσωματωθεί στη βιβλιοθήκη (βλ. - προαιρετική παράμετρος -l). Η πληροφορία της nm που αφορά των τύπο των συμβόλων (symbol type) απαιτεί λίγο περισσότερη επεξήγηση. Ο τύπος παρουσιάζεται ως ένα γράμμα : μικρά γράμματα σημαίνουν ότι το σύμβολο είναι τοπικό (local), ενώ κεφαλαία ότι το σύμβολο είναι καθολικό (εξωτερικό - global,external). Οι χαρακτηριστικοί τύποι συμβόλων περιλαμβάνουν το T (το σύμβολο έχει απλά οριστεί σε αντίστοιχο σημείο στον κώδικα), το D(ανήκει στο τμήμα με τα αρχικοποιημένα δεδομένα), το Β (το σύμβολο ανήκει στο τμήμα με τα μη αρχικοποιημένα σύμβολα), το U(απροσδιόριστο - το σύμβολο χρησιμοποιείται από τη βιβλιοθήκη αλλά δεν ορίζεται σε αυτή), και το W("αδύναμο -weak- σύμβολο": εάν μια άλλη βιβλιοθήκη καθορίζει επίσης αυτό το σύμβολο, εκείνος ο καθορισμός υπερισχύει αυτού). Εάν ξέρετε το όνομα μιας συνάρτησης, αλλά δεν μπορείτε να θυμηθείτε σε ποια βιβλιοθήκη καθορίστηκε, μπορείτε να χρησιμοποιήσετε την προαιρετική παράμετρο "-ο" της nm (που προτάσσει το όνομα αρχείου σε κάθε γραμμή) μαζί με τη grep για να βρείτε το όνομα της βιβλιοθήκης. Από ένα Bourne shell, μπορείτε να ψάξετε όλες τις βιβλιοθήκες στους καταλόγους /lib, /usr/lib, συμπεριλαμβανομένων των (direct) υποκαταλόγων του /usr/lib, και στον /usr/local/lib για το "cos" ως εξής: nm -o /lib/* /usr/lib/* /usr/lib/*/* \ /usr/local/lib/* 2> /dev/null | grep 'cos$' Πολύ περισσότερες πληροφορίες για την nm μπορούν να βρεθούν στην αντίστοιχη τεκμηρίωση που εγκαθίσταται τοπικά στο info:binutils#nm. _________________________________________________________ 5.2. Συναρτήσεις κατασκευαστών (constructor) και καταστροφέων (destructor) βιβλιοθήκης Οι βιβλιοθήκες πρέπει να εξαγάγουν τις ρουτίνες αρχικοποίησης (initialization) και "καθαρισμού" (cleanup) χρησιμοποιώντας τις gcc__attribute__((constructor)) και gcc__attribute__((destructor)). Δείτε τις info pages του gcc για σχετικές πληροφορίες. Οι ρουτίνες των constructors εκτελούνται πριν επιστέψει η dlopen() (ή προτού αρχίσει η main(), εάν η βιβλιοθήκη φορτώνεται κατά το load time). Οι ρουτίνες των destructors εκτελούνται πριν επιστρέψει η dlclose (ή μετά από την exit() ή την ολοκλήρωση της main(), εάν η βιβλιοθήκη φορτώνεται κατά το load time). Τα πρωτότυπα της C (C prototypes) για αυτές τις λειτουργίες είναι: void __attribute__ ((constructor)) my_init(void); void __attribute__ ((destructor)) my_fini(void); Οι κοινές βιβλιοθήκες δεν πρέπει να μεταγλωττιστούν με τα ορίσματα του gcc "- nostartfiles" ή "-nostdlib". Εάν χρησιμοποιηθούν αυτά τα ορίσματα, οι ρουτίνες κατασκευαστών/καταστροφέων δεν θα εκτελεσθούν (εκτός αν λειφθούν ειδικά μέτρα). _________________________________________________________ 5.2.1. Ειδικές συναρτήσεις _init και _fini (ΠΑΡΩΧΗΜΕΝΟ/ΕΠΙΚΙΝΔΥΝΟ) Ιστορικά έχουν υπάρξει δύο ειδικά συναρτήσεις, οι _init και _fini, που μπορούν να χρησιμοποιηθούν για να ελέγξουν τους κατασκευαστές και τους καταστροφείς. Εντούτοις, είναι ξεπερασμένες, και η χρήση τους μπορεί να οδηγήσει σε απρόβλεπτα αποτελέσματα. Οι βιβλιοθήκες σας δεν πρέπει να τις χρησιμοποιήσουν - χρησιμοποιήστε τον κατασκευαστή και τον καταστροφέα ιδιοτήτων συναρτήσεων (function attributes constructor/destructor) που παρουσιάσθηκαν ανωτέρω αντ' αυτού. Εάν πρέπει οπωσδήποτε να εργαστείτε με παλιά συστήματα ή κώδικα που χρησιμοποιούσε _init ή _fini, ακολουθεί μια περιγραφή για τον τρόπο λειτουργίας τους. Δύο ειδικές λειτουργίες καθορίστηκαν για την αρχικοποίηση και την περάτωση ενός module: οι _init και _fini. Εάν μια λειτουργία ``_init '' εξάγεται (is exported) σε μια βιβλιοθήκη, τότε καλείται όταν πρωτοανοίγεται η βιβλιοθήκη (μέσω της dlopen() ή απλά ως διαμοιραζόμενη βιβλιοθήκη). Σε ένα πρόγραμμα C, αυτό απλά σημαίνει πως ορίσατε (defined) κάποια λειτουργία που ονομάστηκε _init. Υπάρχει μια αντίστοιχη λειτουργία αποκαλούμενη _fini, που καλείται όποτε ένας χρήστης τελειώνει με τη χρήση της βιβλιοθήκης (μέσω μιας κλήσης στην dlclose() που φέρνει τον αριθμό συνδέσεων της στο μηδέν -βλ. dlclose(), ή με την κανονική έξοδο του προγράμματος). Τα πρωτότυπα της C για αυτές τις συναρτήσεις είναι: void _init(void); void _fini(void); Σε αυτήν την περίπτωση, κατά τη μεταγλώττιση του αρχείου σε ένα ".o" αρχείο στο gcc, σιγουρευτείτε πως προσθέσατε την προαιρετική παράμετρο του gcc "- nostartfiles". Αυτό εμποδίζει το μεταγλωττιστή της C να συνδέσει (linking) τις βιβλιοθήκες εκκίνησης του συστήματος στο αρχείο. Διαφορετικά, θα πάρετε ένα σφάλμα "multiple-definition" ("πολλαπλός-ορισμός"). Σημειώστε πως τα παραπάνω είναι κάτι εντελώς διαφορετικό από το να μεταγλωττίζετε modules που χρησιμοποιούν τις συνιστώμενες ιδιότητες συναρτήσεων. Οι ευχαριστίες μου στον Jim Mischel και τον Tim Gentry για την πρότασή τους να προστεθεί αυτή η κουβέντα γύρω από τις συναρτήσεις _init και _fini, καθώς επίσης και για τη βοήθεια τους στη δημιουργία της. _________________________________________________________ 5.3. Οι διαμοιραζόμενες βιβλιοθήκες μπορεί να είναι αρχεία εντολών (scripts ) Αξίζει να σημειωθεί ότι ο GNU loader επιτρέπει οι κοινές βιβλιοθήκες να είναι αρχεία εντολών (script files) που χρησιμοποιούν μια εξειδικευμένη scripting γλώσσα, αντί της συνηθισμένης μορφής βιβλιοθηκών. Αυτό είναι χρήσιμο για την έμμεση σύνδεση με άλλες βιβλιοθήκες. Παραδείγματος χάριν, ορίστε η λίστα /usr/lib/libc.so σε ένα από τα συστήματά μου: /* GNU ld script Use the shared library, but some functions are only in the static library, so try that secondarily. */ GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a ) Για περισσότερες πληροφορίες σχετικά, δείτε την τεκμηρίωση texinfo στα scripts του ld linker (ld γλώσσα εντολών). Οι γενικές πληροφορίες είναι στη θέση info:ld#Options και info:ld#Commands, με τις πιθανές εντολές να συζητούνται στο info:ld#Option Commands. _________________________________________________________ 5.4. Εκδόσεις συμβόλων (Symbol versioning) και Αρχείων εντολών έκδοσης (Version scripts) Τυπικά, αναφορές σε εξωτερικές συναρτήσεις (external functions) είναι δεν είναι "προσδεμένες" (bound) κατά την εκκίνηση μιας εφαρμογής, αλλά γίνονται όταν χρειαστεί. Εάν μια κοινή βιβλιοθήκη δεν είναι πρόσφατα ανανεωμένη (out of date), μπορεί να λείπει ένα απαραίτητο interface- όταν η εφαρμογή προσπαθήσεί να το χρησιμοποιήσει, μπορεί ξαφνικά και απροσδόκητα να αποτύχει. Μια λύση σε αυτό το πρόβλημα είναι η χρήση symbol versioning (εκδόσεις συμβόλων) μαζί με τα version scripts (αρχεία εντολών έκδοσης). Με το symbol versioning, ο χρήστης μπορεί να δεχθεί μια προειδοποίηση όταν αρχίζει το πρόγραμμα του εάν οι βιβλιοθήκες που χρησιμοποιούνται με την εφαρμογή είναι πάρα πολύ παλιές. Μπορείτε να μάθετε περισσότερα σχετικά από μια συζήτηση στο εγχειριδίου του ld (man) για τα vesrion scripts στο http://www.gnu.org/manual/ld-2.9.1/html_node/ld_25.html. _________________________________________________________ 5.5. GNU libtool Εάν χτίζετε μια εφαρμογή που θα θέλατε να μπορεί να μεταφερθεί και να λειτουργήσει σε πολλά συστήματα, ίσως θελήσετε να θεωρήσετε το GNU libtool για να χτίσετε και να εγκαταστήσετε τις βιβλιοθήκες. Το GNU libtool είναι ένα γενικό αρχείο εντολών υποστήριξης βιβλιοθηκών (generic library support script). Το libtool κρύβει την πολυπλοκότητα της χρησιμοποίησης των διαμοιραζόμενων βιβλιοθηκών πίσω από μια συνεπή, φορητή διεπαφή. Επιπλέον παρέχει φορητά interfaces για τη δημιουργία object files, στατικές και διαμοιραζόμενες βιβλιοθήκες συνδέσεων (link libraries), σύνδεση εκτελέσιμων (link executables), αποσφαλμάτωση εκτελέσιμων (debug executables), εγκατάσταση βιβλιοθηκών και εγκατάσταση εκτελέσιμων. Περιλαμβάνει επίσης το libltdl, ένα περιτύλιγμα φορητότητας (portability wrapper) για τα δυναμικά προγράμματα φόρτωσης (dynamic loading programs). Για περισσότερες πληροφορίες, δείτε την τεκμηρίωσή του στο http://www.gnu.org/software/libtool/manual.html _________________________________________________________ 5.6. Αφαίρεση συμβόλων για εξοικονόμηση χώρου Όλα τα σύμβολα που συμπεριλαμβάνονται στα παραγόμενα αρχεία είναι χρήσιμα για το debugging, αλλά πιάνουν χώρο. Εάν χρειάζεστε χώρο, μπορείτε να απομακρύνετε μερικά. Η καλύτερη προσέγγιση είναι παραχθούν αρχικά τα object files κανονικά, και κάνετε όλο το debugging και τις δοκιμές πρώτα (η αποσφαλμάτωση και οι δοκιμές γίνονται πολύ πιο εύκολα με τη βοήθεια τους). Κατόπιν, μόλις εξετάσετε το πρόγραμμα λεπτομερώς, χρησιμοποιείτε την strip(1) για να αφαιρέσετε τα σύμβολα. Η εντολή strip(1) σας δίνει πλήρη έλεγχο στην απομάκρυνση των συμβόλων που θέλετε - δείτε την τεκμηρίωσή της για λεπτομέρειες. Μια άλλη προσέγγιση είναι να χρησιμοποιηθούν οι προαιρετικές παράμετροι "-s" και "-s" του GNU ld - η "-S" παραλείπει τις πληροφορίες συμβόλων για τον debugger (αλλά όχι γα όλα τα σύμβολα) από το αρχείο εξόδου, ενώ η "-s" παραλείπει όλες τις πληροφορίες συμβόλων από το αρχείο εξόδου. Μπορείτε να καλέσετε αυτές τις προαιρετικές δυνατότητες μέσω του gcc ως "-Wl,-S" και "-Wl,-s". Εάν πάντα απομακρύνετε τα σύμβολα και αυτές οι επιλογές σας καλύπτουν, αισθανθείτε ελεύθερος να τις χρησιμοποιείτε, αν και παραμένει μια λιγότερο "ελαστική" προσέγγιση. _________________________________________________________ 5.7. Εξαιρετικά μικρά εκτελέσιμα Ίσως βρείτε το paper Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux πολύ χρήσιμο. Περιγράφει πώς να φτιάξετε ένα πραγματικά μικροσκοπικό εκτελέσιμο προγράμματος . Εδώ που τα λέμε, δεν πρέπει να χρησιμοποιήσετε τα περισσότερα από τα τεχνάσματα που περιγράφει κάτω από πραγματικές συνθήκες, αλλά είναι αρκετά αναλυτικά στην επίδειξη του πραγματικού τρόπου λειτουργίας των ELF. _________________________________________________________ 5.8. C++ εναντίον C Αξίζει να σημειωθεί πως εάν γράφετε ένα πρόγραμμα σε C++, και καλείτε μια συνάρτηση από μια βιβλιοθήκη σε C, στον C++ κώδικα σας θα πρέπει να καθορίσετε τη λειτουργία C ως "extern C". Διαφορετικά, ο linker δεν θα είναι σε θέση να εντοπίσει τη συνάρτηση C. Εσωτερικά, οι compilers της C++ "κατακρεουργούν" ("mangle") τα ονόματα των συναρτήσεων στην C++ (π.χ., για λόγους τυποποίησης), και πρέπει να τους ειπωθεί ρητά ότι μια δεδομένη λειτουργία πρέπει να κληθεί ως λειτουργία της C (ώστε να μην κάνουν κάτι τέτοιο). Εάν γράφετε μια βιβλιοθήκη προγράμματος που θα μπορούσε να κληθεί από C ή C ++, συνιστάται να περιλαμβάνετε τις εντολές "extern C" ακριβώς στα αρχεία επικεφαλίδων σας, έτσι ώστε να το κάνετε αυτόματα για τους χρήστες σας. Όταν συνδυάζεται με το συνηθισμένο # ifndef (που μπαίνει στην κορυφή ενός αρχείου για να αποφευχθεί η επαν-εκτέλεση των αρχείων επικεφαλίδων), ένα τυπικό αρχείο επικεφαλίδων, έστω το foobar.h, που μπορεί να χρησιμοποιηθεί είτε από C είτε από C++, θα έμοιαζε με το παρακάτω: /* Explain here what foobar does */ #ifndef FOOBAR_H #define FOOBAR_H #ifdef __cplusplus extern "C" { #endif ... header code for foobar goes here ... #ifdef __cplusplus } #endif #endif _________________________________________________________ 5.9. Κάνοντας ταχύτερη την αρχικοποίηση στη C++ Οι developers του KDE έχουν παρατηρήσει ότι οι μεγάλες εφαρμογές GUI σε εφαρμογές C++ μπορεί να πάρουν κάποιο χρόνο να ξεκινήσουν, εν μέρει λόγω του ότι πρέπει να κάνουν πολλές επαναμεταθέσεις (relocations). Υπάρχουν διάφορες λύσεις σε αυτό το πρόβλημα. Δείτε το Making C++ ready for the desktop (by Waldo Bastian) για περισσότερες πληροφορίες. _________________________________________________________ 5.10. Linux Standard Base (LSB - Βάση Πρότυπων του Linux) Ο στόχος του LSB project είναι να αναπτυχθεί και να προωθηθεί ένα σύνολο προτύπων (standards) που θα αυξήσει τη συμβατότητα μεταξύ των διαφόρων διανομών Linux και θα επιτρέψει στις εφαρμογές λογισμικού να τρέξουν σε οποιοδήποτε σύστημα συμβατό με το Linux. Η αρχική σελίδα του προγράμματος είναι στο http://www.linuxbase.org. Ένα συμπαθητικό άρθρο που συνοψίζει πώς να αναπτύξετε LSB-συμβατές εφαρμογές δημοσιεύθηκε τον Οκτώβριο του 2002, το Developing LSB-certified applications: Five steps to binary-compatible Linux applications απ' τον George Kraft IV (Senior software engineer, IBM's Linux Technology Center). Φυσικά, πρέπει να γράψετε κώδικα που έχει πρόσβαση μόνο στο τυποποιημένο στρώμα φορητότητας (standardized compatibility layer) εάν θέλετε ο κώδικά σας να είναι φορητός. Επιπλέον, το LSB παρέχει μερικά εργαλεία έτσι ώστε οι συγγραφείς κώδικα σε C ή C++ να μπορούν να ελέγξουν οι εφαρμογές που γράφουν είναι LSB- compliant - τα αυτά εργαλεία χρησιμοποιούν κάποιες δυνατότητες του linker και ειδικές βιβλιοθήκες για να κάνουν τέτοιους ελέγχους. Προφανώς, θα πρέπει να εγκαταστήσετε τα εργαλεία για να κάνετε αυτούς τους ελέγχους - μπορείτε να τα κατεβάσετε από τη σελίδα του LSB στο διαδίκτυο. Κατόπιν, απλά χρησιμοποιήστε το μεταγλωττιστή "lsbcc" ως τον C/C++ μεταγλωττιστή σας (ο lsbcc εσωτερικά δημιουργεί ένα περιβάλλον σύνδεσης - linking environment- που θα παραπονεθεί εάν ορισμένοι κανόνες LSB δεν ακολουθούνται): $ CC=lsbcc make myapplication (or) $ CC=lsbcc ./configure; make myapplication Μπορείτε έπειτα να χρησιμοποιήσετε το πρόγραμμα lsbappchk για να επιβεβαιώσετε ότι το πρόγραμμα χρησιμοποιεί μόνο συναρτήσεις που τυποποιούνται από το LSB: $ lsbappchk myapplication Πρέπει επίσης να ακολουθήσετε τις οδηγίες για δημιουργία πακέτων του LSB (π.χ. χρησιμοποιήστε RPM v3. ονόματα πακέτων κατά LSB, εγκατάσταση add-on software στον κατάλογο /opt by default). Δείτε το άρθρο και το website του LSB για περισσότερες πληροφορίες. _________________________________________________________ 5.11. Συγχωνεύοντας βιβλιοθήκες σε μεγαλύτερες διαμοιραζόμενες (shared) βιβλιοθήκες Κι αν θελήσετε να δημιουργήσετε πρώτα μικρότερες βιβλιοθήκες και στη συνέχεια να τις συγψωνέυσετε σε μεγαλύτερες; Σε αυτή την περίπτωση, ίσως βρείτε την παράμετρο του ld "--whole-archive" ιδιαίτερα χρήσιμη, αφού μπορεί να χρησιμοποιηθεί για να τη μεταφορά και σύνδεση αρχέιων .a files σε ένα .so αρχείο. Ακολουθεί ένα παράδειγμα σχετικό με τη χρήση της --whole-archive: gcc -shared -Wl,-soname,libmylib.$(VER) -o libmylib.so $(OBJECTS) \ -Wl,--whole-archive $(LIBS_TO_LINK) -Wl,--no-whole-archive \ $(REGULAR_LIBS) Όπως σημειώνεται και στην τεκμηρίωση του ld, σιγουρευτείται πως όταν χρησιμοποιείτε την παράμετρο --no-whole-archive, τη θέτετε στο τέλος, διαφορετικά ο gcc θα προσπαθήσει να συγχωνεύσει ακόμα και τις standard βιβλιοθήκες. Οι ευχριστίες μου στον Kendall Bennett τόσο για την πρόταση όσο και την παροχή της παραπάνω "συνταγής". _________________________________________________________ 6. Περισσότερα παραδείγματα Τα παρακάτω είναι επιπλέον παραδείγματα και για τις τρεις εναλλακτικές προσεγγίσεις (static, shared, dynamically loaded). Το αρχείο libhello.c είναι μια απλούστατη βιβλιοθήκη, και το libhello.h το αρχείο επικεφαλίδας της. Το demo_use.c είναι ένας απλούστατος caller της βιβλιοθήκης. Τα παραπάνω συνοδεύονται από scripts σχόλια (script_static και script_dynamic), που υποδεικνύουν πως να χρησιμοποιήσετε τη βιβλιοθήκη σαν στατική ή διαμοιραζόμενη βιβλιοθήκη. Τέλος, ακολουθούν τα demo_dynamic.c και script_dynamic, που υποδεικνύουν πως να χρησιμοποιήσετε τη βιβλιοθήκη σαν δυναμική βιβλιοθήκη. _________________________________________________________ 6.1. Αρχείο libhello.c /* libhello.c - demonstrate library use. */ #include void hello(void) { printf("Hello, library world.\n"); } _________________________________________________________ 6.2. Αρχείο libhello.h /* libhello.h - demonstrate library use. */ void hello(void); _________________________________________________________ 6.3. Αρχείο demo_use.c /* demo_use.c -- demonstrate direct use of the "hello" routine */ #include "libhello.h" int main(void) { hello(); return 0; } _________________________________________________________ 6.4. Αρχείο script_static #!/bin/sh # Static library demo # Create static library's object file, libhello-static.o. # I'm using the name libhello-static to clearly # differentiate the static library from the # dynamic library examples, but you don't need to use # "-static" in the names of your # object files or static libraries. gcc -Wall -g -c -o libhello-static.o libhello.c # Create static library. ar rcs libhello-static.a libhello-static.o # At this point we could just copy libhello-static.a # somewhere else to use it. # For demo purposes, we'll just keep the library # in the current directory. # Compile demo_use program file. gcc -Wall -g -c demo_use.c -o demo_use.o # Create demo_use program; -L. causes "." to be searched during # creation of the program. Note that this command causes # the relevant object file in libhello-static.a to be # incorporated into file demo_use_static. gcc -g -o demo_use_static demo_use.o -L. -lhello-static # Execute the program. ./demo_use_static _________________________________________________________ 6.5. Αρχείο script_shared #!/bin/sh # Shared library demo # Create shared library's object file, libhello.o. gcc -fPIC -Wall -g -c libhello.c # Create shared library. # Use -lc to link it against C library, since libhello # depends on the C library. gcc -g -shared -Wl,-soname,libhello.so.0 \ -o libhello.so.0.0 libhello.o -lc # At this point we could just copy libhello.so.0.0 into # some directory, say /usr/local/lib. # Now we need to call ldconfig to fix up the symbolic links. # Set up the soname. We could just execute: # ln -sf libhello.so.0.0 libhello.so.0 # but let's let ldconfig figure it out. /sbin/ldconfig -n . # Set up the linker name. # In a more sophisticated setting, we'd need to make # sure that if there was an existing linker name, # and if so, check if it should stay or not. ln -sf libhello.so.0 libhello.so # Compile demo_use program file. gcc -Wall -g -c demo_use.c -o demo_use.o # Create program demo_use. # The -L. causes "." to be searched during creation # of the program; note that this does NOT mean that "." # will be searched when the program is executed. gcc -g -o demo_use demo_use.o -L. -lhello # Execute the program. Note that we need to tell the program # where the shared library is, using LD_LIBRARY_PATH. LD_LIBRARY_PATH="." ./demo_use _________________________________________________________ 6.6. Αρχείο demo_dynamic.c /* demo_dynamic.c -- demonstrate dynamic loading and use of the "hello" routine */ /* Need dlfcn.h for the routines to dynamically load libraries */ #include #include #include /* Note that we don't have to include "libhello.h". However, we do need to specify something related; we need to specify a type that will hold the value we're going to get from dlsym(). */ /* The type "simple_demo_function" describes a function that takes no arguments, and returns no value: */ typedef void (*simple_demo_function)(void); int main(void) { const char *error; void *module; simple_demo_function demo_function; /* Load dynamically loaded library */ module = dlopen("libhello.so", RTLD_LAZY); if (!module) { fprintf(stderr, "Couldn't open libhello.so: %s\n", dlerror()); exit(1); } /* Get symbol */ dlerror(); demo_function = dlsym(module, "hello"); if ((error = dlerror())) { fprintf(stderr, "Couldn't find hello: %s\n", error); exit(1); } /* Now call the function in the DL library */ (*demo_function)(); /* All done, close things cleanly */ dlclose(module); return 0; } _________________________________________________________ 6.7. Αρχείο script_dynamic #!/bin/sh # Dynamically loaded library demo # Presume that libhello.so and friends have # been created (see dynamic example). # Compile demo_dynamic program file into an object file. gcc -Wall -g -c demo_dynamic.c # Create program demo_use. # Note that we don't have to tell it where to search for DL libraries, # since the only special library this program uses won't be # loaded until after the program starts up. # However, we DO need the option -ldl to include the library # that loads the DL libraries. gcc -g -o demo_dynamic demo_dynamic.o -ldl # Execute the program. Note that we need to tell the # program where get the dynamically loaded library, # using LD_LIBRARY_PATH. LD_LIBRARY_PATH="." ./demo_dynamic _________________________________________________________ 7. Αλλες πηγές πληροφοριών Ιδιαίτερα χρήσιμες πηγές πληροφοριών για τις βιβλιοθήκες είναι οι ακόλουθες: * "The GCC HOWTO" από τον Daniel Barlow. Ειδικότερα, αυτό το HOWTO συζητά τις προαιρετικές παραμέτρους των μεταγλωττιστών για τις βιβλιοθήκες και πώς να κάνετε queries στις βιβλιοθήκες. Καλύπτει τις πληροφορίες που δεν καλύπτονται εδώ, και αντίστροφα. Αυτό το HOWTO είναι διαθέσιμο μέσω του προγράμματος τεκμηρίωσης του Linux στο http://www.tldp.org. * "Executable and Linkable Format", από την επιτροπή TIS (Tool Interface Standards) (αυτό είναι στην πραγματικά ένα κεφάλαιο από το "Portable Formats Specification" από την ίδια επιτροπή). Παρέχει πληροφορίες για τη μορφή ELF (δεν είναι συγκεκριμένο για Linux ή GNU gcc), και παρέχει με πολλές λεπτομέρειες. Δείτε το ftp://tsx-11.mit.edu/pub/linux/packages/GCC/ELF.doc.tar.gz Εάν πάρετε το αρχείο από το MIT, σημειώστε ότι η μορφοποίηση του είναι ασυνήθιστη - θα πάρετε ένα αρχείο "hps" - απλά αφαιρέστε τις πρώτες και τις τελικές γραμμές του αρχείου και μετονομάστε το σε "ps", και θα πάρετε ένα εκτυπώσιμο αρχείο Postscript με το συνηθισμένο όνομα αρχείου. * "ELF: Από την πλευρά του προγραμματιστή", από τον Hongjui Lu. Αυτό δίνει πληροφορίες για το ELF προσανατολισμένες στο Linux και το GNU gcc, και είναι διαθέσιμο στο ftp://tsx-11.mit.edu/pub/linux/packages/GCC/elf.ps.gz. * Στην τεκμηρίωση του ld "Using LD, the GNU Linker", περιγράφεται ο ld με πολύ περισσότερες λεπτομέρειες. Είναι διαθέσιμο στο http://www.gnu.org/manual/ld-2.9.1. * Θα πρέπει να ρίξετε οπωσδήποτε μια ματία και στη συνήθη τεκμηρίωση που παρέχεται μέσω του "info", ειδικά για τα ld και gcc. _________________________________________________________ 8. Άδεια και Πνευματικά Δικαιώματα Το παρόν έγγραφο είναι Copyright (C) 2000 του David Α. Wheeler. Καλύπτεται από την GNU General Public License (GPL). Μπορείτε να το αναδιανείμετε χωρίς κόστος. Αναφερόμενοι ακολούθως στο "πρόγραμμα", αναφερόμαστε στον πηγαίο κώδικα που παρουσιάστηκε από αυτό το έγγραφο, και για αυτό ισχύει: Αυτό το πρόγραμμα είναι ελεύθερο λογισμικό - μπορείτε να τον αναδιανείμετε ή/και να τον τροποποιήσετε υπό τους όρους της General Public License του GNU όπως δημοσιεύεται από το Free Software Foundation - είτε η έκδοση 2 της άδειας, είτε (αφήνετε στην ευχέρεια σας) οποιαδήποτε πιο πρόσφατη έκδοση. Αυτό το πρόγραμμα διανέμεται με την ελπίδα ότι θα είναι χρήσιμο, αλλά ΧΩΡΙΣ ΟΠΟΙΑΔΗΠΟΤΕ ΕΓΓΥΗΣΗ - χωρίς καν την υπονοούμενη εξουσιοδότηση ΕΜΠΟΡΕΥΣΙΜΟΤΗΤΑ ή της ΚΑΤΑΛΛΗΛΟΤΗΤΑΣ ΓΙΑ ΕΝΑΝ ΙΔΙΑΙΤΕΡΟ ΣΚΟΠΟ. Δείτε την GNU GPL για περισσότερες λεπτομέρειες. Θα πρέπει να έχετε λάβει ένα αντίγραφο της GNU GPL μαζί με αυτό το πρόγραμμα - εάν όχι, γράψτε στο Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Αυτοί οι όροι επιτρέπουν το mirroring από άλλα sites, αλλά παρακαλώ: * σιγουρευτείτε οι mirrors σας παίρνουν αυτόματα τις βελτιώσεις από τo master site, * εμφανίστε σαφώς τo url του master site, http://www.dwheeler.com/program-library, με ένα hypertext link σε αυτό, και * αναφέρατε με (David A. Wheeler) ως συντάκτη. Τα πρώτα δύο σημεία με απαλλάσσουν από το να ακούω επανειλημμένα για bugs που διορθώθηκαν. Δεν θέλω να μάθω για bugs που διόρθωσα πριν από ένα χρόνο, ακριβώς επειδή δεν κάνετε σωστά mirroring. Με τo link στο master site, οι χρήστες μπορούν να ελέγξουν και να δουν εάν ο mirror σας είναι ενημερωμένος. Δείχνω ιδιαίτερη ευαισθησία στα προβλήματα των sites με πολύ περιοριστικές απαιτήσεις ασφάλειας και επομένως δεν μπορούν να διακινδυνεύσουν links στο Διαδίκτυο - εάν κάτι τέτοιο περιγράφει την κατάστασή σας, προσπαθήστε τουλάχιστον να ακολουθήσετε τις υπόλοιπες παρατηρήσεις και να δοκιμάσετε να περνάτε τα updates στο περιβάλλον σας από μόνοι, περιστασιακά . Με αυτήν την άδεια, μπορείτε να τροποποιήσετε το έγγραφο, αλλά δεν μπορείτε να υποστηρίξετε πώς ο,τι γράψατε είναι δικός σας (λογοκλοπή) ούτε να παρουσιάστε μια τροποποιημένη έκδοση σαν ίδια με το αρχική άρθρο. Η τροποποίηση του δεν μεταφέρει τα πνευματικά δικαιώματα ολόκληρης της εργασίας σε σας - δεν πρόκειται για "δημόσιο έργο" (public domain work) από την άποψη του νόμου πνευματικών δικαιωμάτων. Δείτε την άδεια για περισσότερες λεπτομέρειες, σημειώνοντας ειδικότερα την εξής αναφορά: "You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change". Εάν έχετε ερωτήσεις για αυτά που η άδεια επιτρέπει, παρακαλώ ελάτε σε επαφή μαζί μου. Στις περισσότερες περιπτώσεις, είναι καλύτερο να στέλνετε τις αλλαγές σας στον βασικό υπεύθυνο (μέχρι σήμερα τον David A. Wheeler), έτσι ώστε οι αλλαγές σας να ενσωματωθούν, μαζί με όσες ακόμα γίνουν, στο πρωτότυπο.