Η δομή μιας ενότητας πυρήνα και οι μέθοδοι μεταγλώττισης της. Δυνατότητες μεταγλώττισης προγράμματος με αρθρωτή δομή. Εισαγωγή και Ιστορικό

Γιατί να μεταγλωττίσετε μόνοι σας τον πυρήνα;
Ίσως η κύρια ερώτηση που τίθεται σχετικά με τη μεταγλώττιση ενός πυρήνα είναι: "Γιατί να το κάνω αυτό;"
Πολλοί το θεωρούν άσκοπο χάσιμο χρόνου προκειμένου να φανούν ως έξυπνος και προηγμένος χρήστης Linux. Στην πραγματικότητα, η μεταγλώττιση του πυρήνα είναι ένα πολύ σημαντικό θέμα. Ας υποθέσουμε ότι αγοράσατε ένα νέο φορητό υπολογιστή και η κάμερα web δεν λειτουργεί. Οι ενέργειές σας; Ψάχνετε στη μηχανή αναζήτησης και αναζητάτε μια λύση στο πρόβλημα σε αυτό το ζήτημα. Αρκετά συχνά μπορεί να αποδειχθεί ότι η webcam σας λειτουργεί με περισσότερο νέα έκδοσηαπό το δικό σου. Αν δεν ξέρετε ποια έκδοση έχετε, πληκτρολογήστε uname -r στο τερματικό, ως αποτέλεσμα θα λάβετε την έκδοση του πυρήνα (π.χ. linux-2.6.31-10). Η μεταγλώττιση πυρήνα χρησιμοποιείται επίσης ευρέως για την αύξηση της απόδοσης: το γεγονός είναι ότι από προεπιλογή, οι διανομές πυρήνα μεταγλωττίζονται "για όλους", γι' αυτό περιλαμβάνει έναν τεράστιο αριθμό προγραμμάτων οδήγησης που μπορεί να μην χρειάζεστε. Επομένως, εάν γνωρίζετε καλά το υλικό που χρησιμοποιείτε, μπορείτε να απενεργοποιήσετε τα περιττά προγράμματα οδήγησης στο στάδιο της διαμόρφωσης. Είναι επίσης δυνατό να ενεργοποιήσετε την υποστήριξη για περισσότερα από 4 GB μνήμης RAM χωρίς αλλαγή του βάθους bit του συστήματος. Λοιπόν, αν εξακολουθείτε να χρειάζεται να έχετε τον δικό σας πυρήνα, ας ξεκινήσουμε τη μεταγλώττιση!

Λήψη του πηγαίου κώδικα του πυρήνα.
Το πρώτο πράγμα που πρέπει να κάνετε είναι να λάβετε τον πηγαίο κώδικα για την απαιτούμενη έκδοση του πυρήνα. Συνήθως πρέπει να λάβετε την πιο πρόσφατη σταθερή έκδοση. Όλες οι επίσημες εκδόσεις του πυρήνα είναι διαθέσιμες στο kernel.org. Εάν έχετε ήδη εγκατεστημένο διακομιστή X ( οικιακός υπολογιστής), μετά μπορείτε να μεταβείτε στον ιστότοπο στο αγαπημένο σας πρόγραμμα περιήγησης και να κάνετε λήψη της επιθυμητής έκδοσης στο αρχείο tar.gz (συμπιεσμένο gzip). Εάν εργάζεστε στην κονσόλα (για παράδειγμα, δεν έχετε εγκαταστήσει ακόμη τον διακομιστή X ή ρυθμίζετε τις παραμέτρους του διακομιστή), μπορείτε να χρησιμοποιήσετε ένα πρόγραμμα περιήγησης κειμένου (για παράδειγμα, συνδέσμους). Μπορείτε επίσης να χρησιμοποιήσετε τον τυπικό διαχειριστή λήψεων wget:
wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.33.1.tar.gz
Λάβετε όμως υπόψη ότι πρέπει να γνωρίζετε τον ακριβή αριθμό έκδοσης που χρειάζεστε.

Αποσυσκευασία του αρχείου πηγαίου κώδικα.
Αφού λάβετε το αρχείο πηγαίου κώδικα, πρέπει να εξαγάγετε το αρχείο σε έναν φάκελο. Αυτό μπορεί να γίνει από γραφικά διαχειριστές αρχείων(δελφίνι, ναυτίλος κ.λπ.) ή μέσω mc. Ή χρησιμοποιήστε την παραδοσιακή εντολή tar:
tar -zxvf path_to_archive
Τώρα έχετε έναν φάκελο με τον πηγαίο κώδικα, μεταβείτε σε αυτόν χρησιμοποιώντας την εντολή cd kernel_source_directory(για να παραθέσετε τους καταλόγους σε έναν φάκελο, χρησιμοποιήστε την εντολή ls).

Διαμόρφωση πυρήνα.
Αφού πλοηγηθείτε στον κατάλογο προέλευσης του πυρήνα, πρέπει να εκτελέσετε μια ρύθμιση παραμέτρων πυρήνα "20 λεπτών". Στόχος του είναι να αφήσει μόνο τα απαραίτητα προγράμματα οδήγησης και λειτουργίες. Όλες οι εντολές πρέπει να εκτελούνται ήδη ως υπερχρήστης.

make config - λειτουργία κονσόλας του διαμορφωτή.

make menuconfig - λειτουργία κονσόλας με τη μορφή λίστας.

make xconfig - γραφική λειτουργία.

Αφού κάνετε τις απαραίτητες αλλαγές, αποθηκεύστε τις ρυθμίσεις και βγείτε από τον διαμορφωτή.

Συλλογή.
Έφτασε η ώρα για το τελικό στάδιο της συναρμολόγησης – μεταγλώττισης. Αυτό γίνεται με δύο εντολές:
κάνει && κάνει εγκατάσταση
Η πρώτη εντολή θα μεταγλωττίσει όλα τα αρχεία σε κώδικα μηχανής και η δεύτερη θα εγκαταστήσει τον νέο πυρήνα στο σύστημά σας.
Περιμένουμε από 20 λεπτά έως αρκετές ώρες (ανάλογα με την ισχύ του υπολογιστή). Ο πυρήνας έχει εγκατασταθεί. Για να εμφανιστεί στη λίστα grub(2), πληκτρολογήστε (ως υπερχρήστης)
ενημέρωση-grub
Τώρα μετά την επανεκκίνηση, πατήστε "Escape" και θα δείτε τον νέο πυρήνα στη λίστα. Εάν ο πυρήνας δεν ενεργοποιείται, τότε απλώς εκκινήστε με τον παλιό πυρήνα και ρυθμίστε τον πιο προσεκτικά.

KernelCheck - μεταγλωττίζει τον πυρήνα χωρίς να μεταβεί στην κονσόλα.
σας επιτρέπει να δημιουργήσετε τον πυρήνα σε πλήρως γραφική λειτουργία για το Debian και τις διανομές που βασίζονται σε αυτό. Μετά την εκκίνηση, το KernelCheck θα προσφέρει τις πιο πρόσφατες εκδόσεις και ενημερώσεις κώδικα του πυρήνα και μετά τη συγκατάθεσή σας, κατεβάστε τον πηγαίο κώδικα και εκκινήστε τον διαμορφωτή γραφικών. Το πρόγραμμα θα μεταγλωττίσει τον πυρήνα σε πακέτα .deb και θα τα εγκαταστήσει. Το μόνο που έχετε να κάνετε είναι να κάνετε επανεκκίνηση.

Σχετικά με: "Βάσει της μετάφρασης" Linux Device Driver 2nd edition. Μετάφραση: Knyazev Alexey [email προστατευμένο]Ημερομηνία τελευταίας τροποποίησης: 08/03/2004 Τοποθεσία: http://lug.kmv.ru/index.php?page=knz_ldd2

Τώρα ας ξεκινήσουμε τον προγραμματισμό! Αυτό το κεφάλαιο παρέχει τα βασικά για τις ενότητες και τον προγραμματισμό πυρήνα.
Εδώ θα δημιουργήσουμε και θα ξεκινήσουμε μια ολοκληρωμένη ενότητα, η δομή της οποίας αντιστοιχεί σε οποιοδήποτε πραγματικό αρθρωτό πρόγραμμα οδήγησης.
Ταυτόχρονα, θα επικεντρωθούμε στις κύριες θέσεις χωρίς να λάβουμε υπόψη τις ιδιαιτερότητες των πραγματικών συσκευών.

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

Γειά σου Κόσμε!

Κατά τη διαδικασία της γνωριμίας με το πρωτότυπο υλικό που γράφτηκε από τους Alessndro Rubini & Jonathan Corbet, το παράδειγμα που δόθηκε ως Hello world μου φάνηκε κάπως ανεπιτυχές. Επομένως, θέλω να παρέχω στον αναγνώστη, κατά τη γνώμη μου, μια πιο επιτυχημένη έκδοση της πρώτης ενότητας. Ελπίζω ότι δεν θα υπάρξουν προβλήματα με τη μεταγλώττιση και την εγκατάστασή του κάτω από τον πυρήνα 2.4.x. Η προτεινόμενη ενότητα και ο τρόπος μεταγλώττισης της επιτρέπουν τη χρήση της σε πυρήνες που υποστηρίζουν και δεν υποστηρίζουν έλεγχο έκδοσης. Θα εξοικειωθείτε με όλες τις λεπτομέρειες και την ορολογία αργότερα, οπότε τώρα ανοίξτε το vim και ξεκινήστε να εργάζεστε!

================================================== === //αρχείο hello_knz.c #include #περιλαμβάνω <1>Γεια, κόσμο\n"); επιστροφή 0; ); void cleanup_module(void) ( printk("<1>Αντίο σκληρός κόσμος\n"); ) MODULE_LICENSE ("GPL"); ================================ ==================

Για να μεταγλωττίσετε μια τέτοια ενότητα, μπορείτε να χρησιμοποιήσετε το ακόλουθο Makefile. Μην ξεχάσετε να βάλετε έναν χαρακτήρα καρτέλας πριν από τη γραμμή που ξεκινά με $(CC) ... .

================================================== === ΣΗΜΑΙΑ = -c -Wall -D__KERNEL__ -DMODULE PARAM = -I/lib/modules/$(uname shell -r)/build/include hello_knz.o: hello_knz.c $(CC) $(FLAGS) $( PARAM) - o $@ $^ ========================================== =======================

Αυτό χρησιμοποιεί δύο χαρακτηριστικά σε σύγκριση με τον αρχικό κώδικα Hello world που πρότεινε η Rubini & Corbet. Πρώτον, η ενότητα θα έχει την ίδια έκδοση με την έκδοση του πυρήνα. Αυτό επιτυγχάνεται με τη ρύθμιση της μεταβλητής PARAM στο σενάριο μεταγλώττισης. Δεύτερον, η λειτουργική μονάδα θα έχει πλέον άδεια χρήσης σύμφωνα με την GPL (χρησιμοποιώντας τη μακροεντολή MODULE_LICENSE()). Εάν αυτό δεν γίνει, τότε κατά την εγκατάσταση της λειτουργικής μονάδας στον πυρήνα μπορεί να δείτε κάτι σαν την ακόλουθη προειδοποίηση:

# insmod hello_knz.o Προειδοποίηση: η φόρτωση του hello_knz.o θα αλλοιώσει τον πυρήνα: δεν υπάρχει άδεια Ανατρέξτε στη διεύθυνση http://www.tux.org/lkml/#export-tainted για πληροφορίες σχετικά με μολυσμένες μονάδες Η μονάδα hello_knz έχει φορτωθεί, με προειδοποιήσεις

Ας εξηγήσουμε τώρα τις επιλογές συλλογής λειτουργικών μονάδων (οι ορισμοί μακροεντολών θα εξηγηθούν αργότερα):

-Με- με αυτήν την επιλογή, ο μεταγλωττιστής gcc θα σταματήσει τη διαδικασία μεταγλώττισης αρχείου αμέσως μετά τη δημιουργία του αρχείου αντικειμένου, χωρίς να επιχειρήσει να δημιουργήσει ένα εκτελέσιμο δυαδικό.

-Τείχος- μέγιστο επίπεδο προειδοποίησης όταν εκτελείται το gcc.

-ΡΕ— ορισμοί μακροσυμβόλων. Το ίδιο με την οδηγία #define στο μεταγλωττισμένο αρχείο. Δεν έχει καμία απολύτως διαφορά πώς να ορίσετε τα σύμβολα μακροεντολών που χρησιμοποιούνται σε αυτήν την ενότητα, χρησιμοποιώντας το #define στο αρχείο προέλευσης ή χρησιμοποιώντας την επιλογή -D για τον μεταγλωττιστή.

-ΕΓΩ- πρόσθετες διαδρομές αναζήτησης για αρχεία συμπερίληψης. Σημειώστε τη χρήση της αντικατάστασης "uname -r", η οποία θα καθορίσει το ακριβές όνομα της έκδοσης του πυρήνα που χρησιμοποιείται αυτήν τη στιγμή.

Η επόμενη ενότητα παρέχει ένα άλλο παράδειγμα μιας ενότητας. Εξηγεί επίσης λεπτομερώς πώς να το εγκαταστήσετε και να το ξεφορτώσετε από τον πυρήνα.

Πρωτότυπο Hello world!

Τώρα ας δούμε τον αρχικό κώδικα για την απλή ενότητα "Hello, World" που προσφέρεται από τη Rubini & Corbet. Αυτός ο κώδικας μπορεί να μεταγλωττιστεί στις εκδόσεις του πυρήνα 2.0 έως 2.4. Αυτό το παράδειγμα, και όλα τα άλλα που παρουσιάζονται στο βιβλίο, είναι διαθέσιμα στην τοποθεσία FTP του O'Reilly (βλ. Κεφάλαιο 1).

//file hello.c #define MODULE #include int init_module(void) ( printk("<1>Γεια, κόσμο\n"); επιστροφή 0; ) void cleanup_module(void) ( printk("<1>Αντίο σκληρός κόσμος\n");)

Λειτουργία printk()ορίζεται στον πυρήνα του Linux και λειτουργεί ως τυπική λειτουργία βιβλιοθήκης printf()στη γλώσσα C. Ο πυρήνας χρειάζεται τη δική του, κατά προτίμηση μικρή, συνάρτηση συμπερασμάτων, που περιέχεται απευθείας στον πυρήνα και όχι σε βιβλιοθήκες σε επίπεδο χρήστη. Μια μονάδα μπορεί να καλέσει μια συνάρτηση printk()γιατί μετά τη φόρτωση της ενότητας χρησιμοποιώντας την εντολή ακατάστατοςΗ ενότητα επικοινωνεί με τον πυρήνα και έχει πρόσβαση σε δημοσιευμένες (εξαγόμενες) συναρτήσεις και μεταβλητές του πυρήνα.

Παράμετρος συμβολοσειράς "<1>” που μεταβιβάζεται στη συνάρτηση printk() είναι η προτεραιότητα του μηνύματος. Οι αρχικές αγγλικές πηγές χρησιμοποιούν τον όρο loglevel, που σημαίνει το επίπεδο καταγραφής μηνυμάτων. Εδώ, θα χρησιμοποιήσουμε τον όρο προτεραιότητα αντί του αρχικού "loglevel". Σε αυτό το παράδειγμα, χρησιμοποιούμε υψηλή προτεραιότητα για το μήνυμα που έχει χαμηλό αριθμό. Η υψηλή προτεραιότητα μηνύματος ορίζεται σκόπιμα επειδή ένα μήνυμα με την προεπιλεγμένη προτεραιότητα ενδέχεται να μην εμφανίζεται στην κονσόλα από την οποία εγκαταστάθηκε η λειτουργική μονάδα. Η κατεύθυνση εξόδου των μηνυμάτων του πυρήνα με προεπιλεγμένη προτεραιότητα εξαρτάται από την έκδοση του τρέχοντος πυρήνα, την έκδοση του δαίμονα klogdκαι τη διαμόρφωσή σας. Πιο αναλυτικά, εργασία με τη λειτουργία printk()θα εξηγήσουμε στο Κεφάλαιο 4, Τεχνικές εντοπισμού σφαλμάτων.

Μπορείτε να δοκιμάσετε τη μονάδα χρησιμοποιώντας την εντολή ακατάστατοςγια να εγκαταστήσετε τη μονάδα στον πυρήνα και τις εντολές rmmodγια να αφαιρέσετε μια ενότητα από τον πυρήνα. Παρακάτω θα δείξουμε πώς μπορεί να γίνει αυτό. Σε αυτήν την περίπτωση, το σημείο εισόδου init_module() εκτελείται όταν μια λειτουργική μονάδα είναι εγκατεστημένη στον πυρήνα και η cleanup_module() εκτελείται όταν αφαιρείται από τον πυρήνα. Να θυμάστε ότι μόνο ένας προνομιούχος χρήστης μπορεί να φορτώσει και να ξεφορτώσει μονάδες.

Το παραπάνω παράδειγμα λειτουργικής μονάδας μπορεί να χρησιμοποιηθεί μόνο με έναν πυρήνα που έχει δημιουργηθεί με απενεργοποιημένη τη σημαία "υποστήριξη έκδοσης μονάδας". Δυστυχώς, οι περισσότερες διανομές χρησιμοποιούν πυρήνες ελεγχόμενους από την έκδοση (αυτό συζητείται στην ενότητα "Έλεγχος έκδοσης σε μονάδες" του Κεφαλαίου 11, "kmod και Προηγμένη σπονδυλοποίηση"). Και αν και παλαιότερες εκδόσεις του πακέτου modutilsεπιτρέπουν τη φόρτωση τέτοιων λειτουργικών μονάδων σε πυρήνες ελεγχόμενους από την έκδοση, κάτι που δεν είναι πλέον δυνατό. Θυμηθείτε ότι το πακέτο modutils περιέχει ένα σύνολο προγραμμάτων που περιλαμβάνει τα προγράμματα insmod και rmmod.

Εργασία: Προσδιορίστε τον αριθμό έκδοσης και τη σύνθεση του πακέτου modutils από τη διανομή σας.

Όταν προσπαθείτε να εισαγάγετε μια τέτοια λειτουργική μονάδα σε έναν πυρήνα που υποστηρίζει έλεγχο έκδοσης, ενδέχεται να δείτε ένα μήνυμα σφάλματος παρόμοιο με το ακόλουθο:

# insmod hello.o hello.o: αναντιστοιχία έκδοσης πυρήνα-μονάδας hello.o μεταγλωττίστηκε για την έκδοση πυρήνα 2.4.20 ενώ αυτός ο πυρήνας είναι η έκδοση 2.4.20-9asp.

Στον κατάλογο διάφορες ενότητεςΠαραδείγματα από το ftp.oreilly.com θα βρείτε το αρχικό παράδειγμα προγράμματος hello.c, το οποίο περιέχει λίγο περισσότερες γραμμές και μπορεί να εγκατασταθεί τόσο σε πυρήνες που ελέγχονται από έκδοση όσο και σε μη εκδόσεις. Ωστόσο, συνιστούμε ανεπιφύλακτα να δημιουργήσετε τον δικό σας πυρήνα χωρίς υποστήριξη ελέγχου έκδοσης. Ταυτόχρονα, συνιστάται η λήψη των αρχικών πηγών πυρήνα στον ιστότοπο www.kernel.org

Εάν είστε νέοι στη συναρμολόγηση πυρήνων, δοκιμάστε να διαβάσετε το άρθρο που δημοσίευσε ο Alessandro Rubini (ένας από τους συγγραφείς του αρχικού βιβλίου) στη διεύθυνση http://www.linux.it/kerneldocs/kconf, το οποίο θα σας βοηθήσει να κατακτήσετε τη διαδικασία.

Εκτελέστε τις ακόλουθες εντολές σε μια κονσόλα κειμένου για να μεταγλωττίσετε και να δοκιμάσετε το αρχικό παράδειγμα ενότητας παραπάνω.

Root# gcc -c hello.c root# insmod ./hello.o Γεια σου, world root# rmmod γεια Αντίο cruel world root#

Ανάλογα με τον μηχανισμό που χρησιμοποιεί το σύστημά σας για τη μετάδοση συμβολοσειρών μηνυμάτων, η κατεύθυνση εξόδου των μηνυμάτων που αποστέλλονται από τη συνάρτηση printk(), μπορεί να διαφέρει. Στο συγκεκριμένο παράδειγμα μεταγλώττισης και δοκιμής μιας λειτουργικής μονάδας, τα μηνύματα που αποστέλλονταν από τη συνάρτηση printk() εξάγονταν στην ίδια κονσόλα από την οποία δόθηκαν οι εντολές εγκατάστασης και εκτέλεσης των λειτουργικών μονάδων. Αυτό το παράδειγμα λήφθηκε από μια κονσόλα κειμένου. Εάν εκτελέσετε τις εντολές ακατάστατοςΚαι rmmodκάτω από το πρόγραμμα xterm, τότε πιθανότατα δεν θα δείτε τίποτα στο τερματικό σας. Αντίθετα, το μήνυμα μπορεί να καταλήξει σε ένα από τα αρχεία καταγραφής του συστήματος, για παράδειγμα στο /var/log/messages.Το ακριβές όνομα του αρχείου εξαρτάται από τη διανομή. Δείτε την ώρα των αλλαγών στα αρχεία καταγραφής. Ο μηχανισμός που χρησιμοποιείται για τη μετάδοση μηνυμάτων από τη συνάρτηση printk() περιγράφεται στην ενότητα "Πώς καταγράφονται τα μηνύματα" στο Κεφάλαιο 4 "Τεχνικές"
αποσφαλμάτωση».

Για να προβάλετε μηνύματα μονάδας στο αρχείο καταγραφής συστήματος /val/log/messages είναι βολικό να το χρησιμοποιήσετε βοηθητικό πρόγραμμα συστήματος tail, το οποίο, από προεπιλογή, εμφανίζει τις τελευταίες 10 γραμμές του αρχείου που του διαβιβάστηκε. Μια ενδιαφέρουσα επιλογή αυτού του βοηθητικού προγράμματος είναι η επιλογή -f, η οποία εκτελεί το βοηθητικό πρόγραμμα στη λειτουργία παρακολούθησης των τελευταίων γραμμών του αρχείου, π.χ. Όταν εμφανιστούν νέες γραμμές στο αρχείο, θα εκτυπωθούν αυτόματα. Για να σταματήσετε την εκτέλεση της εντολής σε αυτήν την περίπτωση, πρέπει να πατήσετε Ctrl+C. Έτσι, για να προβάλετε τις τελευταίες δέκα γραμμές του αρχείου καταγραφής συστήματος, πληκτρολογήστε τα ακόλουθα στη γραμμή εντολών:

Root# tail /var/log/messages

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

Διαφορές μεταξύ λειτουργικών μονάδων πυρήνα και εφαρμογών

Η εφαρμογή έχει ένα σημείο εισόδου, το οποίο ξεκινά να εκτελείται αμέσως μετά την τοποθέτηση εκτελούμενη εφαρμογήστη μνήμη RAM του υπολογιστή. Αυτό το σημείο εισόδου περιγράφεται στο C ως η συνάρτηση main(). Ο τερματισμός της συνάρτησης main() σημαίνει τερματισμός της εφαρμογής. Η λειτουργική μονάδα έχει πολλά σημεία εισόδου που εκτελούνται κατά την εγκατάσταση και την αφαίρεση της λειτουργικής μονάδας από τον πυρήνα, καθώς και κατά την επεξεργασία αιτημάτων από τον χρήστη. Έτσι, το σημείο εισόδου init_module() εκτελείται όταν η μονάδα φορτώνεται στον πυρήνα. Η συνάρτηση cleanup_module() εκτελείται κατά την εκφόρτωση μιας λειτουργικής μονάδας. Στο μέλλον, θα εξοικειωθούμε με άλλα σημεία εισόδου στο module, τα οποία εκτελούνται κατά την εκτέλεση διαφόρων αιτημάτων στο module.

Η δυνατότητα φόρτωσης και εκφόρτωσης μονάδων είναι δύο πυλώνες του μηχανισμού σπονδυλοποίησης. Μπορούν να αξιολογηθούν με διαφορετικούς τρόπους. Για τον προγραμματιστή, αυτό σημαίνει, πρώτα απ 'όλα, μείωση του χρόνου ανάπτυξης, επειδή μπορείτε να δοκιμάσετε τη λειτουργικότητα του προγράμματος οδήγησης χωρίς μια μακρά διαδικασία επανεκκίνησης.

Ως προγραμματιστής, γνωρίζετε ότι μια εφαρμογή μπορεί να καλέσει μια συνάρτηση που δεν είχε δηλωθεί στην εφαρμογή. Στα στάδια της στατικής ή δυναμικής σύνδεσης προσδιορίζονται οι διευθύνσεις τέτοιων συναρτήσεων από τις αντίστοιχες βιβλιοθήκες. Λειτουργία printf()μία από αυτές τις καλέσιμες συναρτήσεις που ορίζεται στη βιβλιοθήκη libc. Μια λειτουργική μονάδα, από την άλλη πλευρά, σχετίζεται μόνο με τον πυρήνα και μπορεί να καλεί μόνο συναρτήσεις που εξάγονται από τον πυρήνα. Ο κώδικας που εκτελείται στον πυρήνα δεν μπορεί να χρησιμοποιήσει εξωτερικές βιβλιοθήκες. Έτσι, για παράδειγμα, η συνάρτηση printk(), που χρησιμοποιήθηκε στο παράδειγμα γεια.γ, είναι ανάλογο της γνωστής συνάρτησης printf(), διαθέσιμο σε εφαρμογές σε επίπεδο χρήστη. Λειτουργία printk()βρίσκεται στον πυρήνα και πρέπει να είναι όσο το δυνατόν μικρότερος. Επομένως, σε αντίθεση με την printf(), έχει πολύ περιορισμένη υποστήριξη για τύπους δεδομένων και, για παράδειγμα, δεν υποστηρίζει καθόλου αριθμούς κινητής υποδιαστολής.

Οι υλοποιήσεις πυρήνα 2.0 και 2.2 δεν υποστήριζαν προσδιοριστές τύπου μεγάλοΚαι Ζ. Παρουσιάστηκαν μόνο στην έκδοση 2.4 του πυρήνα.

Το σχήμα 2-1 δείχνει την υλοποίηση του μηχανισμού για την κλήση συναρτήσεων που είναι σημεία εισόδου στη μονάδα. Επίσης, αυτό το σχήμα δείχνει τον μηχανισμό αλληλεπίδρασης μιας εγκατεστημένης ή εγκατεστημένης μονάδας με τον πυρήνα.

Ρύζι. 2-1. Επικοινωνία μεταξύ της ενότητας και του πυρήνα

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

Ολόκληρη η διεπαφή του πυρήνα περιγράφεται σε αρχεία κεφαλίδας που βρίσκονται στους καταλόγους περιλαμβάνει/linuxΚαι περιλαμβάνω/ασμμέσα στις πηγές του πυρήνα (συνήθως βρίσκονται σε /usr/src/linux-x.y.z(Το x.y.z είναι η έκδοση του πυρήνα σας)). Παλαιότερες διανομές (με βάση libcέκδοση 5 ή μικρότερη) χρησιμοποίησε συμβολικούς συνδέσμους /usr/include/linuxΚαι /usr/include/asmστους αντίστοιχους καταλόγους στις πηγές του πυρήνα. Αυτοί οι συμβολικοί σύνδεσμοι καθιστούν δυνατή, εάν είναι απαραίτητο, τη χρήση διεπαφών πυρήνα σε εφαρμογές χρήστη.

Παρόλο που η διεπαφή των βιβλιοθηκών χώρου χρήστη είναι πλέον ξεχωριστή από τη διεπαφή πυρήνα, μερικές φορές οι διεργασίες χρήστη πρέπει να χρησιμοποιούν διεπαφές πυρήνα. Ωστόσο, πολλές αναφορές σε αρχεία κεφαλίδας πυρήνα αναφέρονται μόνο στον ίδιο τον πυρήνα και δεν θα πρέπει να είναι προσβάσιμες σε εφαρμογές χρήστη. Επομένως, αυτές οι διαφημίσεις προστατεύονται #ifdef __KERNEL__μπλοκ. Αυτός είναι ο λόγος για τον οποίο το πρόγραμμα οδήγησης σας, όπως και άλλοι κώδικας πυρήνα, πρέπει να μεταγλωττιστεί με μια δηλωμένη μακροεντολή __ΠΥΡΗΝΑΣ__.

Ο ρόλος των μεμονωμένων αρχείων κεφαλίδας του πυρήνα θα συζητηθεί όπως αρμόζει σε όλο το βιβλίο.

Οι προγραμματιστές που εργάζονται σε μεγάλα έργα λογισμικού (όπως ο πυρήνας) πρέπει να γνωρίζουν και να αποφεύγουν "ρύπανση χώρου ονομάτων". Αυτό το πρόβλημα παρουσιάζεται όταν υπάρχει μεγάλος αριθμός συναρτήσεων και καθολικών μεταβλητών των οποίων τα ονόματα δεν είναι αρκετά εκφραστικά (διακρίσιμα). Ο προγραμματιστής που αργότερα πρέπει να ασχοληθεί με τέτοιες εφαρμογές αναγκάζεται να αφιερώσει πολύ περισσότερο χρόνο για να θυμάται «δεσμευμένα» ονόματα και να βρίσκει μοναδικά ονόματα για νέα στοιχεία. Οι συγκρούσεις ονομάτων (ασάφειες) μπορούν να δημιουργήσουν ένα ευρύ φάσμα προβλημάτων, που κυμαίνονται από σφάλματα κατά τη φόρτωση μιας λειτουργικής μονάδας έως ασταθή ή ανεξήγητη συμπεριφορά προγράμματος που μπορεί να προκύψει σε χρήστες που χρησιμοποιούν πυρήνα ενσωματωμένο σε διαφορετική διαμόρφωση.

Οι προγραμματιστές δεν μπορούν να αντέξουν οικονομικά τέτοια λάθη όταν γράφουν κώδικα πυρήνα, επειδή ακόμη και η μικρότερη ενότητα θα συνδεθεί με ολόκληρο τον πυρήνα. Η καλύτερη λύση για να αποφύγετε τις συγκρούσεις ονομάτων είναι να δηλώσετε πρώτα τα αντικείμενα του προγράμματος ως στατικός, και, δεύτερον, η χρήση ενός μοναδικού, εντός του συστήματος, προθέματος για την ονομασία καθολικών αντικειμένων. Επιπλέον, ως προγραμματιστής λειτουργικών μονάδων, μπορείτε να ελέγξετε το εύρος των αντικειμένων στον κώδικά σας, όπως περιγράφεται παρακάτω στην ενότητα "Πίνακας συνδέσμων πυρήνα".

Οι περισσότερες (αλλά όχι όλες) εκδόσεις της εντολής ακατάστατοςεξαγωγή όλων των αντικειμένων της μονάδας που δεν έχουν δηλωθεί ως στατικός, από προεπιλογή, δηλ. εκτός εάν η ενότητα ορίζει ειδικές οδηγίες για το σκοπό αυτό. Επομένως, είναι πολύ λογικό να δηλώνετε αντικείμενα λειτουργικής μονάδας που δεν σκοπεύετε να εξαγάγετε ως στατικός.

Η χρήση ενός μοναδικού προθέματος για τοπικά αντικείμενα μέσα σε μια ενότητα μπορεί να είναι μια καλή πρακτική, καθώς διευκολύνει τον εντοπισμό σφαλμάτων. Κατά τη δοκιμή του προγράμματος οδήγησης, ίσως χρειαστεί να εξάγετε πρόσθετα αντικείμενα στον πυρήνα. Χρησιμοποιώντας ένα μοναδικό πρόθεμα για τον προσδιορισμό ονομάτων, δεν κινδυνεύετε να εισάγετε συγκρούσεις στον χώρο ονομάτων του πυρήνα. Τα προθέματα που χρησιμοποιούνται στον πυρήνα είναι, κατά σύμβαση, πεζοί χαρακτήρες, και θα παραμείνουμε σε αυτήν τη σύμβαση.

Μια άλλη σημαντική διαφορά μεταξύ του πυρήνα και των διεργασιών χρήστη είναι ο μηχανισμός διαχείρισης σφαλμάτων. Ο πυρήνας ελέγχει την εκτέλεση της διαδικασίας χρήστη, επομένως ένα σφάλμα στη διαδικασία χρήστη οδηγεί σε ένα μήνυμα που είναι ακίνδυνο για το σύστημα: σφάλμα κατάτμησης. Ταυτόχρονα, ένα πρόγραμμα εντοπισμού σφαλμάτων μπορεί πάντα να χρησιμοποιηθεί για την παρακολούθηση σφαλμάτων στον πηγαίο κώδικα της εφαρμογής χρήστη. Τα σφάλματα που εμφανίζονται στον πυρήνα είναι μοιραία - αν όχι για ολόκληρο το σύστημα, τουλάχιστον για την τρέχουσα διαδικασία. Στην ενότητα «Σφάλματα συστήματος εντοπισμού σφαλμάτων» του Κεφαλαίου 4, «Τεχνικές εντοπισμού σφαλμάτων», θα εξετάσουμε τρόπους παρακολούθησης σφαλμάτων πυρήνα.

Χώρος χρήστη και χώρος πυρήνα

Η ενότητα τρέχει στο λεγόμενο χώρο πυρήνα, ενώ οι εφαρμογές εκτελούνται σε . Αυτή η έννοια είναι η βάση της θεωρίας των λειτουργικών συστημάτων.

Ένας από τους κύριους σκοπούς του λειτουργικού συστήματος είναι να παρέχει στον χρήστη και τα προγράμματα χρήστη πόρους υπολογιστή, οι περισσότεροι από τους οποίους αντιπροσωπεύονται από εξωτερικές συσκευές. Το λειτουργικό σύστημα δεν πρέπει μόνο να παρέχει πρόσβαση σε πόρους, αλλά και να ελέγχει την κατανομή και τη χρήση τους, αποτρέποντας συγκρούσεις και μη εξουσιοδοτημένη πρόσβαση. Επιπροσθέτως, λειτουργικό σύστημαμπορεί να δημιουργήσει ανεξάρτητες λειτουργίες για προγράμματα και να προστατεύσει από μη εξουσιοδοτημένη πρόσβαση σε πόρους. Η επίλυση αυτού του μη τετριμμένου προβλήματος είναι δυνατή μόνο εάν ο επεξεργαστής προστατεύει τα προγράμματα του συστήματος από εφαρμογές χρηστών.

Σχεδόν κάθε σύγχρονος επεξεργαστής είναι σε θέση να παρέχει τέτοιο διαχωρισμό εφαρμόζοντας διαφορετικά επίπεδα προνομίων για τον κώδικα εκτέλεσης (απαιτούνται τουλάχιστον δύο επίπεδα). Για παράδειγμα, οι επεξεργαστές αρχιτεκτονικής I32 έχουν τέσσερα επίπεδα προνομίων από το 0 έως το 3. Επιπλέον, το επίπεδο 0 έχει τα υψηλότερα προνόμια. Για τέτοιους επεξεργαστές, υπάρχει μια κατηγορία προνομιακών εντολών που μπορούν να εκτελεστούν μόνο σε προνομιακά επίπεδα. Τα συστήματα Unix χρησιμοποιούν δύο επίπεδα προνομίων επεξεργαστή. Εάν ένας επεξεργαστής έχει περισσότερα από δύο επίπεδα προνομίων, χρησιμοποιούνται το χαμηλότερο και το υψηλότερο. Ο πυρήνας Unix εκτελείται υψηλότερο επίπεδοπρονόμια, διασφαλίζοντας τον έλεγχο του εξοπλισμού και των διαδικασιών του χρήστη.

Όταν μιλάμε για χώρο πυρήναΚαι χώρο διαδικασίας χρήστηΑυτό σημαίνει όχι μόνο διαφορετικά επίπεδα προνομίων για τον εκτελέσιμο κώδικα, αλλά και διαφορετικούς χώρους διευθύνσεων.

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

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

Παραλληλισμός στον πυρήνα

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

Υπάρχουν πολλοί λόγοι για τον παραλληλισμό του κώδικα πυρήνα. Το Linux έχει συνήθως πολλές διεργασίες που εκτελούνται και ορισμένες από αυτές μπορεί να προσπαθήσουν να αποκτήσουν πρόσβαση στον κώδικα της μονάδας σας ταυτόχρονα. Πολλές συσκευές μπορεί να προκαλέσουν διακοπές υλικού στον επεξεργαστή. Οι χειριστές διακοπών καλούνται ασύγχρονα και μπορούν να κληθούν ενώ το πρόγραμμα οδήγησης εκτελεί άλλο αίτημα. Ορισμένες αφαιρέσεις λογισμικού (όπως τα χρονόμετρα πυρήνα, που εξηγούνται στο Κεφάλαιο 6, «Ροή του χρόνου») εκτελούνται επίσης ασύγχρονα. Επιπλέον, το Linux μπορεί να εκτελεστεί σε ένα σύστημα με συμμετρικούς πολυεπεξεργαστές (SMP), πράγμα που σημαίνει ότι ο κώδικας του προγράμματος οδήγησης μπορεί να εκτελεστεί παράλληλα σε πολλούς επεξεργαστές ταυτόχρονα.

Για αυτούς τους λόγους, ο κώδικας του πυρήνα του Linux, συμπεριλαμβανομένου του κώδικα προγράμματος οδήγησης, πρέπει να επανεισάγεται, δηλ. πρέπει να μπορεί να λειτουργεί με περισσότερα από ένα περιβάλλοντα δεδομένων ταυτόχρονα. Οι δομές δεδομένων πρέπει να είναι σχεδιασμένες ώστε να επιτρέπουν την παράλληλη εκτέλεση πολλαπλών νημάτων. Με τη σειρά του, ο κώδικας του πυρήνα πρέπει να μπορεί να χειρίζεται πολλές παράλληλες ροές δεδομένων χωρίς να τις καταστρέφει. Η εγγραφή κώδικα που μπορεί να εκτελεστεί παράλληλα και η αποφυγή καταστάσεων στις οποίες μια διαφορετική ακολουθία εκτέλεσης θα οδηγούσε σε ανεπιθύμητη συμπεριφορά του συστήματος απαιτεί πολύ χρόνο και ίσως πολύ κόλπο. Κάθε παράδειγμα προγράμματος οδήγησης σε αυτό το βιβλίο είναι γραμμένο έχοντας κατά νου την παράλληλη εκτέλεση. Εάν είναι απαραίτητο, θα εξηγήσουμε τις ιδιαιτερότητες της τεχνικής για τη σύνταξη τέτοιου κώδικα.

Πλέον γενικό λάθοςΤο πρόβλημα που κάνουν οι προγραμματιστές είναι ότι υποθέτουν ότι η ταυτόχρονη λειτουργία δεν είναι πρόβλημα επειδή ορισμένα τμήματα κώδικα δεν μπορούν να μεταβούν σε κατάσταση αναστολής λειτουργίας. Πράγματι, ο πυρήνας του Linux δεν είναι σελιδοποιημένος, με σημαντική εξαίρεση τους χειριστές διακοπής, οι οποίοι δεν μπορούν να αποκτήσουν την CPU ενώ εκτελείται ο κρίσιμος κώδικας του πυρήνα. Πρόσφατα, η μη σελιδοποίηση ήταν αρκετή για να αποτρέψει την ανεπιθύμητη παραλληλοποίηση στις περισσότερες περιπτώσεις. Στα συστήματα SMP, ωστόσο, δεν απαιτείται λήψη κώδικα λόγω παράλληλων υπολογισμών.

Εάν ο κώδικάς σας υποθέσει ότι δεν θα ξεφορτωθεί, τότε δεν θα λειτουργήσει σωστά σε συστήματα SMP. Ακόμα κι αν δεν έχετε τέτοιο σύστημα, κάποιος άλλος που χρησιμοποιεί τον κωδικό σας μπορεί να έχει. Είναι επίσης πιθανό στο μέλλον ο πυρήνας να χρησιμοποιεί τη δυνατότητα σελιδοποίησης, επομένως ακόμη και τα συστήματα ενός επεξεργαστή θα πρέπει να αντιμετωπίσουν τη συγχρονικότητα σε όλη τη διάρκεια. Υπάρχουν ήδη επιλογές για την υλοποίηση τέτοιων πυρήνων. Έτσι, ένας συνετός προγραμματιστής θα γράψει κώδικα πυρήνα με την υπόθεση ότι θα εκτελεστεί σε ένα σύστημα που εκτελεί SMP.

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

Ένα συνηθισμένο λάθος που κάνουν οι προγραμματιστές προγραμμάτων οδήγησης είναι να υποθέτουν ότι η ταυτόχρονη λειτουργία δεν αποτελεί πρόβλημα εφόσον ένα συγκεκριμένο τμήμα κώδικα
δενπηγαίνετε για ύπνο (ή "μπλοκάρετε"). Είναι αλήθεια ότι ο πυρήνας του Linux δεν είναι προληπτικός. με σημαντική εξαίρεση το
διακοπές συντήρησης, δεν θα απομακρύνει τον επεξεργαστή από τον κώδικα του πυρήνα που δεν αποδίδει ηθελημένα. Σε προηγούμενες εποχές, αυτό δεν ήταν προληπτικό
Η συμπεριφορά ήταν αρκετή για να αποτρέψει ανεπιθύμητο συγχρονισμό τις περισσότερες φορές. Στα συστήματα SMP, ωστόσο, δεν απαιτείται προκαταβολή για την πρόκληση
ταυτόχρονη εκτέλεση.

Εάν ο κώδικάς σας υποθέσει ότι δεν θα γίνει προνόμιο, δεν θα εκτελεστεί σωστά σε συστήματα SMP. Ακόμα κι αν δεν έχετε τέτοιο σύστημα,
άλλοι που εκτελούν τον κωδικό σας μπορεί να έχουν έναν. Στο μέλλον, είναι επίσης πιθανό ο πυρήνας να μετακινηθεί σε προληπτικό τρόπο λειτουργίας,
οπότε ακόμη και τα συστήματα μονοεπεξεργαστή θα πρέπει να αντιμετωπίσουν παντού την ταυτόχρονη λειτουργία (ορισμένες παραλλαγές του πυρήνα έχουν ήδη εφαρμοστεί
το).

Πληροφορίες για την τρέχουσα διαδικασία

Αν και ο κώδικας μονάδας πυρήνα δεν εκτελείται διαδοχικά όπως οι εφαρμογές, οι περισσότερες κλήσεις στον πυρήνα εκτελούνται σε σχέση με τη διαδικασία που τον καλεί. Ο κώδικας πυρήνα μπορεί να αναγνωρίσει τη διεργασία που τον κάλεσε με πρόσβαση σε έναν καθολικό δείκτη που οδηγεί στη δομή struct task_struct, που ορίζεται για πυρήνες έκδοση 2.4, στο αρχείο συμπεριλαμβανεται σε . Δείκτης ρεύμαυποδεικνύει την τρέχουσα διαδικασία χρήστη. Κατά την εκτέλεση κλήσεων συστήματος όπως π.χ Άνοιξε()ή Κλείσε(), πρέπει να υπάρχει μια διαδικασία που τα προκάλεσε. Ο κώδικας πυρήνα μπορεί, εάν είναι απαραίτητο, να καλέσει συγκεκριμένες πληροφορίες σχετικά με τη διαδικασία κλήσης μέσω ενός δείκτη ρεύμα. Για παραδείγματα χρήσης αυτού του δείκτη, ανατρέξτε στην ενότητα «Έλεγχος πρόσβασης αρχείων συσκευής» στο Κεφάλαιο 5, «Λειτουργίες βελτιωμένων προγραμμάτων οδήγησης χαρακτήρων».

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

Από την άποψη της ενότητας, ρεύμαμοιάζει με εξωτερικό σύνδεσμο printk(). Η μονάδα μπορεί να χρησιμοποιήσει ρεύμαόπου χρειάζεται. Για παράδειγμα, το ακόλουθο κομμάτι κώδικα εκτυπώνει το αναγνωριστικό διεργασίας (PID) και το όνομα εντολής της διεργασίας που κάλεσε τη μονάδα, αποκτώντας τα μέσω των αντίστοιχων πεδίων της δομής struct task_struct:

Printk("Η διαδικασία είναι \"%s\" (pid %i)\n", current->comm, current->pid);

Το πεδίο current->comm είναι το όνομα του αρχείου εντολών που προκάλεσε την τρέχουσα διαδικασία.

Μεταγλώττιση και φόρτωση ενοτήτων

Το υπόλοιπο αυτού του κεφαλαίου είναι αφιερωμένο στη σύνταξη μιας πλήρους, αν και άτυπης, ενότητας. Εκείνοι. Η ενότητα δεν ανήκει σε καμία από τις κλάσεις που περιγράφονται στην ενότητα "Τάξεις συσκευών και μονάδων" στο Κεφάλαιο 1, "Εισαγωγή στα προγράμματα οδήγησης συσκευών". Το παράδειγμα προγράμματος οδήγησης που εμφανίζεται σε αυτό το κεφάλαιο θα ονομάζεται κρανίο (Simple Kernel Utility for Loading Localities). Μπορείτε να χρησιμοποιήσετε τη μονάδα κρανίου ως πρότυπο για να γράψετε τον δικό σας τοπικό κώδικα.

Χρησιμοποιούμε την έννοια του "τοπικού κώδικα" (τοπικό) για να τονίσουμε τις αλλαγές στον προσωπικό σας κώδικα, στην παλιά καλή παράδοση του Unix (/usr/local).

Ωστόσο, προτού συμπληρώσουμε τις συναρτήσεις init_module() και cleanup_module(), θα γράψουμε ένα σενάριο Makefile που θα χρησιμοποιήσει το make για να δημιουργήσει τον κώδικα αντικειμένου της λειτουργικής μονάδας.

Για να μπορέσει ο προεπεξεργαστής να επεξεργαστεί τη συμπερίληψη οποιουδήποτε αρχείου κεφαλίδας, το σύμβολο μακροεντολής __KERNEL__ πρέπει να οριστεί με μια οδηγία #define. Όπως αναφέρθηκε προηγουμένως, ένα συγκεκριμένο πλαίσιο για τον πυρήνα μπορεί να οριστεί σε αρχεία διεπαφής πυρήνα, ορατό μόνο εάν το σύμβολο __KERNEL__ είναι προκαθορισμένο στην προεπεξεργασία.

Ένα άλλο σημαντικό σύμβολο που ορίζεται από την οδηγία #define είναι το σύμβολο MODULE. Πρέπει να οριστεί πριν από την ενεργοποίηση της διεπαφής (εξαιρουμένων εκείνων των προγραμμάτων οδήγησης που θα μεταγλωττιστούν με τον πυρήνα). Τα προγράμματα οδήγησης που συναρμολογούνται στον πυρήνα δεν θα περιγραφούν σε αυτό το βιβλίο, επομένως το σύμβολο MODULE θα υπάρχει σε όλα τα παραδείγματά μας.

Εάν δημιουργείτε μια λειτουργική μονάδα για ένα σύστημα με SMP, πρέπει επίσης να ορίσετε το σύμβολο μακροεντολής __SMP__ πριν ενεργοποιήσετε τις διεπαφές του πυρήνα. Στην έκδοση 2.2 του πυρήνα, ένα ξεχωριστό στοιχείο στη διαμόρφωση του πυρήνα εισήγαγε την επιλογή μεταξύ ενός συστήματος ενός επεξεργαστή και ενός συστήματος πολλαπλών επεξεργαστών. Επομένως, η συμπερίληψη των ακόλουθων γραμμών ως των πρώτων γραμμών της μονάδας σας θα έχει ως αποτέλεσμα την υποστήριξη πολλών επεξεργαστών.

#περιλαμβάνω #ifdef CONFIG_SMP # ορίστε __SMP__ #endif

Οι προγραμματιστές μονάδων θα πρέπει επίσης να ορίσουν τη σημαία βελτιστοποίησης -O για τον μεταγλωττιστή επειδή πολλές συναρτήσεις δηλώνονται ενσωματωμένες στα αρχεία κεφαλίδας του πυρήνα. Ο μεταγλωττιστής gcc δεν εκτελεί ενσωματωμένη επέκταση σε συναρτήσεις εκτός εάν είναι ενεργοποιημένη η βελτιστοποίηση. Επιτρέποντας την επέκταση των ενσωματωμένων αντικαταστάσεων χρησιμοποιώντας τις επιλογές -g και -O θα σας επιτρέψει να διορθώσετε αργότερα κώδικα που χρησιμοποιεί ενσωματωμένες συναρτήσεις στο πρόγραμμα εντοπισμού σφαλμάτων. Εφόσον ο πυρήνας χρησιμοποιεί εκτενώς τις ενσωματωμένες συναρτήσεις, είναι πολύ σημαντικό να επεκταθούν σωστά.

Σημειώστε, ωστόσο, ότι η χρήση οποιασδήποτε βελτιστοποίησης πάνω από το επίπεδο -O2 είναι επικίνδυνη επειδή ο μεταγλωττιστής μπορεί να επεκτείνει συναρτήσεις που δεν δηλώνονται ενσωματωμένες. Αυτό μπορεί να οδηγήσει σε προβλήματα γιατί... Κάποιος κωδικός συνάρτησης αναμένει να βρει την τυπική στοίβα της κλήσης του. Μια ενσωματωμένη επέκταση νοείται ως εισαγωγή ενός κωδικού συνάρτησης στο σημείο της κλήσης αντί για την αντίστοιχη εντολή κλήσης συνάρτησης. Αντίστοιχα, σε αυτή την περίπτωση, εφόσον δεν υπάρχει κλήση συνάρτησης, τότε δεν υπάρχει στοίβα της κλήσης της.

Ίσως χρειαστεί να βεβαιωθείτε ότι χρησιμοποιείτε τον ίδιο μεταγλωττιστή για τη μεταγλώττιση λειτουργικών μονάδων όπως χρησιμοποιήθηκε για τη δημιουργία του πυρήνα στον οποίο θα εγκατασταθεί η λειτουργική μονάδα. Για λεπτομέρειες, δείτε το πρωτότυπο έγγραφο από το αρχείο Τεκμηρίωση/Αλλαγέςπου βρίσκεται στον κατάλογο πηγών πυρήνα. Η ανάπτυξη πυρήνα και μεταγλωττιστή συνήθως συγχρονίζεται μεταξύ των ομάδων ανάπτυξης. Μπορεί να υπάρχουν περιπτώσεις όπου η ενημέρωση ενός από αυτά τα στοιχεία αποκαλύπτει σφάλματα σε ένα άλλο. Ορισμένοι κατασκευαστές διανομής παρέχουν εξαιρετικά νέες εκδόσεις του μεταγλωττιστή που δεν ταιριάζουν με τον πυρήνα που χρησιμοποιούν. Σε αυτήν την περίπτωση, συνήθως παρέχουν ένα ξεχωριστό πακέτο (που συχνά ονομάζεται kgcc) με έναν μεταγλωττιστή ειδικά σχεδιασμένο για
συλλογή πυρήνα.

Τέλος, για την αποφυγή δυσάρεστων σφαλμάτων, σας προτείνουμε να χρησιμοποιήσετε την επιλογή compile -Τείχος(όλες οι προειδοποιήσεις - όλες οι προειδοποιήσεις). Για να ικανοποιήσετε όλες αυτές τις προειδοποιήσεις, ίσως χρειαστεί να αλλάξετε το συνηθισμένο στυλ προγραμματισμού σας. Όταν γράφετε κώδικα πυρήνα, είναι προτιμότερο να χρησιμοποιείτε το στυλ κωδικοποίησης που προτείνει ο Linus Torvalds. Ναι, έγγραφο Τεκμηρίωση/Κωδικοποίηση,από τον κατάλογο πηγής πυρήνα, είναι αρκετά ενδιαφέρον και προτείνεται σε όλους όσους ενδιαφέρονται για τον προγραμματισμό σε επίπεδο πυρήνα.

Συνιστάται να τοποθετήσετε ένα σύνολο σημαιών συλλογής λειτουργικών μονάδων, με τις οποίες γνωριστήκαμε πρόσφατα, σε μια μεταβλητή CFLAGSτο Makefile σας. Για το βοηθητικό πρόγραμμα make, αυτή είναι μια ειδική μεταβλητή, η χρήση της οποίας θα γίνει σαφής από την ακόλουθη περιγραφή.

Εκτός από τις σημαίες στη μεταβλητή CFLAGS, μπορεί να χρειαστείτε έναν στόχο στο Makefile σας που να συνδυάζει διαφορετικά αρχεία αντικειμένων. Ένας τέτοιος στόχος είναι απαραίτητος μόνο όταν ο κώδικας της μονάδας χωρίζεται σε πολλά αρχεία πηγής, κάτι που γενικά δεν είναι ασυνήθιστο. Τα αρχεία αντικειμένων συνδυάζονται με την εντολή ld -r, η οποία δεν είναι μια λειτουργία σύνδεσης με τη γενικά αποδεκτή έννοια, παρά τη χρήση ενός συνδετήρα ( ld). Το αποτέλεσμα της εκτέλεσης της εντολής ld -rείναι ένα άλλο αρχείο αντικειμένων που συνδυάζει τους κωδικούς αντικειμένων των αρχείων εισόδου του συνδέτη. Επιλογή -rπου σημαίνει " μετατοπίσιμος - μετεγκατάσταση», δηλ. Μετακινούμε το αρχείο εξόδου της εντολής στο χώρο διευθύνσεων, γιατί δεν περιέχει ακόμη απόλυτες διευθύνσεις κλήσεων συναρτήσεων.

Το ακόλουθο παράδειγμα δείχνει το ελάχιστο Makefile που απαιτείται για τη μεταγλώττιση μιας λειτουργικής μονάδας που αποτελείται από δύο αρχεία προέλευσης. Εάν η ενότητα σας αποτελείται από ένα ενιαίο αρχείο προέλευσης, τότε από το παράδειγμα που δίνεται πρέπει να αφαιρέσετε τον στόχο που περιέχει την εντολή ld -r.

# Η διαδρομή προς τον κατάλογο προέλευσης του πυρήνα μπορεί να αλλάξει εδώ, # ή μπορείτε να τη μεταβιβάσετε ως παράμετρο όταν καλείτε "make" KERNELDIR = /usr/src/linux include $(KERNELDIR)/.config CFLAGS = -D__KERNEL__ -DMODULE - I$(KERNELDIR) /include \ -O -Wall ifdef CONFIG_SMP CFLAGS += -D__SMP__ -DSMP endif all: skull.o skull.o: skull_init.o skull_clean.o $(LD) -r $^ -o $@ καθαρό : rm -f * .o *~πυρήνας

Εάν είστε νέοι στο πώς λειτουργεί το make, μπορεί να εκπλαγείτε που δεν υπάρχουν κανόνες για τη μεταγλώττιση αρχείων *.c σε αρχεία αντικειμένων *.o. Ο καθορισμός τέτοιων κανόνων δεν είναι απαραίτητος, γιατί το βοηθητικό πρόγραμμα make, εάν είναι απαραίτητο, μετατρέπει το ίδιο τα αρχεία *.c σε αρχεία *.o χρησιμοποιώντας τον προεπιλεγμένο μεταγλωττιστή ή τον μεταγλωττιστή που καθορίζεται από μια μεταβλητή $(CC). Σε αυτή την περίπτωση, τα περιεχόμενα της μεταβλητής $ (CFLAGS)χρησιμοποιείται για τον καθορισμό σημαιών μεταγλώττισης.

Το επόμενο βήμα μετά τη δημιουργία μιας ενότητας είναι να τη φορτώσετε στον πυρήνα. Έχουμε ήδη πει ότι για αυτό θα χρησιμοποιήσουμε το βοηθητικό πρόγραμμα insmod, το οποίο συσχετίζει όλα τα απροσδιόριστα σύμβολα (κλήσεις συναρτήσεων κ.λπ.) του module με τον πίνακα συμβόλων του τρέχοντος πυρήνα. Ωστόσο, σε αντίθεση με έναν σύνδεσμο (για παράδειγμα, όπως το ld), δεν αλλάζει το αρχείο του δίσκου της μονάδας, αλλά φορτώνει το αντικείμενο της μονάδας που είναι συνδεδεμένο με τον πυρήνα στη μνήμη RAM. Το βοηθητικό πρόγραμμα insmod μπορεί να δεχτεί ορισμένες επιλογές γραμμής εντολών. Μπορείτε να δείτε λεπτομέρειες μέσω άνθρωπος ασθμαίνων. Χρησιμοποιώντας αυτές τις επιλογές, μπορείτε, για παράδειγμα, να αντιστοιχίσετε συγκεκριμένες μεταβλητές ακέραιου αριθμού και συμβολοσειρών στη λειτουργική μονάδα σας σε καθορισμένες τιμές πριν συνδέσετε τη λειτουργική μονάδα στον πυρήνα. Έτσι, εάν η μονάδα έχει σχεδιαστεί σωστά, μπορεί να ρυθμιστεί κατά την εκκίνηση. Αυτή η μέθοδος διαμόρφωσης μιας ενότητας δίνει στο χρήστη μεγαλύτερη ευελιξία από τη διαμόρφωση κατά τη στιγμή της μεταγλώττισης. Η διαμόρφωση του χρόνου εκκίνησης επεξηγείται στην ενότητα "Μη αυτόματη και αυτόματη διαμόρφωση" παρακάτω σε αυτό το κεφάλαιο.

Ορισμένοι αναγνώστες θα ενδιαφέρονται για τις λεπτομέρειες του τρόπου λειτουργίας του βοηθητικού προγράμματος insmod. Η υλοποίηση του insmod βασίζεται σε πολλές κλήσεις συστήματος που ορίζονται στο kernel/module.c. Η συνάρτηση sys_create_module() εκχωρεί την απαιτούμενη ποσότητα μνήμης στο χώρο διευθύνσεων του πυρήνα για τη φόρτωση της λειτουργικής μονάδας. Αυτή η μνήμη εκχωρείται χρησιμοποιώντας τη συνάρτηση vmalloc() (δείτε την ενότητα «vmalloc και φίλοι» στο Κεφάλαιο 7, «Αποκτήστε τη μνήμη»). Η κλήση συστήματος get_kernel_sysms() επιστρέφει τον πίνακα συμβόλων του πυρήνα, ο οποίος θα χρησιμοποιηθεί για τον προσδιορισμό των πραγματικών διευθύνσεων των αντικειμένων κατά τη σύνδεση. Η συνάρτηση sys_init_module() αντιγράφει τον κώδικα αντικειμένου της μονάδας στον χώρο διευθύνσεων του πυρήνα και καλεί τη συνάρτηση αρχικοποίησης της λειτουργικής μονάδας.

Αν κοιτάξετε τις πηγές κώδικα του πυρήνα, θα βρείτε ονόματα κλήσεων συστήματος που ξεκινούν με το πρόθεμα sys_. Αυτό το πρόθεμα χρησιμοποιείται μόνο για κλήσεις συστήματος. Δεν πρέπει να το χρησιμοποιούν άλλες λειτουργίες. Λάβετε αυτό υπόψη κατά την επεξεργασία πηγών κώδικα πυρήνα με το βοηθητικό πρόγραμμα αναζήτησης grep.

Εξαρτήσεις έκδοσης

Εάν δεν γνωρίζετε τίποτα περισσότερο από αυτό που καλύπτεται εδώ, τότε πιθανότατα τα modules που δημιουργείτε θα πρέπει να μεταγλωττιστούν εκ νέου για κάθε έκδοση του πυρήνα στον οποίο συνδέονται. Κάθε ενότητα πρέπει να ορίζει ένα σύμβολο που ονομάζεται __module_kernel_version, του οποίου η αξία
συγκρίνεται με την έκδοση του τρέχοντος πυρήνα χρησιμοποιώντας το βοηθητικό πρόγραμμα insmod. Αυτό το σύμβολο βρίσκεται στην ενότητα .modinfoΑρχεία ELF (Εκτέλεση και Μορφή σύνδεσης). Αυτό εξηγείται με περισσότερες λεπτομέρειες στο Κεφάλαιο 11 «kmod και προηγμένη αρθροποίηση». Λάβετε υπόψη ότι αυτή η μέθοδος ελέγχου έκδοσης ισχύει μόνο για τις εκδόσεις πυρήνα 2.2 και 2.4. Στον πυρήνα 2.0 αυτό γίνεται με λίγο διαφορετικό τρόπο.

Ο μεταγλωττιστής θα ορίσει αυτό το σύμβολο όπου κι αν περιλαμβάνεται το αρχείο κεφαλίδας . Επομένως, στο παράδειγμα hello.c που δόθηκε νωρίτερα, δεν περιγράψαμε αυτό το σύμβολο. Αυτό σημαίνει επίσης ότι εάν η ενότητα σας αποτελείται από πολλά αρχεία προέλευσης, πρέπει να συμπεριλάβετε το αρχείο στον κωδικό σας μόνο μία φορά. Εξαίρεση αποτελεί η περίπτωση κατά τη χρήση του ορισμού __ΟΧΙ_ΕΚΔΟΣΗ__, που θα γνωρίσουμε αργότερα.

Παρακάτω είναι ο ορισμός του περιγραφόμενου συμβόλου από το αρχείο module.h που εξάγεται από τον κώδικα του πυρήνα 2.4.25.

Static const char __module_kernel_versio/PRE__attribute__((section(.modinfo"))) = "kernel_version=" UTS_RELEASE;

Εάν μια λειτουργική μονάδα αποτύχει να φορτώσει λόγω αναντιστοιχίας έκδοσης, μπορείτε να προσπαθήσετε να φορτώσετε αυτήν την ενότητα περνώντας το κλειδί insmod στη γραμμή παραμέτρων του βοηθητικού προγράμματος -φά(δύναμη). Αυτή η μέθοδος φόρτωσης μιας μονάδας δεν είναι ασφαλής και δεν είναι πάντα επιτυχής. Είναι αρκετά δύσκολο να εξηγηθούν οι λόγοι για πιθανές αποτυχίες. Είναι πιθανό η μονάδα να μην φορτώσει επειδή τα σύμβολα δεν μπορούν να επιλυθούν κατά τη σύνδεση. Σε αυτήν την περίπτωση, θα λάβετε ένα κατάλληλο μήνυμα σφάλματος. Οι λόγοι για την αποτυχία μπορεί επίσης να βρίσκονται σε αλλαγές στη λειτουργία ή τη δομή του πυρήνα. Σε αυτήν την περίπτωση, η φόρτωση της μονάδας μπορεί να οδηγήσει σε σοβαρά σφάλματα χρόνου εκτέλεσης, καθώς και σε πανικό στο σύστημα. Το τελευταίο θα πρέπει να χρησιμεύσει ως καλό κίνητρο για τη χρήση ενός συστήματος ελέγχου έκδοσης. Οι αναντιστοιχίες εκδόσεων μπορούν να αντιμετωπιστούν πιο κομψά χρησιμοποιώντας τον έλεγχο έκδοσης στον πυρήνα. Θα μιλήσουμε για αυτό λεπτομερώς στην ενότητα "Έλεγχος έκδοσης σε μονάδες" στο Κεφάλαιο 11 "kmod και προηγμένη αρθροποίηση".

Εάν θέλετε να μεταγλωττίσετε την ενότητα σας για μια συγκεκριμένη έκδοση πυρήνα, πρέπει να συμπεριλάβετε τα αρχεία κεφαλίδας για τη συγκεκριμένη έκδοση του πυρήνα. Στο παράδειγμα Makefile που περιγράφεται παραπάνω, η μεταβλητή χρησιμοποιήθηκε για τον προσδιορισμό του καταλόγου για αυτά τα αρχεία KERNELDIR. Τέτοια προσαρμοσμένη μεταγλώττιση δεν είναι ασυνήθιστη όταν υπάρχουν διαθέσιμες πηγές πυρήνα. Επίσης, δεν είναι ασυνήθιστο να υπάρχουν διαφορετικές εκδόσεις του πυρήνα στο δέντρο καταλόγου. Όλα τα παραδείγματα ενότητας σε αυτό το βιβλίο χρησιμοποιούν τη μεταβλητή KERNELDIRγια να υποδείξετε τη θέση του καταλόγου προέλευσης για την έκδοση του πυρήνα στην οποία υποτίθεται ότι είναι συνδεδεμένη η συναρμολογημένη μονάδα. Μπορείτε να χρησιμοποιήσετε μια μεταβλητή συστήματος για να καθορίσετε αυτόν τον κατάλογο ή μπορείτε να περάσετε τη θέση του μέσω των επιλογών της γραμμής εντολών για δημιουργία.

Κατά τη φόρτωση μιας λειτουργικής μονάδας, το βοηθητικό πρόγραμμα insmod χρησιμοποιεί τις δικές του διαδρομές αναζήτησης για τα αρχεία αντικειμένων της λειτουργικής μονάδας, αναζητώντας καταλόγους που εξαρτώνται από την έκδοση ξεκινώντας από /lib/modules. Και παρόλο που παλαιότερες εκδόσεις του βοηθητικού προγράμματος περιλάμβαναν τον τρέχοντα κατάλογο στη διαδρομή αναζήτησης, αυτή η συμπεριφορά θεωρείται πλέον απαράδεκτη για λόγους ασφαλείας (τα ίδια προβλήματα με τη χρήση της μεταβλητής συστήματος ΜΟΝΟΠΑΤΙ). Έτσι, εάν θέλετε να φορτώσετε μια λειτουργική μονάδα από τον τρέχοντα κατάλογο, μπορείτε να την καθορίσετε στο στυλ ./module.ο. Αυτή η ένδειξη της θέσης της μονάδας θα λειτουργήσει για οποιαδήποτε έκδοση του βοηθητικού προγράμματος insmod.

Μερικές φορές μπορεί να συναντήσετε διεπαφές πυρήνα που διαφέρουν μεταξύ 2.0.x και 2.4.x. Σε αυτήν την περίπτωση, θα χρειαστεί να καταφύγετε σε μια μακροεντολή που καθορίζει την τρέχουσα έκδοση του πυρήνα. Αυτή η μακροεντολή βρίσκεται στο αρχείο κεφαλίδας . Θα υποδείξουμε περιπτώσεις διαφορών στις διεπαφές κατά τη χρήση τους. Αυτό μπορεί να γίνει είτε αμέσως κατά μήκος της περιγραφής, είτε στο τέλος της ενότητας, σε μια ειδική ενότητα αφιερωμένη στις εξαρτήσεις εκδόσεων. Σε ορισμένες περιπτώσεις, η τοποθέτηση των λεπτομερειών σε ξεχωριστή ενότητα θα σας επιτρέψει να αποφύγετε να περιπλέκετε την περιγραφή της έκδοσης 2.4.x του πυρήνα που σχετίζεται με αυτό το βιβλίο.

Στο αρχείο κεφαλίδας linux/version.hΟι ακόλουθες μακροεντολές ορίζονται σχετικά με τον προσδιορισμό της έκδοσης του πυρήνα.

UTS_RELEASEΜακροεντολή που επεκτείνεται σε μια συμβολοσειρά που περιγράφει την τρέχουσα έκδοση του πυρήνα
δέντρο πηγής. Για παράδειγμα, μια μακροεντολή μπορεί να επεκταθεί σε κάτι σαν αυτό:
γραμμή: "2.3.48" . LINUX_VERSION_CODEΑυτή η μακροεντολή επεκτείνεται σε μια δυαδική αναπαράσταση της έκδοσης του πυρήνα, από
ένα byte για κάθε μέρος του αριθμού. Για παράδειγμα, δυαδικό
η αναπαράσταση για την έκδοση 2.3.48 θα είναι 131888 (δεκαδική
παράσταση για εξάγωνο 0x020330). Πιθανώς δυαδικό
Θα βρείτε την αναπαράσταση πιο βολική από την παράσταση συμβολοσειράς. Παρατηρήστε τι είναι
Η αναπαράσταση σάς επιτρέπει να περιγράψετε όχι περισσότερες από 256 επιλογές σε καθεμία
μέρη του αριθμού. KERNEL_VERSION (μείζον, δευτερεύον, έκδοση)Αυτός ο ορισμός μακροεντολής σας επιτρέπει να δημιουργήσετε "kernel_version_code"
από τα επιμέρους στοιχεία που απαρτίζουν την έκδοση του πυρήνα. Για παράδειγμα,
επόμενη μακροεντολή KERNEL_VERSION(2, 3, 48)
θα επεκταθεί στο 131888. Αυτός ο ορισμός μακροεντολών είναι πολύ βολικός όταν
συγκρίνοντας την τρέχουσα έκδοση του πυρήνα με την απαιτούμενη. Θα είμαστε επανειλημμένα
χρησιμοποιήστε αυτόν τον μακροορισμό σε όλο το βιβλίο.

Εδώ είναι τα περιεχόμενα του αρχείου: linux/version.hγια τον πυρήνα 2.4.25 (το κείμενο του αρχείου κεφαλίδας δίνεται ολόκληρο).

#define UTS_RELEASE "2.4.25" #define LINUX_VERSION_CODE 132121 #define KERNEL_VERSION(a,b,c) (((a)<< 16) + ((b) << 8) + (c))

Το αρχείο κεφαλίδας version.h περιλαμβάνεται στο αρχείο module.h, επομένως γενικά δεν χρειάζεται να συμπεριλάβετε ρητά το version.h στον κώδικα της μονάδας σας. Από την άλλη πλευρά, μπορείτε να αποτρέψετε τη συμπερίληψη του αρχείου κεφαλίδας version.h στο module.h δηλώνοντας μια μακροεντολή __ΟΧΙ_ΕΚΔΟΣΗ__. θα χρησιμοποιήσετε __ΟΧΙ_ΕΚΔΟΣΗ__, για παράδειγμα στην περίπτωση που πρέπει να ενεργοποιήσετε σε πολλά αρχεία προέλευσης, τα οποία στη συνέχεια θα συνδεθούν σε μία ενότητα. Ανακοίνωση __ΟΧΙ_ΕΚΔΟΣΗ__πριν συμπεριλάβετε το αρχείο κεφαλίδας module.h αποτρέπει
αυτόματη περιγραφή συμβολοσειράς __module_kernel_versionή το αντίστοιχο σε αρχεία πηγής. Μπορεί να χρειαστείτε αυτό για να ικανοποιήσετε τα παράπονα του συνδέσμου όταν ld -r, στους οποίους δεν θα αρέσουν πολλές περιγραφές συμβόλων σε πίνακες συνδέσμων. Συνήθως, εάν ο κώδικας της μονάδας χωρίζεται σε πολλαπλά αρχεία προέλευσης, συμπεριλαμβανομένου ενός αρχείου κεφαλίδας , μετά η ανακοίνωση __ΟΧΙ_ΕΚΔΟΣΗ__γίνεται σε όλα αυτά τα αρχεία εκτός από ένα. Στο τέλος του βιβλίου υπάρχει ένα παράδειγμα ενότητας που χρησιμοποιεί __ΟΧΙ_ΕΚΔΟΣΗ__.

Οι περισσότερες εξαρτήσεις έκδοσης πυρήνα μπορούν να αντιμετωπιστούν χρησιμοποιώντας λογική που βασίζεται σε οδηγίες προεπεξεργαστή χρησιμοποιώντας ορισμούς μακροεντολών KERNEL_VERSIONΚαι LINUX_VERSION_CODE. Ωστόσο, ο έλεγχος των εξαρτήσεων έκδοσης μπορεί να περιπλέξει πολύ την αναγνωσιμότητα του κώδικα της ενότητας λόγω ετερογενών οδηγιών #ifdef. Επομένως, ίσως η καλύτερη λύση είναι να τοποθετήσετε τον έλεγχο εξάρτησης σε ένα ξεχωριστό αρχείο κεφαλίδας. Αυτός είναι ο λόγος που το παράδειγμά μας περιλαμβάνει ένα αρχείο κεφαλίδας sysdep.h, χρησιμοποιείται για να φιλοξενήσει όλους τους ορισμούς μακροεντολών που σχετίζονται με ελέγχους εξάρτησης έκδοσης.

Η πρώτη εξάρτηση έκδοσης που θέλουμε να αντιπροσωπεύουμε βρίσκεται στη δήλωση προορισμού" κάντε εγκατάσταση"Το σενάριο μεταγλώττισης του προγράμματος οδήγησης. Όπως θα περίμενε κανείς, ο κατάλογος εγκατάστασης, ο οποίος αλλάζει ανάλογα με την έκδοση του πυρήνα που χρησιμοποιείται, επιλέγεται με βάση την προβολή του αρχείου version.h. Ακολουθεί ένα απόσπασμα κώδικα από το αρχείο Κανόνες.κατασκευάζω, το οποίο χρησιμοποιείται από όλα τα Makefiles του πυρήνα.

VERSIONFILE = $(INCLUDEDIR)/linux/version.h VREION = $(shell awk -F\" "/REL/ (εκτύπωση $2)" $(VERSIONFILE)) INSTALLDIR = /lib/modules/$(VERSION)/misc

Σημειώστε ότι χρησιμοποιούμε τον κατάλογο misc (τη δήλωση INSTALLDIR στο παράδειγμα Makefile παραπάνω) για να εγκαταστήσουμε όλα τα προγράμματα οδήγησης. Ξεκινώντας από την έκδοση 2.4 του πυρήνα, αυτός ο κατάλογος είναι ο προτεινόμενος κατάλογος για την τοποθέτηση προσαρμοσμένων προγραμμάτων οδήγησης. Επιπρόσθετα, τόσο η παλιά όσο και η νέα έκδοση του πακέτου modutils περιέχουν έναν διάφορο κατάλογο στις διαδρομές αναζήτησής τους.

Χρησιμοποιώντας τον παραπάνω ορισμό INSTALLDIR, ο στόχος εγκατάστασης στο Makefile μπορεί να μοιάζει με αυτό:

Εγκατάσταση: εγκατάσταση -d $(INSTALLDIR) εγκατάσταση -c $(OBJS) $(INSTALLDIR)

Εξάρτηση από την πλατφόρμα

Κάθε πλατφόρμα υπολογιστή έχει τα δικά της χαρακτηριστικά που πρέπει να ληφθούν υπόψη από τους προγραμματιστές του πυρήνα για να επιτευχθεί η υψηλότερη απόδοση.

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

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

Ένα άλλο ενδιαφέρον χαρακτηριστικό του Makefile είναι η υποστήριξή του για cross-platform ή απλά cross compilation. Αυτός ο όρος χρησιμοποιείται όταν χρειάζεται να μεταγλωττίσετε κώδικα για άλλη πλατφόρμα. Για παράδειγμα, χρησιμοποιώντας την πλατφόρμα i86 πρόκειται να δημιουργήσετε κώδικα για την πλατφόρμα M68000. Εάν πρόκειται να κάνετε cross compile, τότε θα χρειαστεί να αντικαταστήσετε τα εργαλεία μεταγλώττισης ( gcc, ld, κ.λπ.) με ένα άλλο σύνολο αντίστοιχων εργαλείων
(Για παράδειγμα, m68k-linux-gcc, m68k-linux-ld). Το πρόθεμα που χρησιμοποιείται μπορεί να καθοριστεί είτε από τη μεταβλητή Makefile $(CROSS_COMPILE), από μια επιλογή γραμμής εντολών στο βοηθητικό πρόγραμμα make ή από μια μεταβλητή περιβάλλοντος συστήματος.

Η αρχιτεκτονική SPARC είναι μια ειδική περίπτωση που πρέπει να αντιμετωπιστεί αναλόγως στο Makefile. Τα προγράμματα χρήστη που εκτελούνται στην πλατφόρμα SPARC64 (SPARC V9) είναι δυαδικά, συνήθως σχεδιασμένα για την πλατφόρμα SPARC32 (SPARC V8). Επομένως, ο προεπιλεγμένος μεταγλωττιστής στην πλατφόρμα SPARC64 (gcc) δημιουργεί κώδικα αντικειμένου για το SPARC32. Από την άλλη πλευρά, ένας πυρήνας που έχει σχεδιαστεί για να εκτελείται στο SPARC V9 πρέπει να περιέχει κώδικα αντικειμένου για το SPARC V9, επομένως ακόμη και τότε απαιτείται cross compiler. Όλες οι διανομές GNU/Linux που έχουν σχεδιαστεί για το SPARC64 περιλαμβάνουν έναν κατάλληλο cross-compiler, ο οποίος πρέπει να επιλεγεί στο Makefile για το σενάριο μεταγλώττισης του πυρήνα.

Και παρόλο που η πλήρης λίστα των εξαρτήσεων έκδοσης και πλατφόρμας είναι λίγο πιο περίπλοκη από ό,τι περιγράφεται εδώ, αρκεί για να εκτελέσετε cross compilation. Για περισσότερες πληροφορίες, μπορείτε να δείτε τα σενάρια μεταγλώττισης του Makefile και τα αρχεία προέλευσης του πυρήνα.

Χαρακτηριστικά του πυρήνα 2.6

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

Μπορείτε να χρησιμοποιήσετε ενσωματωμένα περιβάλλοντα ανάπτυξης όπως το TimeStorm του TimeSys, το οποίο θα δημιουργήσει σωστά τον σκελετό και το σενάριο μεταγλώττισης για την ενότητα σας, ανάλογα με την απαιτούμενη έκδοση του πυρήνα. Εάν πρόκειται να τα γράψετε όλα αυτά μόνοι σας, τότε θα χρειαστείτε κάποιες επιπλέον πληροφορίες σχετικά με τις κύριες διαφορές που εισάγει ο νέος πυρήνας.

Ένα από τα χαρακτηριστικά του πυρήνα 2.6 είναι η ανάγκη χρήσης των μακροεντολών module_init() και module_exit() για τη ρητή καταχώρηση των ονομάτων των συναρτήσεων αρχικοποίησης και τερματισμού.

Η μακροεντολή MODULE_LISENCE(), που εισήχθη στον πυρήνα 2.4, εξακολουθεί να είναι απαραίτητη εάν δεν θέλετε να βλέπετε τις αντίστοιχες προειδοποιήσεις κατά τη φόρτωση μιας λειτουργικής μονάδας. Μπορείτε να επιλέξετε τις ακόλουθες συμβολοσειρές άδειας χρήσης που θα μεταφερθούν στη μακροεντολή: "GPL", "GPL v2", "GPL και πρόσθετα δικαιώματα", "Dual BSD/GPL" (επιλογή μεταξύ αδειών BSD ή GPL), "Dual MPL/GPL" " (επιλογή μεταξύ αδειών Mozilla ή GPL) και
"Ιδιόκτητος".

Πιο σημαντικό για τον νέο πυρήνα είναι ένα νέο σχήμα μεταγλώττισης λειτουργικών μονάδων, το οποίο συνεπάγεται όχι μόνο αλλαγές στον κώδικα της ίδιας της ενότητας αλλά και στο σενάριο Makefile για τη μεταγλώττιση του.

Έτσι, ο ορισμός του συμβόλου μακροεντολής MODULE δεν απαιτείται πλέον ούτε στον κώδικα της ενότητας ούτε στο Makefile. Εάν είναι απαραίτητο, το ίδιο το νέο σχήμα μεταγλώττισης θα καθορίσει αυτό το μακροσύμβολο. Επίσης, δεν θα χρειαστεί να ορίσετε ρητά τα __KERNEL__ μακροσύμβολα ή νεότερα όπως το KBUILD_BASENAME και το KBUILD_MODNAME.

Επίσης, δεν πρέπει να καθορίσετε το επίπεδο βελτιστοποίησης κατά τη μεταγλώττιση (-O2 ή άλλα), γιατί Η ενότητα σας θα μεταγλωττιστεί με ολόκληρο το σύνολο σημαιών, συμπεριλαμβανομένων των σημαιών βελτιστοποίησης, με τις οποίες συντάσσονται όλες οι άλλες μονάδες του πυρήνα σας - το βοηθητικό πρόγραμμα make χρησιμοποιεί αυτόματα ολόκληρο το απαραίτητο σύνολο σημαιών.

Για αυτούς τους λόγους, το Makefile για τη μεταγλώττιση μιας ενότητας για τον πυρήνα 2.6 είναι πολύ πιο απλό. Έτσι, για την ενότητα hello.c το Makefile θα μοιάζει με αυτό:

Obj-m:= γεια.ο

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

# make -C /usr/src/linux-2.6.1 SUBDIRS=`pwd` modules

Έτσι, έχουμε την πηγή της ενότητας γεια-2.6.c, για μεταγλώττιση στον πυρήνα 2.6:

//hello-2.6.c #include #περιλαμβάνω #περιλαμβάνω MODULE_LICENSE("GPL"); static int __init my_init(void) ( printk("Hello world\n"); return 0; ); static void __exit my_cleanup(void) ( printk("Good bye\n"); ); module_init(my_init); module_exit(my_cleanup);

Αντίστοιχα, έχουμε ένα Makefile:

Obj-m:= γεια-2.6.o

Καλούμε το βοηθητικό πρόγραμμα make για να επεξεργαστεί το Makefile μας με τις ακόλουθες παραμέτρους:

# make -C/usr/src/linux-2.6.3 SUBDIRS=`pwd` modules

Η κανονική διαδικασία μεταγλώττισης θα παράγει την ακόλουθη τυπική έξοδο:

Μάρκα: Εισαγάγετε τον κατάλογο `/usr/src/linux-2.6.3" *** Προειδοποίηση: Η παράκαμψη SUBDIRS στη γραμμή εντολών μπορεί να προκαλέσει *** ασυνέπειες κάνουν: "arch/i386/kernel/asm-offsets.s" δεν απαιτεί ενημέρωση. CHK include/asm-i386/asm_offsets.h CC [M] /home/knz/j.kernel/3/hello-2.6.o Δόμηση λειτουργικών μονάδων, στάδιο 2. /usr/src/linux-2.6.3/scripts/Makefile .modpost:17: *** Ωχ, έχετε μπαγιάτικες εγγραφές ενότητας. Μπερδεψατε με τα SUBDIRS, /usr/src/linux-2.6.3/scripts/Makefile.modpost:18: μην παραπονιέστε αν κάτι πάει στραβά. MODPOST CC /home/knz/j.kernel/3/hello-2.6.mod.o LD [M] /home/knz/j.kernel/3/hello-2.6.ko make: Έξοδος από τον κατάλογο `/usr/src / linux-2.6.3"

Το τελικό αποτέλεσμα της μεταγλώττισης θα είναι ένα αρχείο module hello-2.6.ko που μπορεί να εγκατασταθεί στον πυρήνα.

Σημειώστε ότι στον πυρήνα 2.6, τα αρχεία λειτουργιών έχουν επίθημα .ko αντί .o όπως στον πυρήνα 2.4.

Πίνακας συμβόλων πυρήνα

Έχουμε ήδη μιλήσει για το πώς το βοηθητικό πρόγραμμα insmod χρησιμοποιεί τον πίνακα δημοσίων συμβόλων του πυρήνα όταν συνδέει μια λειτουργική μονάδα με τον πυρήνα. Αυτός ο πίνακας περιέχει τις διευθύνσεις των καθολικών αντικειμένων πυρήνα - συναρτήσεις και μεταβλητές - που απαιτούνται για την υλοποίηση αρθρωτών επιλογών προγραμμάτων οδήγησης. Ο πίνακας δημοσίων συμβόλων του πυρήνα μπορεί να διαβαστεί σε μορφή κειμένου από το αρχείο /proc/ksyms, υπό την προϋπόθεση ότι ο πυρήνας σας υποστηρίζει το σύστημα αρχείων /proc.

Στον πυρήνα 2.6, το /proc/ksyms μετονομάστηκε σε /proc/modules.

Όταν φορτώνεται μια λειτουργική μονάδα, τα σύμβολα που εξάγονται από τη λειτουργική μονάδα γίνονται μέρος του πίνακα συμβόλων του πυρήνα και μπορείτε να τα δείτε στο /proc/ksyms.

Οι νέες λειτουργικές μονάδες μπορούν να χρησιμοποιούν σύμβολα που εξάγονται από τη μονάδα σας. Για παράδειγμα, η μονάδα msdos βασίζεται σε χαρακτήρες που εξάγονται από τη μονάδα fat και κάθε συσκευή USB που χρησιμοποιείται σε λειτουργία ανάγνωσης χρησιμοποιεί χαρακτήρες από τις μονάδες usbcore και εισόδου. Αυτή η σχέση, που πραγματοποιείται με τη διαδοχική φόρτωση των μονάδων, ονομάζεται στοίβα μονάδων.

Η στοίβα μονάδων είναι βολική στη χρήση κατά τη δημιουργία σύνθετων έργων λειτουργικών μονάδων. Αυτή η αφαίρεση είναι χρήσιμη για το διαχωρισμό του κώδικα προγράμματος οδήγησης συσκευής σε μέρη που εξαρτώνται από το υλικό και ανεξάρτητα από το υλικό. Για παράδειγμα, το σετ προγραμμάτων οδήγησης video-for-linux αποτελείται από μια βασική μονάδα που εξάγει σύμβολα για ένα πρόγραμμα οδήγησης χαμηλού επιπέδου που λαμβάνει υπόψη τις ιδιαιτερότητες του υλικού που χρησιμοποιείται. Σύμφωνα με τη διαμόρφωσή σας, φορτώνετε την κύρια λειτουργική μονάδα βίντεο και μια ειδική μονάδα για το υλικό σας. Με τον ίδιο τρόπο, υλοποιείται η υποστήριξη για παράλληλες θύρες και μια ευρεία κατηγορία συνδεδεμένων συσκευών, όπως συσκευές USB. Η στοίβα συστήματος παράλληλης θύρας φαίνεται στην Εικ. 2-2. Τα βέλη δείχνουν την αλληλεπίδραση μεταξύ των μονάδων και της διεπαφής προγραμματισμού πυρήνα. Η αλληλεπίδραση μπορεί να πραγματοποιηθεί τόσο σε επίπεδο συναρτήσεων όσο και σε επίπεδο δομών δεδομένων που διαχειρίζονται οι συναρτήσεις.

Εικόνα 2-2. Στοίβα μονάδων παράλληλης θύρας

Όταν χρησιμοποιείτε μονάδες στοίβας, είναι βολικό να χρησιμοποιείτε το βοηθητικό πρόγραμμα modprobe. Η λειτουργικότητα του βοηθητικού προγράμματος modprobe είναι από πολλές απόψεις παρόμοια με το βοηθητικό πρόγραμμα insmod, αλλά κατά τη φόρτωση μιας μονάδας, ελέγχει τις υποκείμενες εξαρτήσεις της και, εάν είναι απαραίτητο, φορτώνει τις απαραίτητες μονάδες μέχρι να γεμίσει η απαιτούμενη στοίβα λειτουργιών. Έτσι, μια εντολή modprobe μπορεί να οδηγήσει σε πολλαπλές κλήσεις στην εντολή insmod. Θα μπορούσατε να πείτε ότι η εντολή modprobe είναι ένα έξυπνο περιτύλιγμα γύρω από το insmod. Μπορείτε να χρησιμοποιήσετε το modprobe αντί για το insmod παντού, εκτός από τη φόρτωση των δικών σας λειτουργικών μονάδων από τον τρέχοντα κατάλογο, επειδή Το modprobe εξετάζει μόνο συγκεκριμένους καταλόγους λειτουργικών μονάδων και δεν θα μπορεί να ικανοποιήσει πιθανές εξαρτήσεις.

Η διαίρεση των μονάδων σε μέρη συμβάλλει στη μείωση του χρόνου ανάπτυξης απλοποιώντας τον ορισμό του προβλήματος. Αυτό είναι παρόμοιο με τον διαχωρισμό μεταξύ μηχανισμού υλοποίησης και πολιτικής ελέγχου, ο οποίος συζητείται στο Κεφάλαιο 1, «Εισαγωγή στα προγράμματα οδήγησης συσκευών».

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

Τα αρχεία κεφαλίδας πυρήνα Linux προσφέρουν έναν βολικό τρόπο ελέγχου της ορατότητας των συμβόλων σας, αποτρέποντας έτσι τη μόλυνση του χώρου ονομάτων του πίνακα συμβόλων του πυρήνα. Ο μηχανισμός που περιγράφεται σε αυτό το κεφάλαιο λειτουργεί σε πυρήνες ξεκινώντας από την έκδοση 2.1.18. Ο πυρήνας 2.0 είχε έναν εντελώς διαφορετικό μηχανισμό ελέγχου
ορατότητα συμβόλων, η οποία θα περιγραφεί στο τέλος του κεφαλαίου.

Εάν η μονάδα σας δεν χρειάζεται καθόλου να εξάγει σύμβολα, μπορείτε να πραγματοποιήσετε ρητά την ακόλουθη κλήση μακροεντολής στο αρχείο προέλευσης της λειτουργικής μονάδας:

EXPORT_NO_SYMBOLS;

Αυτή η κλήση μακροεντολής, που ορίζεται στο αρχείο linux/module.h, επεκτείνεται σε μια οδηγία assembler και μπορεί να καθοριστεί οπουδήποτε στη λειτουργική μονάδα. Ωστόσο, όταν δημιουργείτε κώδικα που είναι φορητός σε διαφορετικούς πυρήνες, είναι απαραίτητο να τοποθετήσετε αυτήν την κλήση μακροεντολής στη συνάρτηση προετοιμασίας της μονάδας (init_module), επειδή η έκδοση αυτής της μακροεντολής που ορίσαμε στο αρχείο sysdep.h για παλαιότερες εκδόσεις πυρήνα θα λειτουργήσει μόνο εδώ.

Από την άλλη πλευρά, εάν πρέπει να εξαγάγετε μερικά από τα σύμβολα από τη μονάδα σας, τότε πρέπει να χρησιμοποιήσετε ένα σύμβολο μακροεντολής
EXPORT_SYMTAB. Αυτό το σύμβολο μακροεντολής πρέπει να οριστεί πρινσυμπεριλαμβάνοντας το αρχείο κεφαλίδας module.h. Είναι κοινή πρακτική
ορίζοντας αυτόν τον χαρακτήρα μακροεντολής μέσω σημαίας -ΡΕστο Makefile.

Εάν το σύμβολο της μακροεντολής EXPORT_SYMTABορίζεται, τότε μεμονωμένα σύμβολα μπορούν να εξαχθούν χρησιμοποιώντας ένα ζεύγος μακροεντολών:

EXPORT_SYMBOL(όνομα); EXPORT_SYMBOL_NOVERS(όνομα);

Οποιαδήποτε από αυτές τις δύο μακροεντολές θα κάνει το δεδομένο σύμβολο διαθέσιμο εκτός της λειτουργικής μονάδας. Η διαφορά είναι ότι η μακροεντολή EXPORT_SYMBOL_NOVERSεξάγει το σύμβολο χωρίς πληροφορίες έκδοσης (βλ. Κεφάλαιο 11 «kmod και προηγμένη σπονδυλοποίηση»). Για περισσότερες πληροφορίες
ελέγξτε το αρχείο κεφαλίδας , αν και τα αναφερόμενα είναι αρκετά επαρκή για πρακτική χρήση
μακροεντολές.

Εκκίνηση και ολοκλήρωση ενοτήτων

Όπως αναφέρθηκε, η συνάρτηση init_module() καταχωρεί τα λειτουργικά στοιχεία μιας λειτουργικής μονάδας στον πυρήνα. Μετά από μια τέτοια εγγραφή, η εφαρμογή που χρησιμοποιεί τη λειτουργική μονάδα θα έχει πρόσβαση στα σημεία εισόδου της λειτουργικής μονάδας μέσω της διεπαφής που παρέχεται από τον πυρήνα.

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

Στο Κεφάλαιο 1, «Εισαγωγή στα προγράμματα οδήγησης συσκευών», αναφέρθηκε η ταξινόμηση των κύριων τύπων συσκευών. Μπορείτε να καταχωρήσετε όχι μόνο τους τύπους συσκευών που αναφέρονται εκεί, αλλά και οποιουσδήποτε άλλους, ακόμη και αφαιρέσεις λογισμικού, όπως, για παράδειγμα, αρχεία /proc κ.λπ. Όλα όσα μπορούν να λειτουργήσουν στον πυρήνα μέσω της διεπαφής προγραμματισμού προγράμματος οδήγησης μπορούν να καταχωρηθούν ως πρόγραμμα οδήγησης .

Εάν θέλετε να μάθετε περισσότερα σχετικά με τους τύπους προγραμμάτων οδήγησης που καταχωρούνται χρησιμοποιώντας τον πυρήνα σας ως παράδειγμα, μπορείτε να πραγματοποιήσετε μια αναζήτηση για την υποσυμβολοσειρά EXPORT_SYMBOL στις πηγές του πυρήνα και να βρείτε τα σημεία εισόδου που προσφέρονται από τα διάφορα προγράμματα οδήγησης. Κατά κανόνα, οι συναρτήσεις εγγραφής χρησιμοποιούν ένα πρόθεμα στο όνομά τους κανω ΕΓΓΡΑΦΗ_,
οπότε ένας άλλος πιθανός τρόπος για να τα βρείτε είναι να αναζητήσετε μια υποσυμβολοσειρά κανω ΕΓΓΡΑΦΗ_στο αρχείο /proc/ksyms χρησιμοποιώντας το βοηθητικό πρόγραμμα grep. Όπως ήδη αναφέρθηκε, στον πυρήνα 2.6.x το αρχείο /proc/ksyms αντικαταστάθηκε με /proc/modules.

Σφάλμα χειρισμού στο init_module

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

Οτιδήποτε καταχωρήθηκε ή εκχωρήθηκε πριν εμφανιστεί το σφάλμα στη συνάρτηση προετοιμασίας init_module() πρέπει να ακυρωθεί ή να ελευθερωθεί από μόνο του, επειδή ο πυρήνας Linux δεν παρακολουθεί σφάλματα αρχικοποίησης και δεν αναιρεί το δανεισμό και τη χορήγηση πόρων από τον κώδικα της μονάδας. Εάν δεν κάνατε επαναφορά ή δεν μπορέσατε να επαναφέρετε την ολοκληρωμένη εγγραφή, ο πυρήνας θα παραμείνει σε ασταθή κατάσταση και όταν η λειτουργική μονάδα φορτωθεί ξανά
δεν θα μπορείτε να επαναλάβετε την εγγραφή των ήδη καταχωρισμένων στοιχείων και δεν θα μπορείτε να ακυρώσετε μια εγγραφή που έγινε στο παρελθόν, επειδή στη νέα παρουσία της συνάρτησης init_module() δεν θα έχετε τη σωστή τιμή των διευθύνσεων των καταχωρημένων συναρτήσεων. Η επαναφορά του συστήματος στην προηγούμενη κατάστασή του θα απαιτήσει τη χρήση διαφόρων πολύπλοκων κόλπων, και αυτό γίνεται συχνά με απλή επανεκκίνηση του συστήματος.

Η υλοποίηση της επαναφοράς της προηγούμενης κατάστασης του συστήματος όταν εμφανίζονται σφάλματα αρχικοποίησης της μονάδας υλοποιείται καλύτερα χρησιμοποιώντας τον τελεστή goto. Συνήθως αυτός ο χειριστής αντιμετωπίζεται εξαιρετικά αρνητικά, και μάλιστα με μίσος, αλλά σε αυτήν την κατάσταση αποδεικνύεται πολύ χρήσιμος. Επομένως, στον πυρήνα, η πρόταση goto χρησιμοποιείται συχνά για τον χειρισμό σφαλμάτων προετοιμασίας λειτουργικών μονάδων.

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

Int init_module(void) ( int err; /* η εγγραφή παίρνει έναν δείκτη και ένα όνομα */ err = register_this(ptr1, "skull"); if (err) goto fail_this; err = register_that(ptr2, "skull"); αν (err) goto fail_that; err = register_those(ptr3, "κρανίο"); if (err) goto fail_those; return 0; /* επιτυχία */ fail_those: unregister_that(ptr2, "κρανίο"); fail_that: unregister_this(ptr1, " κρανίο"); fail_this: return err; /* διάδοση του σφάλματος */ )

Αυτό το παράδειγμα επιχειρεί να καταχωρήσει τρία στοιχεία λειτουργικής μονάδας. Η δήλωση goto χρησιμοποιείται όταν παρουσιάζεται ένα σφάλμα εγγραφής και προκαλεί την κατάργηση της καταχώρισης των καταχωρημένων στοιχείων πριν από τη διακοπή της φόρτωσης της μονάδας.

Ένα άλλο παράδειγμα χρήσης μιας δήλωσης goto για να διευκολύνει την ανάγνωση του κώδικα είναι το τέχνασμα του να «θυμόμαστε» επιτυχημένες εγγραφές λειτουργικών μονάδων και να καλούμε την cleanup_module() για να μεταβιβάσουμε αυτές τις πληροφορίες όταν παρουσιάζεται σφάλμα. Η συνάρτηση cleanup_module() έχει σχεδιαστεί για επαναφορά των ολοκληρωμένων λειτουργιών αρχικοποίησης και καλείται αυτόματα όταν η μονάδα εκφορτώνεται. Η τιμή που επιστρέφει η συνάρτηση init_module() πρέπει να είναι
αντιπροσωπεύουν τον κωδικό σφάλματος προετοιμασίας της μονάδας. Στον πυρήνα του Linux, ο κωδικός σφάλματος είναι ένας αρνητικός αριθμός από ένα σύνολο ορισμών που έγιναν στο αρχείο κεφαλίδας . Συμπεριλάβετε αυτό το αρχείο κεφαλίδας στη λειτουργική μονάδα σας για να χρησιμοποιήσετε συμβολικά μνημονικά για δεσμευμένους κωδικούς σφαλμάτων όπως -ENODEV, -ENOMEM κ.λπ. Η χρήση τέτοιων μνημονικών θεωρείται καλό στυλ προγραμματισμού. Ωστόσο, θα πρέπει να σημειωθεί ότι ορισμένες εκδόσεις των βοηθητικών προγραμμάτων από το πακέτο modutils δεν επεξεργάζονται σωστά τους επιστρεφόμενους κωδικούς σφάλματος και εμφανίζουν το μήνυμα "Device busy"
ως απόκριση σε μια ολόκληρη ομάδα σφαλμάτων εντελώς διαφορετικής φύσης που επιστρέφονται από τη συνάρτηση init_modules(). Στις τελευταίες εκδόσεις του πακέτου αυτό
Το ενοχλητικό σφάλμα διορθώθηκε.

Ο κώδικας συνάρτησης cleanup_module() για την παραπάνω περίπτωση θα μπορούσε, για παράδειγμα, να είναι ως εξής:

Void cleanup_module(void) ( unregister_those(ptr3, "skull"); unregister_that(ptr2, "skull"); unregister_this(ptr1, "skull"); return; )

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

Παρακάτω είναι ένα παράδειγμα για το πώς να γράψετε τις συναρτήσεις init_module() και cleanup_module(). Αυτό το παράδειγμα χρησιμοποιεί καθολικά καθορισμένους δείκτες που μεταφέρουν πληροφορίες σχετικά με το εύρος της επιτυχημένης προετοιμασίας.

Δομήστε κάτι *item1; struct somethingelse *item2; int stuff_ok; void cleanup_module(void) ( if (item1) release_thing(item1); if (item2) release_thing2(item2); if (stuff_ok) unregister_stuff(); return; ) int init_module(void) (int err = -ENOMEM; item1 = allocate_thing (ορίσματα); item2 = allocate_thing2(arguments2); if (!item2 || !item2) goto fail; err = register_stuff(item1, item2); if (!err) stuff_ok = 1; other goto fail; return 0; /* επιτυχία */ αποτυχία: cleanup_module(); return err; )

Ανάλογα με την πολυπλοκότητα των λειτουργιών προετοιμασίας της μονάδας σας, ίσως θέλετε να χρησιμοποιήσετε μία από τις μεθόδους που παρατίθενται εδώ για τον έλεγχο των σφαλμάτων προετοιμασίας της μονάδας.

Μετρητής χρήσης μονάδας

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

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

MOD_INC_USE_COUNTΑυξάνει τον μετρητή χρήσης της τρέχουσας μονάδας MOD_DEC_USE_COUNTΜειώνει τον μετρητή χρήσης της τρέχουσας μονάδας MOD_IN_USEΕπιστρέφει true εάν ο μετρητής χρήσης αυτής της ενότητας είναι μηδέν

Αυτές οι μακροεντολές ορίζονται στο , και χειρίζονται μια ειδική εσωτερική δομή δεδομένων στην οποία δεν είναι επιθυμητή η άμεση πρόσβαση. Το γεγονός είναι ότι η εσωτερική δομή και ο τρόπος διαχείρισης αυτών των δεδομένων ενδέχεται να αλλάζουν από έκδοση σε έκδοση, ενώ η εξωτερική διεπαφή για τη χρήση αυτών των μακροεντολών παραμένει αμετάβλητη.

Σημειώστε ότι δεν χρειάζεται να κάνετε έλεγχο MOD_IN_USEστον κώδικα συνάρτησης cleanup_module(), επειδή αυτός ο έλεγχος εκτελείται αυτόματα πριν κληθεί η cleanup_module() στην κλήση συστήματος sys_delete_module(), η οποία ορίζεται στον πυρήνα/module.c.

Η σωστή διαχείριση του μετρητή χρήσης της μονάδας είναι κρίσιμη για τη σταθερότητα του συστήματος. Να θυμάστε ότι ο πυρήνας μπορεί να αποφασίσει να ξεφορτώσει αυτόματα μια αχρησιμοποίητη λειτουργική μονάδα ανά πάσα στιγμή. Ένα συνηθισμένο λάθος στον προγραμματισμό της μονάδας είναι ο εσφαλμένος έλεγχος αυτού του μετρητή. Για παράδειγμα, ως απόκριση σε ένα συγκεκριμένο αίτημα, ο κωδικός της μονάδας εκτελεί ορισμένες ενέργειες και, όταν ολοκληρωθεί η επεξεργασία, αυξάνει τον μετρητή χρήσης της μονάδας. Εκείνοι. ένας τέτοιος προγραμματιστής υποθέτει ότι αυτός ο μετρητής προορίζεται για τη συλλογή στατιστικών στοιχείων χρήσης της μονάδας, ενώ, στην πραγματικότητα, είναι, στην πραγματικότητα, ένας μετρητής για την τρέχουσα κατάληψη της μονάδας, δηλ. παρακολουθεί τον αριθμό των διεργασιών που χρησιμοποιούν τον κωδικό της μονάδας αυτή τη στιγμή. Επομένως, κατά την επεξεργασία ενός αιτήματος σε μια ενότητα, πρέπει να καλέσετε MOD_INC_USE_COUNTπριν εκτελέσετε οποιαδήποτε ενέργεια και MOD_DEC_USE_COUNTαφού ολοκληρωθούν.

Μπορεί να υπάρχουν καταστάσεις στις οποίες, για προφανείς λόγους, δεν θα μπορείτε να ξεφορτώσετε μια μονάδα εάν χάσετε τον έλεγχο του μετρητή χρήσης της. Αυτή η κατάσταση συμβαίνει συχνά στο στάδιο ανάπτυξης της ενότητας. Για παράδειγμα, μια διαδικασία μπορεί να ματαιωθεί κατά την προσπάθεια αποσύνδεσης ενός δείκτη NULL και δεν θα μπορείτε να ξεφορτώσετε μια τέτοια λειτουργική μονάδα μέχρι να μηδενίσετε τον μετρητή χρήσης της. Μία από τις πιθανές λύσεις σε αυτό το πρόβλημα στο στάδιο εντοπισμού σφαλμάτων της μονάδας είναι να εγκαταλείψουμε πλήρως τον έλεγχο του μετρητή χρήσης της μονάδας επαναπροσδιορίζοντας MOD_INC_USE_COUNTΚαι MOD_DEC_USE_COUNTσε κενό κώδικα. Μια άλλη λύση είναι να δημιουργήσετε μια κλήση ioctl() που να μηδενίζει τον μετρητή χρήσης της μονάδας. Θα το καλύψουμε στην ενότητα «Χρήση του ορίσματος ioctl» στο Κεφάλαιο 5, «Βελτιωμένες λειτουργίες προγράμματος οδήγησης Char». Φυσικά, σε ένα έτοιμο προς χρήση πρόγραμμα οδήγησης, τέτοιοι δόλιοι χειρισμοί με τον μετρητή θα πρέπει να αποκλείονται, ωστόσο, στο στάδιο εντοπισμού σφαλμάτων, εξοικονομούν χρόνο στον προγραμματιστή και είναι αρκετά αποδεκτοί.

Θα βρείτε τον τρέχοντα μετρητή χρήσης συστήματος για κάθε λειτουργική μονάδα στο τρίτο πεδίο κάθε καταχώρησης στο αρχείο /proc/modules. Αυτό το αρχείο περιέχει πληροφορίες σχετικά με τις τρέχουσες φορτωμένες λειτουργικές μονάδες - μία γραμμή ανά λειτουργική μονάδα. Το πρώτο πεδίο της γραμμής περιέχει το όνομα της μονάδας, το δεύτερο πεδίο είναι το μέγεθος που καταλαμβάνει η μονάδα στη μνήμη και το τρίτο πεδίο είναι η τρέχουσα τιμή του μετρητή χρήσης. Αυτές οι πληροφορίες, σε μορφοποιημένη μορφή,
μπορεί να ληφθεί καλώντας το βοηθητικό πρόγραμμα lsmod. Παρακάτω είναι ένα παράδειγμα αρχείου /proc/modules:

Parport_pc 7604 1 (autoclean) lp 4800 0 (αχρησιμοποίητο) parport 8084 1 lockd 33256 1 (autoclean) sunrpc 56612 1 (autoclean) ds 6252 1 i82365 140cm 2203

Εδώ βλέπουμε αρκετές μονάδες φορτωμένες στο σύστημα. Στο πεδίο σημαίες (το τελευταίο πεδίο της γραμμής), η στοίβα των εξαρτήσεων λειτουργικών μονάδων εμφανίζεται σε αγκύλες. Μεταξύ άλλων, μπορείτε να παρατηρήσετε ότι οι μονάδες παράλληλης θύρας επικοινωνούν μέσω μιας στοίβας μονάδων, όπως φαίνεται στην Εικ. 2-2. Η σημαία (autoclean) επισημαίνει μονάδες που ελέγχονται από kmod ή πυρήνα. Αυτό θα καλυφθεί στο Κεφάλαιο 11 «kmod και προηγμένη σπονδυλοποίηση»). Η (αχρησιμοποίητη) σημαία σημαίνει ότι η μονάδα δεν χρησιμοποιείται αυτήν τη στιγμή. Στον πυρήνα 2.0, το πεδίο μεγέθους εμφάνιζε πληροφορίες όχι σε byte, αλλά σε σελίδες, οι οποίες για τις περισσότερες πλατφόρμες έχουν μέγεθος 4 kB.

Εκφόρτωση μιας μονάδας

Για να ξεφορτώσετε μια μονάδα, χρησιμοποιήστε το βοηθητικό πρόγραμμα rmmod. Η εκφόρτωση μιας λειτουργικής μονάδας είναι μια απλούστερη εργασία από τη φόρτωσή της, η οποία περιλαμβάνει τη δυναμική σύνδεσή της με τον πυρήνα. Όταν μια λειτουργική μονάδα εκφορτώνεται, εκτελείται η κλήση συστήματος delete_module(), η οποία είτε καλεί τη συνάρτηση cleanup_module() της μη φορτωμένης λειτουργικής μονάδας εάν ο αριθμός χρήσης της είναι μηδέν, είτε τερματίζεται με σφάλμα.

Όπως αναφέρθηκε ήδη, η συνάρτηση cleanup_module() επαναφέρει τις λειτουργίες αρχικοποίησης που πραγματοποιήθηκαν κατά τη φόρτωση της μονάδας με τη συνάρτηση cleanup_module(). Επίσης, τα εξαγόμενα σύμβολα λειτουργικών μονάδων διαγράφονται αυτόματα.

Ρητός ορισμός των συναρτήσεων τερματισμού και αρχικοποίησης

Όπως αναφέρθηκε ήδη, κατά τη φόρτωση μιας λειτουργικής μονάδας ο πυρήνας καλεί τη συνάρτηση init_module() και κατά την εκφόρτωση καλεί την cleanup_module(). Ωστόσο, στις σύγχρονες εκδόσεις του πυρήνα αυτές οι συναρτήσεις έχουν συχνά διαφορετικά ονόματα. Ξεκινώντας με τον πυρήνα 2.3.23, κατέστη δυνατός ο ρητός ορισμός ενός ονόματος για τη λειτουργία φόρτωσης και εκφόρτωσης μιας μονάδας. Σήμερα, αυτή η ρητή ονομασία αυτών των συναρτήσεων είναι το προτεινόμενο στυλ προγραμματισμού.

Ας δώσουμε ένα παράδειγμα. Εάν θέλετε να δηλώσετε τη συνάρτηση my_init() ως τη συνάρτηση αρχικοποίησης της λειτουργικής μονάδας σας και τη λειτουργία my_cleanup() ως τελική συνάρτηση, αντί για init_module() και cleanup_module(), αντίστοιχα, τότε θα χρειαστεί να προσθέσετε τα ακόλουθα δύο μακροεντολές στο κείμενο της ενότητας (συνήθως εισάγονται στο τέλος
αρχείο πηγής κώδικα ενότητας):

Module_init(my_init); module_exit(my_cleanup);

Σημειώστε ότι για να χρησιμοποιήσετε αυτές τις μακροεντολές θα χρειαστεί να συμπεριλάβετε ένα αρχείο κεφαλίδας στη λειτουργική μονάδα σας .

Η ευκολία χρήσης αυτού του στυλ είναι ότι κάθε συνάρτηση προετοιμασίας και τερματισμού της μονάδας στον πυρήνα μπορεί να έχει το δικό της μοναδικό όνομα, το οποίο βοηθάει πολύ στον εντοπισμό σφαλμάτων. Επιπλέον, η χρήση αυτών των λειτουργιών απλοποιεί τον εντοπισμό σφαλμάτων, ανεξάρτητα από το αν εφαρμόζετε τον κώδικα του προγράμματος οδήγησης ως λειτουργική μονάδα ή πρόκειται να τον ενσωματώσετε απευθείας στον πυρήνα. Φυσικά, η χρήση των μακροεντολών module_init και module_exit δεν είναι απαραίτητη εάν οι συναρτήσεις αρχικοποίησης και τερματισμού έχουν δεσμευμένα ονόματα, π.χ. init_module() και cleanup_module() αντίστοιχα.

Εάν κοιτάξετε τις πηγές πυρήνα 2.2 ή νεότερες, ενδέχεται να δείτε μια ελαφρώς διαφορετική μορφή περιγραφής για τις συναρτήσεις αρχικοποίησης και τερματισμού. Για παράδειγμα:

Static int __init my_init(void) ( .... ) static void __exit my_cleanup(void) ( .... )

Χρήση χαρακτηριστικών __μέσα σε αυτόθα προκαλέσει την εκφόρτωση της συνάρτησης αρχικοποίησης από τη μνήμη μετά την ολοκλήρωση της προετοιμασίας. Ωστόσο, αυτό λειτουργεί μόνο για ενσωματωμένα προγράμματα οδήγησης πυρήνα και θα αγνοηθεί για λειτουργικές μονάδες. Επίσης, για προγράμματα οδήγησης που είναι ενσωματωμένα στον πυρήνα, το χαρακτηριστικό __έξοδοςθα προκαλέσει την παράβλεψη ολόκληρης της συνάρτησης που επισημαίνεται με αυτό το χαρακτηριστικό. Για λειτουργικές μονάδες, αυτή η σημαία θα αγνοηθεί επίσης.

Χρήση Ιδιοτήτων __μέσα σε αυτό(Και __αρχικά δεδομέναγια να περιγράψει δεδομένα) μπορεί να μειώσει την ποσότητα της μνήμης που χρησιμοποιείται από τον πυρήνα. Σημαία __μέσα σε αυτόΗ λειτουργία αρχικοποίησης της μονάδας δεν θα φέρει ούτε όφελος ούτε κακό. Ο έλεγχος αυτού του τύπου αρχικοποίησης δεν έχει ακόμη εφαρμοστεί για τις ενότητες, αν και μπορεί να είναι δυνατός στο μέλλον.

Συνοψίζοντας

Έτσι, ως αποτέλεσμα του παρουσιαζόμενου υλικού, μπορούμε να παρουσιάσουμε την ακόλουθη έκδοση της ενότητας "Hello world":

Κωδικός αρχείου πηγής ενότητας ============================================= = #περιλαμβάνω #περιλαμβάνω #περιλαμβάνω static int __init my_init_module (void) (EXPORT_NO_SYMBOLS; printk("<1>Γεια σου κόσμο\n"); επιστροφή 0; ); static void __exit my_cleanup_module (void) ( printk("<1>Αντίο\n"); ); module_init(my_init_module); module_exit(my_cleanup_module); MODULE_LICENSE("GPL"); ======================== ====================== Δημιουργία αρχείου για τη σύνταξη της ενότητας ======================== ================================== CFLAGS = -Wall -D__KERNEL__ -DMODULE -I/lib/modules/ $(shell uname -r)/build/include hello.o: =================================== ============================

Λάβετε υπόψη ότι κατά τη σύνταξη του Makefile, χρησιμοποιήσαμε τη σύμβαση ότι το βοηθητικό πρόγραμμα GNU make μπορεί να καθορίσει ανεξάρτητα τον τρόπο δημιουργίας ενός αρχείου αντικειμένου με βάση τη μεταβλητή CFLAGS και τον μεταγλωττιστή που είναι διαθέσιμος στο σύστημα.

Χρήση πηγών

Μια λειτουργική μονάδα δεν μπορεί να ολοκληρώσει την εργασία της χωρίς τη χρήση πόρων συστήματος όπως μνήμη, θύρες I/O, μνήμη I/O, γραμμές διακοπής και κανάλια DMA.

Ως προγραμματιστής, θα πρέπει να είστε ήδη εξοικειωμένοι με τη δυναμική διαχείριση μνήμης. Η διαχείριση δυναμικής μνήμης στον πυρήνα δεν είναι ουσιαστικά διαφορετική. Το πρόγραμμά σας μπορεί να αποκτήσει μνήμη χρησιμοποιώντας τη λειτουργία kmalloc()και ελευθερώστε την με τη βοήθεια kfree(). Αυτές οι συναρτήσεις μοιάζουν πολύ με τις συναρτήσεις malloc() και free() που γνωρίζετε, εκτός από το ότι η συνάρτηση kmalloc() λαμβάνει ένα πρόσθετο όρισμα - προτεραιότητα. Συνήθως η προτεραιότητα είναι GFP_KERNEL ή GFP_USER. Το GFP είναι ένα αρκτικόλεξο για το "get free page". Η διαχείριση της δυναμικής μνήμης στον πυρήνα καλύπτεται λεπτομερώς στο Κεφάλαιο 7, «Λήψη της μνήμης».

Ένας αρχάριος προγραμματιστής προγραμμάτων οδήγησης μπορεί να εκπλαγεί από την ανάγκη να εκχωρηθούν ρητά οι θύρες I/O, η μνήμη I/O και οι γραμμές διακοπής. Μόνο τότε η μονάδα πυρήνα μπορεί εύκολα να έχει πρόσβαση σε αυτούς τους πόρους. Παρόλο που η μνήμη του συστήματος μπορεί να εκχωρηθεί οπουδήποτε, η μνήμη I/O, οι θύρες και οι γραμμές διακοπής παίζουν ιδιαίτερο ρόλο και κατανέμονται διαφορετικά. Για παράδειγμα, το πρόγραμμα οδήγησης πρέπει να εκχωρήσει ορισμένες θύρες, όχι
τα πάντα, εκτός από αυτά που χρειάζεται για τον έλεγχο της συσκευής. Αλλά ο οδηγός δεν μπορεί να χρησιμοποιήσει αυτούς τους πόρους μέχρι να βεβαιωθεί ότι δεν χρησιμοποιούνται από κάποιον άλλο.

Η περιοχή της μνήμης που ανήκει σε μια περιφερειακή συσκευή ονομάζεται συνήθως μνήμη I/O, για να διακρίνεται από τη RAM συστήματος (RAM), η οποία ονομάζεται απλώς μνήμη.

Θύρες και μνήμη I/O

Η εργασία ενός τυπικού προγράμματος οδήγησης αποτελείται σε μεγάλο βαθμό από θύρες ανάγνωσης και εγγραφής και μνήμη I/O. Οι θύρες και η μνήμη I/O ενώνονται με ένα κοινό όνομα - περιοχή (ή περιοχή) I/O.

Δυστυχώς, δεν μπορεί κάθε αρχιτεκτονική διαύλου να ορίσει με σαφήνεια την περιοχή I/O που ανήκει σε κάθε συσκευή και είναι πιθανό ο οδηγός να πρέπει να μαντέψει τη θέση της περιοχής στην οποία ανήκει ή ακόμη και να επιχειρήσει λειτουργίες ανάγνωσης/εγγραφής σε πιθανή διεύθυνση χώρους. Αυτό το πρόβλημα είναι ιδιαίτερα
αναφέρεται στον δίαυλο ISA, ο οποίος εξακολουθεί να χρησιμοποιείται για την εγκατάσταση απλών συσκευών σε έναν προσωπικό υπολογιστή και είναι πολύ δημοφιλής στον βιομηχανικό κόσμο στην εφαρμογή του PC/104 (βλ. ενότητα «PC/104 και PC/104+» στο Κεφάλαιο 15 «Επισκόπηση των περιφερειακών λεωφορείων»).

Όποιος και αν είναι ο δίαυλος που χρησιμοποιείται για τη σύνδεση μιας συσκευής υλικού, το πρόγραμμα οδήγησης της συσκευής πρέπει να έχει εγγυημένη αποκλειστική πρόσβαση στην περιοχή I/O του για την αποφυγή συγκρούσεων μεταξύ των προγραμμάτων οδήγησης. Εάν μια μονάδα, έχοντας πρόσβαση στη δική της συσκευή, γράψει σε μια συσκευή που δεν της ανήκει, αυτό μπορεί να οδηγήσει σε θανατηφόρες συνέπειες.

Οι προγραμματιστές Linux εφάρμοσαν έναν μηχανισμό για την αίτηση/απελευθέρωση περιοχών I/O κυρίως για την πρόληψη συγκρούσεων μεταξύ διαφορετικών συσκευών. Αυτός ο μηχανισμός χρησιμοποιείται εδώ και πολύ καιρό για θύρες I/O και πρόσφατα γενικεύτηκε στη διαχείριση πόρων γενικά. Σημειώστε ότι αυτός ο μηχανισμός αντιπροσωπεύει μια αφαίρεση λογισμικού και δεν επεκτείνεται σε δυνατότητες υλικού. Για παράδειγμα, η μη εξουσιοδοτημένη πρόσβαση σε θύρες I/O σε επίπεδο υλικού δεν προκαλεί κανένα σφάλμα παρόμοιο με ένα «σφάλμα τμηματοποίησης», καθώς το υλικό δεν εκχωρεί και δεν εξουσιοδοτεί τους πόρους του.

Πληροφορίες σχετικά με τους εγγεγραμμένους πόρους είναι διαθέσιμες σε μορφή κειμένου στα αρχεία /proc/ioports και /proc/iomem. Αυτές οι πληροφορίες έχουν εισαχθεί στο Linux από τον πυρήνα 2.3. Ως υπενθύμιση, αυτό το βιβλίο εστιάζει κυρίως στον πυρήνα 2.4 και οι σημειώσεις συμβατότητας θα παρουσιαστούν στο τέλος του κεφαλαίου.

λιμάνια

Τα παρακάτω είναι τα τυπικά περιεχόμενα του αρχείου /proc/ioports:

0000-001f: dma1 0020-003f: pic1 0040-005f: χρονοδιακόπτης 0060-006f: πληκτρολόγιο 0080-008f: dma σελίδα reg 00a0-00bf: pic2 00c0-002p000df 77: ide1 01f0-01f7 : ide0 02f8-02ff: serial(set) 0300-031f: NE2000 0376-0376: ide1 03c0-03df: vga+ 03f6-03f6: ide0 03f8-03ff: serial(set) 103AB2 Corporation 103AB2 Corporation 103AB2 000 -1003 : acpi 1004-1005: acpi 1008-100b: acpi 100c-100f: acpi 1100-110f: Intel Corporation 82371AB PIIX4 IDE 1300-131f: pcnet_cs 1400-100f Corporation -18ff: PCI CardBus #02 1c00- 1cff: PCI CardBus #04 5800-581f: Intel Corporation 82371AB PIIX4 USB d000-dfff: PCI Bus #01 d000-d0ff: ATI Technologies Inc 3D Rage LT Pro AGP-133

Κάθε γραμμή αυτού του αρχείου εμφανίζει σε δεκαεξαδικό το εύρος των θυρών που σχετίζονται με το πρόγραμμα οδήγησης ή τον κάτοχο της συσκευής. Σε προηγούμενες εκδόσεις του πυρήνα, το αρχείο είχε την ίδια μορφή, εκτός από το ότι δεν εμφανιζόταν η ιεραρχία της θύρας.

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

Αυτό που είναι πιο σημαντικό είναι ότι το αρχείο /proc/ioports έχει μια δομή δεδομένων προσβάσιμη μέσω προγραμματισμού που σχετίζεται με αυτό. Επομένως, όταν αρχικοποιείται το πρόγραμμα οδήγησης της συσκευής, μπορεί να γνωρίζει το κατειλημμένο εύρος των θυρών I/O. Αυτό σημαίνει ότι εάν είναι απαραίτητο να σαρώσετε θύρες για αναζήτηση νέας συσκευής, το πρόγραμμα οδήγησης μπορεί να αποφύγει την κατάσταση εγγραφής σε θύρες που καταλαμβάνονται από άλλες συσκευές.

Η σάρωση του διαύλου ISA είναι γνωστό ότι είναι μια επικίνδυνη εργασία. Επομένως, ορισμένα προγράμματα οδήγησης που διανέμονται με τον επίσημο πυρήνα Linux αποφεύγουν μια τέτοια σάρωση κατά τη φόρτωση της λειτουργικής μονάδας. Με αυτόν τον τρόπο, αποφεύγουν τον κίνδυνο να καταστρέψουν ένα λειτουργικό σύστημα γράφοντας σε θύρες που χρησιμοποιούνται από άλλο εξοπλισμό. Ευτυχώς, οι σύγχρονες αρχιτεκτονικές λεωφορείων είναι απρόσβλητες σε αυτά τα προβλήματα.

Η διεπαφή λογισμικού που χρησιμοποιείται για την πρόσβαση σε καταχωρητές εισόδου/εξόδου αποτελείται από τις ακόλουθες τρεις λειτουργίες:

Int check_region(unsigned long start, unsigned long len); struct resource *request_region(unsigned long start, unsigned long len, char *name); void release_region(unsigned long start, unsigned long len);

Λειτουργία check_region()μπορεί να κληθεί να ελέγξει εάν ένα συγκεκριμένο εύρος θυρών είναι κατειλημμένο. Επιστρέφει έναν αρνητικό κωδικό σφάλματος (όπως -EBUSY ή -EINVAL) εάν η απάντηση είναι αρνητική.

Λειτουργία request_region()εκτελεί την εκχώρηση μιας δεδομένης περιοχής διευθύνσεων, επιστρέφοντας, εάν είναι επιτυχής, έναν μη μηδενικό δείκτη. Το πρόγραμμα οδήγησης δεν χρειάζεται να αποθηκεύσει ή να χρησιμοποιήσει τον επιστρεφόμενο δείκτη. Το μόνο που χρειάζεται να κάνετε είναι να ελέγξετε για NULL. Ο κώδικας που πρέπει να λειτουργεί μόνο με πυρήνα 2.4 (ή νεότερης έκδοσης) δεν χρειάζεται καθόλου να καλεί τη συνάρτηση check_region(). Δεν υπάρχει αμφιβολία για το πλεονέκτημα αυτής της μεθόδου διανομής, γιατί
Είναι άγνωστο τι μπορεί να συμβεί μεταξύ των κλήσεων προς check_region() και request_region(). Εάν θέλετε να διατηρήσετε τη συμβατότητα με παλαιότερες εκδόσεις του πυρήνα, τότε είναι απαραίτητη η κλήση check_region() πριν από request_region().

Λειτουργία release_region()πρέπει να κληθεί όταν το πρόγραμμα οδήγησης απελευθερώσει θύρες που χρησιμοποιήθηκαν στο παρελθόν.

Η πραγματική τιμή του δείκτη που επιστρέφεται από την request_region() χρησιμοποιείται μόνο από το υποσύστημα κατανομής πόρων που εκτελείται στον πυρήνα.

Αυτές οι τρεις συναρτήσεις είναι στην πραγματικότητα μακροεντολές που ορίζονται σε .

Παρακάτω είναι ένα παράδειγμα της ακολουθίας κλήσεων που χρησιμοποιείται για την καταχώρηση θυρών. Το παράδειγμα λαμβάνεται από τον κωδικό οδηγού εκπαίδευσης κρανίου. (Ο κώδικας συνάρτησης skull_probe_hw() δεν εμφανίζεται εδώ επειδή περιέχει κώδικα που εξαρτάται από το υλικό.)

#περιλαμβάνω #περιλαμβάνω static int skull_detect(unsigned int port, unsigned int range) (int err; if ((err = check_region(port,range))< 0) return err; /* busy */ if (skull_probe_hw(port,range) != 0) return -ENODEV; /* not found */ request_region(port,range,"skull"); /* "Can"t fail" */ return 0; }

Αυτό το παράδειγμα ελέγχει πρώτα τη διαθεσιμότητα του απαιτούμενου εύρους θυρών. Εάν οι θύρες δεν είναι προσβάσιμες, τότε δεν είναι δυνατή η πρόσβαση στον εξοπλισμό.
Η πραγματική θέση των θυρών της συσκευής μπορεί να αποσαφηνιστεί με σάρωση. Η συνάρτηση request_region() δεν πρέπει, σε αυτό το παράδειγμα,
θα καταλήξει σε αποτυχία. Ο πυρήνας δεν μπορεί να φορτώσει περισσότερες από μία λειτουργικές μονάδες ταυτόχρονα, επομένως δεν θα προκύψουν συγκρούσεις χρήσης θύρας
πρέπει.

Τυχόν θύρες I/O που εκχωρήθηκαν από το πρόγραμμα οδήγησης πρέπει στη συνέχεια να απελευθερωθούν. Το πρόγραμμα οδήγησης κρανίου μας το κάνει αυτό στη συνάρτηση cleanup_module():

Static void skull_release(unsigned int port, unsigned int range) ( release_region(port,range); )

Ο μηχανισμός αιτήματος/απελευθέρωσης πόρων είναι παρόμοιος με τον μηχανισμό εγγραφής/απεγγραφής της μονάδας και υλοποιείται τέλεια με βάση το σχήμα χρήσης του χειριστή goto που περιγράφεται παραπάνω.

Μνήμη

Πληροφορίες σχετικά με τη μνήμη I/O είναι διαθέσιμες μέσω του αρχείου /proc/iomem. Παρακάτω είναι ένα τυπικό παράδειγμα ενός τέτοιου αρχείου για έναν προσωπικό υπολογιστή:

00000000-0009fbff: RAM συστήματος 0009fc00-0009ffff: δεσμευμένο 000a0000-000bffff: Περιοχή RAM βίντεο 000c0000-000c7fff: ROM βίντεο 000f0000-0000-00000-0000FF:1 Systemffff 0 0100000-0022c557: Κωδικός πυρήνα 0022c558-0024455f: Δεδομένα πυρήνα 20000000 - 2fffffff: Intel Corporation 440BX/ZX - 82443BX/ZX Host bridge 68000000-68000fff: Texas Instruments PCI1225 68001000-68001fff: Texas Instruments PCI12001 #0ff 00000-e7 ffffff: PCI Bus #01 e4000000 -e4ffffff : ATI Technologies Inc 3D Rage LT Pro AGP-133 e6000000-e6000fff: ATI Technologies Inc 3D Rage LT Pro AGP-133 fffc0000-ffffffff: με κράτηση

Οι τιμές του εύρους διευθύνσεων εμφανίζονται με δεκαεξαδικό συμβολισμό. Για κάθε περιοχή ares, εμφανίζεται ο ιδιοκτήτης της.

Η καταχώρηση προσβάσεων στη μνήμη I/O είναι παρόμοια με την καταχώρηση θυρών I/O και βασίζεται στον ίδιο μηχανισμό στον πυρήνα.

Για να αποκτήσει και να αποδεσμεύσει το απαιτούμενο εύρος διευθύνσεων μνήμης I/O, το πρόγραμμα οδήγησης πρέπει να χρησιμοποιήσει τις ακόλουθες κλήσεις:

Int check_mem_region(unsigned long start, unsigned long len); int request_mem_region(unsigned long start, unsigned long len, char *name); int release_mem_region(unsigned long start, unsigned long len);

Συνήθως, το πρόγραμμα οδήγησης γνωρίζει το εύρος των διευθύνσεων μνήμης I/O, επομένως ο κώδικας για την εκχώρηση αυτού του πόρου μπορεί να μειωθεί σε σύγκριση με το παράδειγμα για την εκχώρηση μιας περιοχής θυρών:

Εάν (check_mem_region(mem_addr, mem_size)) ( printk("όνομα προγράμματος οδήγησης: μνήμη ήδη χρησιμοποιείται\n"); return -EBUSY; ) request_mem_region(mem_addr, mem_size, "drivername");

Κατανομή πόρων στο Linux 2.4

Ο τρέχων μηχανισμός κατανομής πόρων εισήχθη στον πυρήνα Linux 2.3.11 και παρέχει ευέλικτη πρόσβαση στη διαχείριση πόρων του συστήματος. Αυτή η ενότητα περιγράφει συνοπτικά αυτόν τον μηχανισμό. Ωστόσο, οι βασικές συναρτήσεις κατανομής πόρων (όπως request_region(), κ.λπ.) εξακολουθούν να εφαρμόζονται ως μακροεντολές και χρησιμοποιούνται για συμβατότητα με παλαιότερες εκδόσεις του πυρήνα. Στις περισσότερες περιπτώσεις δεν χρειάζεται να γνωρίζετε τίποτα για τον πραγματικό μηχανισμό διανομής, αλλά μπορεί να είναι ενδιαφέρον όταν δημιουργείτε πιο περίπλοκα προγράμματα οδήγησης.

Το σύστημα διαχείρισης πόρων που εφαρμόζεται στο Linux μπορεί να διαχειρίζεται αυθαίρετους πόρους με ενιαίο ιεραρχικό τρόπο. Οι παγκόσμιοι πόροι του συστήματος (για παράδειγμα, οι θύρες I/O) μπορούν να χωριστούν σε υποσύνολα - για παράδειγμα, αυτά που σχετίζονται με μια συγκεκριμένη υποδοχή διαύλου υλικού. Ορισμένα προγράμματα οδήγησης μπορούν επίσης προαιρετικά να υποδιαιρούν τους καταγεγραμμένους πόρους με βάση τη λογική τους δομή.

Το εύρος των κατανεμημένων πόρων περιγράφεται μέσω της δομής πόρων struct, η οποία δηλώνεται στο αρχείο κεφαλίδας :

Πόρος δομής ( const char *όνομα; ανυπόγραφη μακρά έναρξη, τέλος; ανυπόγραφες μεγάλες σημαίες; πόρος δομής *γονέας, *αδερφός, *παιδί; );

Ένα παγκόσμιο (ριζικό) εύρος πόρων δημιουργείται κατά την εκκίνηση. Για παράδειγμα, μια δομή πόρων που περιγράφει τις θύρες I/O δημιουργείται ως εξής:

Struct resource ioport_resource = ("PCI IO", 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO);

Ένας πόρος που ονομάζεται PCI IO περιγράφεται εδώ, ο οποίος καλύπτει το εύρος διευθύνσεων από μηδέν έως IO_SPACE_LIMIT. Η τιμή αυτής της μεταβλητής εξαρτάται από την πλατφόρμα που χρησιμοποιείται και μπορεί να είναι ίση με 0xFFFF (χώρος διευθύνσεων 16 bit, για αρχιτεκτονικές x86, IA-64, Alpha, M68k και MIPS), 0xFFFFFFFF (χώρος διευθύνσεων 32 bit, για SPARC, PPC , SH) ή 0xFFFFFFFFFFFFFFFF (64-bit, SPARC64).

Υποκατηγορίες αυτού του πόρου μπορούν να δημιουργηθούν χρησιμοποιώντας μια κλήση στο allocate_resource(). Για παράδειγμα, κατά την προετοιμασία του διαύλου PCI, δημιουργείται ένας νέος πόρος για την περιοχή διευθύνσεων αυτού του διαύλου και εκχωρείται σε μια φυσική συσκευή. Όταν ο κώδικας του πυρήνα PCI επεξεργάζεται εκχωρήσεις θύρας και μνήμης, δημιουργεί έναν νέο πόρο μόνο για αυτές τις περιοχές και τις εκχωρεί χρησιμοποιώντας κλήσεις στην ioport_resource() ή την iomem_resource().

Το πρόγραμμα οδήγησης μπορεί στη συνέχεια να ζητήσει ένα υποσύνολο ενός πόρου (συνήθως μέρος ενός καθολικού πόρου) και να το επισημάνει ως κατειλημμένο. Η απόκτηση πόρων ολοκληρώνεται καλώντας την request_region(), η οποία επιστρέφει είτε έναν δείκτη σε μια νέα δομή πόρων δομής που περιγράφει τον ζητούμενο πόρο είτε NULL σε σφάλμα. Αυτή η δομή είναι μέρος του παγκόσμιου δέντρου πόρων. Όπως αναφέρθηκε ήδη, μετά την απόκτηση του πόρου, το πρόγραμμα οδήγησης δεν θα χρειαστεί την τιμή αυτού του δείκτη.

Ο ενδιαφερόμενος αναγνώστης μπορεί να απολαύσει την προβολή των λεπτομερειών αυτού του σχήματος διαχείρισης πόρων στο αρχείο kernel/resource.c που βρίσκεται στον κατάλογο πηγών πυρήνα. Ωστόσο, για τους περισσότερους προγραμματιστές η γνώση που έχει ήδη παρουσιαστεί θα είναι επαρκής.

Ο πολυεπίπεδος μηχανισμός κατανομής πόρων αποφέρει διπλά οφέλη. Από τη μία πλευρά, δίνει μια οπτική αναπαράσταση των δομών δεδομένων του πυρήνα. Ας ρίξουμε μια ματιά στο παράδειγμα αρχείου /proc/ioports ξανά:

E800-e8ff: Adaptec AHA-2940U2/W / 7890 e800-e8be: aic7xxx

Η περιοχή e800-e8ff εκχωρείται στον προσαρμογέα Adaptec, ο οποίος αυτοπροσδιορίστηκε ως πρόγραμμα οδήγησης στο δίαυλο PCI. Το μεγαλύτερο μέρος αυτής της περιοχής ζητήθηκε από το πρόγραμμα οδήγησης aic7xxx.

Ένα άλλο πλεονέκτημα αυτής της διαχείρισης πόρων είναι η διαίρεση του χώρου διευθύνσεων σε υποπεριοχές που αντικατοπτρίζουν την πραγματική διασύνδεση του εξοπλισμού. Η διαχείριση πόρων δεν μπορεί να εκχωρήσει επικαλυπτόμενες υποπεριοχές διευθύνσεων, γεγονός που μπορεί να αποτρέψει την εγκατάσταση ενός προγράμματος οδήγησης που δυσλειτουργεί.

Αυτόματη και χειροκίνητη διαμόρφωση

Ορισμένες παράμετροι που απαιτούνται από το πρόγραμμα οδήγησης ενδέχεται να διαφέρουν από σύστημα σε σύστημα. Για παράδειγμα, το πρόγραμμα οδήγησης πρέπει να γνωρίζει τις έγκυρες διευθύνσεις I/O και τις περιοχές μνήμης. Για καλά οργανωμένες διεπαφές διαύλου αυτό δεν αποτελεί πρόβλημα. Ωστόσο, μερικές φορές θα χρειαστεί να μεταβιβάσετε παραμέτρους στο πρόγραμμα οδήγησης για να το βοηθήσετε να βρει τη δική του συσκευή ή να ενεργοποιήσετε/απενεργοποιήσετε ορισμένες από τις λειτουργίες του.

Αυτές οι ρυθμίσεις που επηρεάζουν τη λειτουργία του προγράμματος οδήγησης διαφέρουν ανάλογα με τη συσκευή. Για παράδειγμα, αυτός θα μπορούσε να είναι ο αριθμός έκδοσης της εγκατεστημένης συσκευής. Φυσικά, τέτοιες πληροφορίες είναι απαραίτητες για τη σωστή λειτουργία του προγράμματος οδήγησης με τη συσκευή. Ο καθορισμός τέτοιων παραμέτρων (διαμόρφωση προγράμματος οδήγησης) είναι αρκετά σημαντικός
μια δύσκολη εργασία που εκτελείται κατά την προετοιμασία του προγράμματος οδήγησης.

Συνήθως, υπάρχουν δύο τρόποι για να λάβετε τις σωστές τιμές αυτής της παραμέτρου - είτε ο χρήστης τις ορίζει ρητά είτε ο οδηγός τις καθορίζει ανεξάρτητα, με βάση τη μέτρηση του εξοπλισμού. Αν και η αυτόματη ανίχνευση είναι αναμφίβολα η καλύτερη λύση για τη διαμόρφωση του προγράμματος οδήγησης,
Η προσαρμοσμένη διαμόρφωση είναι πολύ πιο εύκολη στην εφαρμογή. Ο προγραμματιστής του προγράμματος οδήγησης θα πρέπει να εφαρμόσει την αυτόματη διαμόρφωση του προγράμματος οδήγησης όπου είναι δυνατόν, αλλά ταυτόχρονα θα πρέπει να παρέχει στο χρήστη έναν μηχανισμό μη αυτόματης διαμόρφωσης. Φυσικά, η χειροκίνητη διαμόρφωση θα πρέπει να έχει μεγαλύτερη προτεραιότητα από την αυτόματη διαμόρφωση. Στα αρχικά στάδια ανάπτυξης, συνήθως εφαρμόζεται μόνο χειροκίνητη μετάδοση παραμέτρων στον οδηγό. Η αυτόματη διαμόρφωση, εάν είναι δυνατόν, προστίθεται αργότερα.

Πολλά προγράμματα οδήγησης, μεταξύ των παραμέτρων διαμόρφωσής τους, έχουν παραμέτρους που ελέγχουν τις λειτουργίες του προγράμματος οδήγησης. Για παράδειγμα, τα προγράμματα οδήγησης διασύνδεσης Integrated Device Electronics (IDE) επιτρέπουν στο χρήστη να ελέγχει τις λειτουργίες DMA. Επομένως, εάν το πρόγραμμα οδήγησης σας κάνει καλή δουλειά στον αυτόματο εντοπισμό υλικού, ίσως θέλετε να δώσετε στον χρήστη τον έλεγχο της λειτουργικότητας του προγράμματος οδήγησης.

Οι τιμές των παραμέτρων μπορούν να μεταβιβαστούν κατά τη φόρτωση της μονάδας χρησιμοποιώντας τις εντολές insmod ή modprobe. Πρόσφατα κατέστη δυνατή η ανάγνωση της τιμής των παραμέτρων από ένα αρχείο διαμόρφωσης (συνήθως /etc/modules.conf). Οι τιμές ακέραιων και συμβολοσειρών μπορούν να μεταβιβαστούν ως παράμετροι. Έτσι, εάν πρέπει να περάσετε μια ακέραια τιμή για την παράμετρο skull_ival και μια τιμή συμβολοσειράς για την παράμετρο skull_sval, μπορείτε να τις μεταβιβάσετε κατά τη φόρτωση της μονάδας με πρόσθετες παραμέτρους στην εντολή insmod:

Insmod skull skull_ival=666 skull_sval="the beast"

Ωστόσο, προτού η εντολή insmod μπορέσει να αλλάξει τις τιμές των παραμέτρων μιας μονάδας, η μονάδα πρέπει να καταστήσει διαθέσιμες αυτές τις παραμέτρους. Οι παράμετροι δηλώνονται χρησιμοποιώντας τον ορισμό μακροεντολής MODULE_PARM, ο οποίος ορίζεται στο αρχείο κεφαλίδας module.h. Η μακροεντολή MODULE_PARM λαμβάνει δύο παραμέτρους: το όνομα της μεταβλητής και μια συμβολοσειρά που καθορίζει τον τύπο της. Αυτός ο ορισμός μακροεντολής πρέπει να τοποθετηθεί εκτός οποιασδήποτε συνάρτησης και συνήθως βρίσκεται στην αρχή του αρχείου μετά τον καθορισμό των μεταβλητών. Έτσι, οι δύο παράμετροι που αναφέρονται παραπάνω μπορούν να δηλωθούν ως εξής:

Int skull_ival=0; char *skull_sval; MODULE_PARM(skull_ival, "i"); MODULE_PARM(skull_sval, "s");

Προς το παρόν, υποστηρίζονται πέντε τύποι παραμέτρων μονάδας:

  • b - τιμή ενός byte.
  • h - (σύντομη) τιμή δύο byte.
  • i - ακέραιος αριθμός;
  • l - μακρύς ακέραιος.
  • s - συμβολοσειρά (char *);

Στην περίπτωση των παραμέτρων συμβολοσειράς, ένας δείκτης (char *) πρέπει να δηλωθεί στη μονάδα. Η εντολή insmod εκχωρεί μνήμη για τη συμβολοσειρά που μεταβιβάζεται και την προετοιμάζει με την απαιτούμενη τιμή. Χρησιμοποιώντας τη μακροεντολή MODULE_PARM, μπορείτε να αρχικοποιήσετε πίνακες παραμέτρων. Σε αυτήν την περίπτωση, ο ακέραιος αριθμός που προηγείται του χαρακτήρα τύπου καθορίζει το μήκος του πίνακα. Όταν καθορίζονται δύο ακέραιοι αριθμοί, χωρισμένοι με παύλα, καθορίζουν τον ελάχιστο και μέγιστο αριθμό τιμών που πρέπει να μεταδοθούν. Για μια πιο λεπτομερή κατανόηση του τρόπου λειτουργίας αυτής της μακροεντολής, ανατρέξτε στο αρχείο κεφαλίδας .

Για παράδειγμα, ας υποθέσουμε ότι ένας πίνακας παραμέτρων πρέπει να αρχικοποιηθεί με τουλάχιστον δύο και τουλάχιστον τέσσερις ακέραιες τιμές. Τότε μπορεί να περιγραφεί ως εξής:

Int skull_array; MODULE_PARM(συστοιχία_κρανίου, "2-4i");

Επιπλέον, η εργαλειοθήκη του προγραμματιστή έχει τον ορισμό μακροεντολής MODULE_PARM_DESC, ο οποίος σας επιτρέπει να τοποθετείτε σχόλια στις παραμέτρους της μονάδας που μεταβιβάζονται. Αυτά τα σχόλια αποθηκεύονται στο αρχείο αντικειμένων λειτουργικής μονάδας και μπορούν να προβληθούν χρησιμοποιώντας, για παράδειγμα, το βοηθητικό πρόγραμμα objdump ή χρησιμοποιώντας αυτοματοποιημένα εργαλεία διαχείρισης συστήματος. Ακολουθεί ένα παράδειγμα χρήσης αυτού του ορισμού μακροεντολής:

Int base_port = 0x300; MODULE_PARM(base_port, "i"); MODULE_PARM_DESC (base_port, "Η βασική θύρα I/O (προεπιλογή 0x300)");

Είναι επιθυμητό όλες οι παράμετροι της μονάδας να έχουν προεπιλεγμένες τιμές. Η αλλαγή αυτών των τιμών χρησιμοποιώντας το insmod θα πρέπει να απαιτείται μόνο εάν είναι απαραίτητο. Η μονάδα μπορεί να ελέγξει τη ρητή ρύθμιση των παραμέτρων ελέγχοντας τις τρέχουσες τιμές τους με τις προεπιλεγμένες τιμές. Στη συνέχεια, μπορείτε να εφαρμόσετε έναν μηχανισμό αυτόματης διαμόρφωσης με βάση το παρακάτω διάγραμμα. Εάν οι τιμές των παραμέτρων έχουν προεπιλεγμένες τιμές, τότε εκτελείται η αυτόματη διαμόρφωση. Διαφορετικά, χρησιμοποιούνται οι τρέχουσες τιμές. Για να λειτουργήσει αυτό το σχήμα, είναι απαραίτητο οι προεπιλεγμένες τιμές να μην αντιστοιχούν σε καμία από τις πιθανές διαμορφώσεις συστήματος του πραγματικού κόσμου. Στη συνέχεια, θα υποτεθεί ότι τέτοιες τιμές δεν μπορούν να οριστούν από τον χρήστη σε μη αυτόματη διαμόρφωση.

Το ακόλουθο παράδειγμα δείχνει πώς το πρόγραμμα οδήγησης κρανίου εντοπίζει αυτόματα τον χώρο διευθύνσεων των θυρών της συσκευής. Στο παραπάνω παράδειγμα, η αυτόματη ανίχνευση εξετάζει πολλές συσκευές, ενώ η μη αυτόματη διαμόρφωση περιορίζει το πρόγραμμα οδήγησης σε μία μόνο συσκευή. Έχετε ήδη συναντήσει τη συνάρτηση skull_detect νωρίτερα στην ενότητα που περιγράφει τις θύρες I/O. Η υλοποίηση της skull_init_board() δεν εμφανίζεται επειδή
Εκτελεί αρχικοποίηση ανάλογα με το υλικό.

/* * Εύρος θυρών: η συσκευή μπορεί να βρίσκεται μεταξύ * 0x280 και 0x300, σε βήματα 0x10. Χρησιμοποιεί θύρες 0x10. */ #define SKULL_PORT_FLOOR 0x280 #define SKULL_PORT_CEIL 0x300 #define SKULL_PORT_RANGE 0x010 /* * η ακόλουθη συνάρτηση εκτελεί αυτόματη ανίχνευση, εκτός εάν μια συγκεκριμένη τιμή * έχει εκχωρηθεί από το insmod στο "skull_port_static_base"*/ /* 0 αναγκάζει την αυτόματη ανίχνευση */ MODULE_PARM (skull_port_base, "i"); MODULE_PARM_DESC(skull_port_base, "Base I/O port for skull"); static int skull_find_hw(void) /* επιστρέφει το # των συσκευών */ ( /* η βάση είναι είτε η τιμή χρόνου φόρτωσης είτε η πρώτη δοκιμή */ int base = skull_port_base; skull_port_base: SKULL_PORT_FLOOR; int αποτέλεσμα = 0; /* loop one χρόνος εάν έχει εκχωρηθεί η τιμή; δοκιμάστε τα όλα εάν γίνεται αυτόματη ανίχνευση */ do ( if (skull_detect(base, SKULL_PORT_RANGE) == 0) ( skull_init_board(base); result++; ) base += SKULL_PORT_RANGE; /* προετοιμασία για την επόμενη δοκιμή */ ) ενώ (skull_port_base == 0 && βάση< SKULL_PORT_CEIL); return result; }

Εάν οι μεταβλητές διαμόρφωσης χρησιμοποιούνται μόνο μέσα στο πρόγραμμα οδήγησης (δηλαδή δεν δημοσιεύονται στον πίνακα συμβόλων του πυρήνα), τότε ο προγραμματιστής μπορεί να κάνει τη ζωή του χρήστη λίγο πιο εύκολη, μη χρησιμοποιώντας προθέματα στα ονόματα των μεταβλητών (στην περίπτωσή μας, το πρόθεμα skull_) . Αυτά τα προθέματα σημαίνουν ελάχιστα για τον χρήστη και η απουσία τους απλοποιεί την πληκτρολόγηση της εντολής από το πληκτρολόγιο.

Για πληρότητα, θα παρέχουμε μια περιγραφή τριών ακόμη ορισμών μακροεντολών που σας επιτρέπουν να τοποθετήσετε ορισμένα σχόλια στο αρχείο αντικειμένου.

MODULE_AUTHOR (όνομα)Τοποθετεί μια γραμμή με το όνομα του συγγραφέα στο αρχείο αντικειμένου. MODULE_DESCRIPTION(περιγραφή)Τοποθετεί μια γραμμή με μια γενική περιγραφή της ενότητας στο αρχείο αντικειμένου. MODULE_SUPPORTED_DEVICE(dev)Τοποθετεί μια γραμμή που περιγράφει τη συσκευή που υποστηρίζεται από τη μονάδα. Το Linux παρέχει ένα ισχυρό και εκτεταμένο API για εφαρμογές, αλλά μερικές φορές δεν είναι αρκετό. Για να αλληλεπιδράσετε με το υλικό ή να εκτελέσετε λειτουργίες με πρόσβαση σε προνομιακές πληροφορίες στο σύστημα, απαιτείται πρόγραμμα οδήγησης πυρήνα.

Μια λειτουργική μονάδα πυρήνα Linux είναι ένας μεταγλωττισμένος δυαδικός κώδικας που εισάγεται απευθείας στον πυρήνα του Linux, εκτελώντας τον δακτύλιο 0, τον εσωτερικό και λιγότερο ασφαλή δακτύλιο εκτέλεσης εντολών στον επεξεργαστή x86–64. Εδώ ο κώδικας εκτελείται εντελώς χωρίς κανέναν έλεγχο, αλλά με απίστευτη ταχύτητα και με πρόσβαση σε οποιουσδήποτε πόρους του συστήματος.

Όχι για απλούς θνητούς

Η σύνταξη μιας μονάδας πυρήνα Linux δεν είναι για τους αδύναμους. Με την αλλαγή του πυρήνα, κινδυνεύετε να χάσετε δεδομένα. Δεν υπάρχει τυπική ασφάλεια στον κώδικα του πυρήνα όπως υπάρχει σε κανονικές εφαρμογές Linux. Εάν κάνετε λάθος, κλείστε ολόκληρο το σύστημα.

Αυτό που κάνει την κατάσταση χειρότερη είναι ότι το πρόβλημα δεν εμφανίζεται απαραίτητα αμέσως. Εάν η μονάδα κρεμάσει το σύστημα αμέσως μετά τη φόρτωση, τότε αυτό είναι το καλύτερο σενάριο αποτυχίας. Όσο περισσότερος κώδικας υπάρχει, τόσο μεγαλύτερος είναι ο κίνδυνος άπειρων βρόχων και διαρροών μνήμης. Εάν δεν είστε προσεκτικοί, τα προβλήματα θα αυξηθούν σταδιακά καθώς λειτουργεί το μηχάνημα. Τελικά, σημαντικές δομές δεδομένων, ακόμη και buffer, μπορούν να αντικατασταθούν.

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

Επίσης, δεν έχετε πρόσβαση στην τυπική βιβλιοθήκη. Παρόλο που ο πυρήνας παρέχει ορισμένες λειτουργίες όπως το printk (που αντικαθιστά το printf) και το kmalloc (που λειτουργεί παρόμοια με το malloc), αφεθείτε κυρίως στις δικές σας συσκευές. Επιπλέον, θα πρέπει να καθαρίσετε πλήρως τον εαυτό σας μετά την εκφόρτωση της μονάδας. Εδώ δεν υπάρχει αποκομιδή σκουπιδιών.

Απαιτούμενα εξαρτήματα

Πριν ξεκινήσετε, θα πρέπει να βεβαιωθείτε ότι έχετε όλα τα απαραίτητα εργαλεία για την εργασία. Το πιο σημαντικό, χρειάζεστε μια μηχανή Linux. Ξέρω ότι αυτό είναι απροσδόκητο! Αν και οποιαδήποτε διανομή Linux μπορεί να κάνει, χρησιμοποιώ το Ubuntu 16.04 LTS σε αυτό το παράδειγμα, οπότε ίσως χρειαστεί να τροποποιήσετε ελαφρώς τις εντολές εγκατάστασης εάν χρησιμοποιείτε άλλες διανομές.

Δεύτερον, χρειάζεστε είτε μια ξεχωριστή φυσική μηχανή είτε μια εικονική μηχανή. Προσωπικά, προτιμώ να εργάζομαι σε εικονική μηχανή, αλλά πάρτε την επιλογή σας. Δεν συνιστώ τη χρήση του κύριου υπολογιστή σας λόγω απώλειας δεδομένων όταν κάνετε λάθος. Λέω «πότε» και όχι «αν» γιατί σίγουρα θα κρεμάσεις το αυτοκίνητο τουλάχιστον μερικές φορές κατά τη διάρκεια της διαδικασίας. Οι τελευταίες αλλαγές στον κώδικα ενδέχεται να εξακολουθούν να βρίσκονται στο buffer εγγραφής όταν ο πυρήνας πανικοβάλλεται, επομένως οι πηγές σας μπορεί επίσης να είναι κατεστραμμένες. Η δοκιμή σε μια εικονική μηχανή εξαλείφει αυτούς τους κινδύνους.

Τέλος, πρέπει να γνωρίζετε τουλάχιστον λίγο C. Ο χρόνος εκτέλεσης της C++ είναι πολύ μεγάλος για τον πυρήνα, επομένως πρέπει να γράφετε σε καθαρό, γυμνό C. Ορισμένες γνώσεις της γλώσσας assembly είναι επίσης χρήσιμες για την αλληλεπίδραση με το υλικό.

Εγκατάσταση του περιβάλλοντος ανάπτυξης

Στο Ubuntu πρέπει να εκτελέσετε:

Apt-get install build-essential linux-headers-`uname -r`
Εγκαθιστούμε τα πιο σημαντικά εργαλεία ανάπτυξης και τις κεφαλίδες του πυρήνα που απαιτούνται για αυτό το παράδειγμα.

Τα παρακάτω παραδείγματα υποθέτουν ότι τρέχετε ως κανονικός χρήστης και όχι ως root, αλλά ότι έχετε δικαιώματα sudo. Το Sudo απαιτείται για τη φόρτωση λειτουργικών μονάδων πυρήνα, αλλά θέλουμε να εργαζόμαστε εκτός root όποτε είναι δυνατόν.

Αρχίζουν

Ας αρχίσουμε να γράφουμε κώδικα. Ας προετοιμάσουμε το περιβάλλον μας:

Mkdir ~/src/lkm_example cd ~/src/lkm_example
Εκκινήστε τον αγαπημένο σας επεξεργαστή (vim στην περίπτωσή μου) και δημιουργήστε ένα αρχείο lkm_example.c με το ακόλουθο περιεχόμενο:

#περιλαμβάνω #περιλαμβάνω #περιλαμβάνω MODULE_LICENSE ("GPL"); MODULE_AUTHOR ("Robert W. Oliver II"); MODULE_DESCRIPTION("Ένα απλό παράδειγμα λειτουργικής μονάδας Linux."); MODULE_VERSION(“0,01”); static int __init lkm_example_init(void) ( printk(KERN_INFO "Hello, World!\n"); return 0; ) static void __exit lkm_example_exit(void) ( printk(KERN_INFO "Goodbye, World!\n"); ) ) module_exit(lkm_example_exit);
Έχουμε σχεδιάσει την απλούστερη δυνατή ενότητα, ας ρίξουμε μια πιο προσεκτική ματιά στα πιο σημαντικά μέρη της:

  • περιλαμβάνει λίστες με τα αρχεία κεφαλίδας που απαιτούνται για την ανάπτυξη του πυρήνα του Linux.
  • Το MODULE_LICENSE μπορεί να ρυθμιστεί σε διαφορετικές τιμές ανάλογα με την άδεια χρήσης της μονάδας. Για να δείτε την πλήρη λίστα, εκτελέστε:

    Grep “MODULE_LICENSE” -B 27 /usr/src/linux-headers-`unname -r`/include/linux/module.h

  • Ρυθμίζουμε την init (φόρτωση) και την έξοδο (ξεφόρτωση) ως στατικές συναρτήσεις που επιστρέφουν ακέραιους αριθμούς.
  • Σημειώστε τη χρήση του printk αντί του printf. Επίσης, οι επιλογές για το printk είναι διαφορετικές από το printf. Για παράδειγμα, η σημαία KERN_INFO για τη δήλωση της προτεραιότητας καταγραφής για μια συγκεκριμένη γραμμή καθορίζεται χωρίς κόμμα. Ο πυρήνας χειρίζεται αυτά τα πράγματα μέσα στη συνάρτηση printk για εξοικονόμηση μνήμης στοίβας.
  • Στο τέλος του αρχείου, μπορείτε να καλέσετε module_init και module_exit και να καθορίσετε τις συναρτήσεις φόρτωσης και εκφόρτωσης. Αυτό καθιστά δυνατή την αυθαίρετη ονομασία συναρτήσεων.
Ωστόσο, δεν μπορούμε να μεταγλωττίσουμε αυτό το αρχείο ακόμα. Χρειάζεται makefile. Αυτό το βασικό παράδειγμα αρκεί προς το παρόν. Σημειώστε ότι η μάρκα είναι πολύ επιλεκτική όσον αφορά τα κενά και τις καρτέλες, επομένως φροντίστε να χρησιμοποιείτε καρτέλες αντί για κενά όπου χρειάζεται.

Obj-m += lkm_example.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r )/κατασκευή M=$(PWD) καθαρό
Αν τρέξουμε το make θα πρέπει να μεταγλωττίσει το module μας με επιτυχία. Το αποτέλεσμα θα είναι το αρχείο lkm_example.ko. Εάν παρουσιαστούν σφάλματα, ελέγξτε ότι τα εισαγωγικά στον πηγαίο κώδικα έχουν οριστεί σωστά και όχι κατά λάθος στην κωδικοποίηση UTF-8.

Τώρα μπορείτε να εφαρμόσετε τη μονάδα και να τη δοκιμάσετε. Για να το κάνουμε αυτό τρέχουμε:

Sudo insmod lkm_example.ko
Αν όλα είναι καλά, τότε δεν θα δείτε τίποτα. Η συνάρτηση printk παρέχει έξοδο όχι στην κονσόλα, αλλά στο αρχείο καταγραφής του πυρήνα. Για προβολή πρέπει να τρέξετε:

Sudo dmesg
Θα πρέπει να δείτε τη γραμμή "Hello, World!" με χρονική σήμανση στην αρχή. Αυτό σημαίνει ότι η μονάδα πυρήνα έχει φορτωθεί και εγγραφεί με επιτυχία στο αρχείο καταγραφής του πυρήνα. Μπορούμε επίσης να ελέγξουμε ότι η μονάδα είναι ακόμα στη μνήμη:

lsmod | grep "lkm_example"
Για να αφαιρέσετε μια λειτουργική μονάδα, εκτελέστε:

Sudo rmmod lkm_example
Εάν εκτελέσετε ξανά το dmesg, θα δείτε την καταχώρηση «Goodbye, World!» στο αρχείο καταγραφής. Μπορείτε να εκτελέσετε ξανά το lsmod και να βεβαιωθείτε ότι η μονάδα έχει ξεφορτωθεί.

Όπως μπορείτε να δείτε, αυτή η διαδικασία δοκιμής είναι λίγο κουραστική, αλλά μπορεί να αυτοματοποιηθεί προσθέτοντας:

Δοκιμή: sudo dmesg -C sudo insmod lkm_example.ko sudo rmmod lkm_example.ko dmesg
στο τέλος του Makefile και στη συνέχεια εκτελείται:

Κάντε τεστ
για να δοκιμάσετε τη μονάδα και να ελέγξετε την έξοδο στο αρχείο καταγραφής του πυρήνα χωρίς να χρειάζεται να εκτελέσετε ξεχωριστές εντολές.

Τώρα έχουμε μια πλήρως λειτουργική, αν και εντελώς ασήμαντη, μονάδα πυρήνα!

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

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

Πιθανόν να έχετε ασχοληθεί με αρχεία συσκευής στο παρελθόν. Οι εντολές που αναφέρουν τα /dev/zero , /dev/null και παρόμοια αλληλεπιδρούν με τις συσκευές "zero" και "null", οι οποίες επιστρέφουν τις αναμενόμενες τιμές.

Στο παράδειγμά μας επιστρέφουμε "Hello, World". Αν και αυτό δεν είναι μια ιδιαίτερα χρήσιμη δυνατότητα για εφαρμογές, εξακολουθεί να δείχνει τη διαδικασία αλληλεπίδρασης με μια εφαρμογή μέσω ενός αρχείου συσκευής.

Εδώ είναι η πλήρης λίστα:

#περιλαμβάνω #περιλαμβάνω #περιλαμβάνω #περιλαμβάνω #περιλαμβάνω MODULE_LICENSE ("GPL"); MODULE_AUTHOR ("Robert W. Oliver II"); MODULE_DESCRIPTION("Ένα απλό παράδειγμα λειτουργικής μονάδας Linux."); MODULE_VERSION(“0,01”); #define DEVICE_NAME "lkm_example" #define EXAMPLE_MSG "Γεια σου, Κόσμε!\n" #define MSG_BUFFER_LEN 15 /* Πρωτότυπα για λειτουργίες συσκευής */ static int device_open(struct inode *, struct file *); static int device_release(struct inode *, struct file *); static ssize_t device_read(struct file *, char *, size_t, loff_t *); static ssize_t device_write(struct file *, const char *, size_t, loff_t *); static int major_num; static int device_open_count = 0; static char msg_buffer; στατικό char *msg_ptr; /* Αυτή η δομή δείχνει όλες τις λειτουργίες της συσκευής */ static struct file_operations file_ops = ( .read = device_read, .write = device_write, .open = device_open, .release = device_release ); /* Όταν μια διεργασία διαβάζει από τη συσκευή μας, αυτή καλείται. */ static ssize_t device_read(struct file *flip, char *buffer, size_t len, loff_t *offset) ( int bytes_read = 0; /* Εάν βρισκόμαστε στο τέλος, κάντε βρόχο πίσω στην αρχή */ if (*msg_ptr = = 0) ( msg_ptr = msg_buffer; ) /* Τοποθετήστε δεδομένα στην προσωρινή μνήμη */ ενώ (len && *msg_ptr) ( /* Η προσωρινή μνήμη βρίσκεται στα δεδομένα χρήστη, όχι στον πυρήνα, επομένως δεν μπορείτε απλώς να αναφέρετε * με έναν δείκτη. Η συνάρτηση put_user χειρίζεται αυτό για εμάς */ put_user(*(msg_ptr++), buffer++); len--; bytes_read++; ) return bytes_read; ) /* Καλείται όταν μια διεργασία προσπαθεί να γράψει στη συσκευή μας */ static ssize_t device_write(struct file * flip, const char *buffer, size_t len, loff_t *offset) ( /* Αυτή είναι μια συσκευή μόνο για ανάγνωση */ printk(KERN_ALERT "Αυτή η λειτουργία δεν υποστηρίζεται.\n"); return -EINVAL; ) /* Καλείται όταν μια διαδικασία ανοίγει τη συσκευή μας */ static int device_open(struct inode *inode, struct file *file) ( /* Εάν η συσκευή είναι ανοιχτή, επιστρέψτε κατειλημμένη */ if (device_open_count) ( return -EBUSY; ) device_open_count++; try_module_get(THIS_MODULE); επιστροφή 0; ) /* Καλείται όταν μια διεργασία κλείνει τη συσκευή μας */ static int device_release(struct inode *inode, struct file *file) ( /* Μειώστε τον ανοιχτό μετρητή και τον αριθμό χρήσης. Χωρίς αυτό, η μονάδα δεν θα ξεφόρτωνε. */ device_open_count- -; module_put(THIS_MODULE); return 0; ) static int __init lkm_example_init(void) ( /* Συμπληρώστε το buffer με το μήνυμά μας */ strncpy(msg_buffer, EXAMPLE_MSG, MSG_BUFFER_LEN); /* Ορίστε το msg_ptr στο buffer_ms ; /* Προσπαθήστε να καταχωρήσετε τη συσκευή χαρακτήρων */ major_num = register_chrdev(0, "lkm_example", &file_ops); εάν (major_num< 0) { printk(KERN_ALERT “Could not register device: %d\n”, major_num); return major_num; } else { printk(KERN_INFO “lkm_example module loaded with device major number %d\n”, major_num); return 0; } } static void __exit lkm_example_exit(void) { /* Remember - we have to clean up after ourselves. Unregister the character device. */ unregister_chrdev(major_num, DEVICE_NAME); printk(KERN_INFO “Goodbye, World!\n”); } /* Register module functions */ module_init(lkm_example_init); module_exit(lkm_example_exit);

Δοκιμή ενός βελτιωμένου παραδείγματος

Τώρα το παράδειγμά μας δεν εκτυπώνει απλώς ένα μήνυμα κατά τη φόρτωση και την εκφόρτωση, επομένως θα χρειαστεί μια λιγότερο αυστηρή διαδικασία δοκιμής. Ας αλλάξουμε το Makefile για να φορτώνει μόνο το module, χωρίς να το ξεφορτώνουμε.

Obj-m += lkm_example.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r )/build M=$(PWD) καθαρός έλεγχος: # Βάζουμε ένα - μπροστά από την εντολή rmmod για να πούμε make να αγνοήσει # ένα σφάλμα σε περίπτωση που η μονάδα δεν έχει φορτωθεί. -sudo rmmod lkm_example # Εκκαθάριση του αρχείου καταγραφής του πυρήνα χωρίς echo sudo dmesg -C # Εισαγάγετε τη μονάδα sudo insmod lkm_example.ko # Εμφάνιση του αρχείου καταγραφής του πυρήνα dmesg
Τώρα, αφού εκτελέσετε τη δοκιμή make, θα δείτε να βγαίνει ο κύριος αριθμός συσκευής. Στο παράδειγμά μας, εκχωρείται αυτόματα από τον πυρήνα. Ωστόσο, αυτός ο αριθμός είναι απαραίτητος για τη δημιουργία μιας νέας συσκευής.

Πάρτε τον αριθμό που δημιουργήθηκε από τη δοκιμή make και χρησιμοποιήστε τον για να δημιουργήσετε ένα αρχείο συσκευής, ώστε να μπορούμε να επικοινωνούμε με τη μονάδα πυρήνα από το χώρο χρήστη.

Sudo mknod /dev/lkm_example με MAJOR 0
(σε αυτό το παράδειγμα, αντικαταστήστε το MAJOR με την τιμή που λαμβάνεται από τη δοκιμή make ή dmesg)

Η επιλογή c στην εντολή mknod λέει στο mknod ότι πρέπει να δημιουργήσουμε ένα αρχείο συσκευής χαρακτήρων.

Τώρα μπορούμε να λάβουμε περιεχόμενο από τη συσκευή:

Cat /dev/lkm_example
ή ακόμα και μέσω της εντολής dd:

Dd if=/dev/lkm_example of=test bs=14 count=100
Μπορείτε επίσης να αποκτήσετε πρόσβαση σε αυτό το αρχείο από εφαρμογές. Αυτά δεν χρειάζεται να είναι μεταγλωττισμένες εφαρμογές - ακόμη και τα σενάρια Python, Ruby και PHP έχουν πρόσβαση σε αυτά τα δεδομένα.

Όταν τελειώσουμε με τη συσκευή, την αφαιρούμε και ξεφορτώνουμε τη μονάδα:

Sudo rm /dev/lkm_example sudo rmmod lkm_example

συμπέρασμα

Ελπίζω να απολαύσατε τις φάρσες μας στον κεντρικό χώρο. Αν και τα παραδείγματα που παρουσιάζονται είναι πρωτόγονα, αυτές οι δομές μπορούν να χρησιμοποιηθούν για τη δημιουργία των δικών σας λειτουργικών μονάδων που εκτελούν πολύ περίπλοκες εργασίες.

Απλώς να θυμάστε ότι στον χώρο του πυρήνα τα πάντα είναι δική σας ευθύνη. Δεν υπάρχει υποστήριξη ή δεύτερη ευκαιρία για τον κωδικό σας. Εάν κάνετε ένα έργο για έναν πελάτη, προγραμματίστε εκ των προτέρων για διπλό, αν όχι τριπλό, χρόνο εντοπισμού σφαλμάτων. Ο κώδικας πυρήνα πρέπει να είναι όσο το δυνατόν τέλειος για να διασφαλίζεται η ακεραιότητα και η αξιοπιστία των συστημάτων στα οποία εκτελείται.

Μερικά χαρακτηριστικά του αρθρωτού προγραμματισμού και γενικές συστάσεις για την κατασκευή υποπρογραμμάτων μιας αρθρωτής δομής.

Οι μονάδες συνδέονται στο κύριο πρόγραμμα με τη σειρά με την οποία δηλώνονται ΧΡΗΣΕΙΣ, και με την ίδια σειρά είναι τα μπλοκ αρχικοποίησης των μονάδων που συνδέονται στο κύριο πρόγραμμα πριν ξεκινήσει η εκτέλεση του προγράμματος.

Η σειρά με την οποία εκτελούνται οι μονάδες μπορεί να επηρεάσει την πρόσβαση στα δεδομένα της βιβλιοθήκης και την πρόσβαση ρουτίνας.

Για παράδειγμα, εάν οι μονάδες με τα ονόματα M1, M2 περιέχουν τον ίδιο τύπο A, μεταβλητή B και υπορουτίνα C, τότε μετά τη σύνδεση αυτών των μοντέλων USES, οι κλήσεις σε A, B, C σε αυτήν την PU θα είναι ισοδύναμες με κλήσεις προς αντικείμενα στη μονάδα M2 .

Αλλά για να χαρακτηριστεί η ορθότητα των κλήσεων προς τα απαραίτητα αντικείμενα με το ίδιο όνομα από διαφορετικές συνδεδεμένες μονάδες, συνιστάται κατά την πρόσβαση σε μια μονάδα να υποδεικνύεται πρώτα το όνομα της μονάδας, ακολουθούμενη από μια τελεία το όνομα του αντικειμένου: M1. A M1.B M1.C M2.B.

Προφανώς, είναι πολύ εύκολο να χωρίσετε ένα μεγάλο πρόγραμμα σε δύο μέρη (PU), δηλ. κύριο πρόγραμμα + ενότητες.

Τοποθέτηση κάθε PU στο δικό του τμήμα μνήμης και στο δικό του αρχείο δίσκου.

Όλες οι δηλώσεις τύπων, καθώς και εκείνες οι μεταβλητές που θα πρέπει να είναι διαθέσιμες σε μεμονωμένες PU (το κύριο πρόγραμμα και οι μελλοντικές ενότητες) θα πρέπει να τοποθετούνται σε ξεχωριστή ενότητα με ένα κενό εκτελέσιμο μέρος. Ωστόσο, δεν πρέπει να δώσετε προσοχή στο γεγονός ότι κάποια PE (για παράδειγμα, μια ενότητα) δεν χρησιμοποιεί όλες αυτές τις δηλώσεις. Το τμήμα εκκίνησης μιας τέτοιας ενότητας μπορεί να περιλαμβάνει δηλώσεις που συσχετίζουν μεταβλητές αρχείου με μη τυπικά αρχεία κειμένου (ASSIGN) και εκκινούν αυτά τα αρχεία, π.χ. υποδεικνύοντας τις κλήσεις μεταφοράς δεδομένων για αυτά (ΕΠΑΝΑΦΟΡΑ και ΕΓΓΡΑΦΗ).

Η πρώτη ομάδα άλλων υπορουτίνων, για παράδειγμα, αρκετές συμπαγείς λειτουργίες θα πρέπει να τοποθετηθούν στην ενότητα 3 (με τη σειρά τους), άλλες, για παράδειγμα, διαδικασίες γενικού σκοπού - στην ενότητα 4 κ.λπ.

Κατά τη διανομή υπορουτίνων σε ενότητες σε ένα σύνθετο έργο, θα πρέπει να δίνεται ιδιαίτερη προσοχή στη σειρά και τον τόπο γραφής τους.

Το περιβάλλον TP περιέχει εργαλεία που ελέγχουν διάφορους τρόπους μεταγλώττισης μονάδων.

Μεταγλώττιση Alt+F9 RUN Cntr+F9

Μνήμη προορισμού

Αυτές οι λειτουργίες διαφέρουν μόνο ως προς τη μέθοδο επικοινωνίας και το κύριο πρόγραμμα.

Λειτουργία μεταγλώττισης

Μεταγλωττίζει το κύριο πρόγραμμα ή λειτουργική μονάδα που βρίσκεται αυτήν τη στιγμή στο ενεργό παράθυρο του προγράμματος επεξεργασίας. Εάν αυτή η PU περιέχει πρόσβαση σε μη τυπικές μονάδες χρήστη, τότε αυτή η λειτουργία απαιτεί την παρουσία αρχείων δίσκου με το ίδιο όνομα με την επέκταση ___.tpu για κάθε τέτοια μονάδα προσθήκης.



Εάν ο προορισμός είναι αποθηκευμένος στη μνήμη, τότε αυτά τα αρχεία παραμένουν μόνο στη μνήμη και δεν δημιουργείται αρχείο δίσκου.

Ωστόσο, είναι πολύ πιο εύκολο να δημιουργήσετε αρχεία tpu μαζί με τον μεταγλωττιστή ολόκληρου του προγράμματος χρησιμοποιώντας άλλες λειτουργίες που δεν απαιτούν ρύθμιση του δίσκου για την επιλογή προορισμού.

Δημιουργία λειτουργίας

Κατά τη μεταγλώττιση σε αυτήν τη λειτουργία, ελέγχονται πρώτα τα ακόλουθα (πριν από τη μεταγλώττιση του κύριου προγράμματος) για κάθε ενότητα:

1) Η ύπαρξη αρχείου tpu δίσκου. αν δεν υπάρχει, τότε δημιουργείται αυτόματα με τη μεταγλώττιση του πηγαίο κώδικα του module, δηλ. το αρχείο του pas

2) Αντιστοιχία του αρχείου tpu που βρέθηκε στο κείμενο προέλευσης της ενότητας, όπου θα μπορούσαν να είχαν γίνει αλλαγές. διαφορετικά το αρχείο tpu δημιουργείται ξανά αυτόματα

3) Αμετάβλητο της ενότητας διεπαφής της μονάδας: εάν έχει αλλάξει, τότε όλες εκείνες οι ενότητες (τα αρχεία προέλευσης τους) στα οποία αυτή η ενότητα καθορίζεται στην ρήτρα USES, επίσης μεταγλωττίζονται εκ νέου.

Εάν δεν υπήρξε αλλαγή στους πηγαίους κώδικες των μονάδων, τότε ο μεταγλωττιστής αλληλεπιδρά με αυτά τα αρχεία tpu και χρησιμοποιεί το χρόνο μεταγλώττισης.

Λειτουργία κατασκευής

Σε αντίθεση με τη λειτουργία Δημιουργία, απαιτεί απαραίτητα την παρουσία αρχείων προέλευσης. μεταγλωττίζει (εκ νέου μεταγλωττίζει) κάθε ενότητα και έτσι διασφαλίζει ότι λαμβάνονται υπόψη όλες οι αλλαγές στα κείμενα των αρχείων pas. Αυτό αυξάνει τον χρόνο μεταγλώττισης του προγράμματος στο σύνολό του.

Σε αντίθεση με τη λειτουργία μεταγλώττισης, οι λειτουργίες Δημιουργία και Δημιουργία σάς επιτρέπουν να ξεκινήσετε τη μεταγλώττιση ενός προγράμματος με αρθρωτή δομή από οποιοδήποτε δεδομένο αρχείο pas (ονομάζεται πρωτεύον αρχείο), ανεξάρτητα από το ποιο αρχείο (ή μέρος του προγράμματος) βρίσκεται στο ενεργό παράθυρο επεξεργασίας. Για να το κάνετε αυτό, στο στοιχείο μεταγλώττισης, επιλέξτε την επιλογή Κύριο αρχείο, πατήστε Enter και σημειώστε το όνομα του πρωτεύοντος αρχείου pas και, στη συνέχεια, θα ξεκινήσει η μεταγλώττιση από αυτό το αρχείο.

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

Σημείωση: Στο μέλλον, σκοπεύω να χρησιμοποιήσω το σύστημα T2 για τη μεταγλώττιση του πυρήνα και των λειτουργικών μονάδων για το Puppy. Το T2 είναι επί του παρόντος εγκατεστημένο για τη μεταγλώττιση του πυρήνα και πολλών πρόσθετων λειτουργικών μονάδων, αλλά όχι την έκδοση που χρησιμοποιείται αυτήν τη στιγμή στο Puppy. Σκοπεύω να κάνω συγχρονισμό σε μελλοντικές εκδόσεις του Puppy, έτσι ώστε ο πυρήνας που έχει μεταγλωττιστεί στο T2 να χρησιμοποιείται στο Puppy. Δείτε http://www.puppyos.net/pfs/ για περισσότερες πληροφορίες σχετικά με το Puppy και το T2.

Το Puppy έχει έναν πολύ απλό τρόπο να χρησιμοποιεί μεταγλωττιστές C/C++ προσθέτοντας ένα μόνο αρχείο, devx_xxx.sfs, όπου xxx είναι ο αριθμός έκδοσης. Για παράδειγμα, το Puppy 2.12 θα είχε ένα αρχείο ανάπτυξης συμμόρφωσης με το όνομα devx_212.sfs. Όταν εκτελείται σε λειτουργία LiveCD, τοποθετήστε το αρχείο devx_xxx.sfs στην ίδια θέση με το αρχείο προσωπικών ρυθμίσεων pup_save.3fs, το οποίο συνήθως βρίσκεται στον κατάλογο /mnt/home/. Αυτό ισχύει και για άλλες λειτουργίες εγκατάστασης που έχουν αρχείο pup_save.3fs. Εάν το Puppy είναι εγκατεστημένο σε σκληρό δίσκο με πλήρη εγκατάσταση "Επιλογή 2", τότε δεν υπάρχει προσωπικό αρχείο, κοιτάξτε στις ιστοσελίδες του Puppy που πρόκειται να μεταγλωττιστούν με διαφορετικές επιλογές διαμόρφωσης, ώστε οι μονάδες να μην είναι συμβατές. Αυτές οι εκδόσεις απαιτούν μόνο ένα patch για squashf. Το Puppy 2.12 έχει πυρήνα 2.6.18.1 και έχει τρεις επιδιορθώσεις. squashfs, προεπιλεγμένο loglevel κονσόλας και επιδιόρθωση τερματισμού λειτουργίας.

Αυτές οι εντολές για να επιδιορθώσετε τον πυρήνα δίνονται αποκλειστικά για την αυτοεκπαίδευσή σας, καθώς υπάρχει ήδη ένας διορθωμένος πυρήνας...

Το πρώτο πράγμα που πρέπει να κάνετε είναι να κατεβάσετε τον ίδιο τον πυρήνα. Βρίσκεται για να βρείτε έναν σύνδεσμο προς μια κατάλληλη τοποθεσία λήψης. Αυτή θα πρέπει να είναι μια "αρχαία" πηγή διαθέσιμη στο kernel.org ή στα mirrors του.

Συνδεθείτε στο Internet, κατεβάστε τον πυρήνα στο φάκελο /usr/src. Στη συνέχεια, αποσυσκευάστε το:

cd / usr/ src tar -jxf linux-2.6.16.7.tar.bz2 tar -zxf linux-2.6.16.7.tar.gz

Θα πρέπει να δείτε αυτόν τον φάκελο: /usr/src/linux-2.6.16.7. Στη συνέχεια, πρέπει να βεβαιωθείτε ότι αυτός ο σύνδεσμος οδηγεί στον πυρήνα:

ln -sf linux-2.6.16.7 linux ln -sf linux-2.6.16.7 linux-2.6.9

Πρέπει να εφαρμόσετε τις ακόλουθες διορθώσεις, ώστε να έχετε ακριβώς την ίδια πηγή που χρησιμοποιείται κατά τη μεταγλώττιση του πυρήνα για το Puppy. Διαφορετικά, θα λάβετε μηνύματα σφάλματος "μη επιλυμένα σύμβολα" όταν κάνετε μεταγλώττιση του προγράμματος οδήγησης και, στη συνέχεια, προσπαθήστε να το χρησιμοποιήσετε με τον πυρήνα του Puppy. Εφαρμογή της επιδιόρθωσης squashfs

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

Κάντε λήψη της ενημέρωσης κώδικα, squashfs2.1-patch-k2.6.9.gz, στο φάκελο /usr/src. Σημειώστε ότι αυτή η ενημέρωση κώδικα έγινε για την έκδοση 2.6.9 του πυρήνα, αλλά συνεχίζει να λειτουργεί σε εκδόσεις 2.6.11.x όσο υπάρχει αναφορά στο linux-2.6.9. Στη συνέχεια, εφαρμόστε την επιδιόρθωση:

Cd/ usr/ src gunzip squashfs2.1-patch-k2.6.9.gz cd/ usr/ src/ linux-2.6.11.11

Σημείωση, το p1 έχει τον αριθμό 1, όχι το σύμβολο l... (Υπέροχο αστείο - περίπου. μετάφραση)

patch --dry-run -p1< ../ squashfs2.1-patch patch -p1 < ../ squashfs2.1-patch

Ετοιμος! Ο πυρήνας είναι έτοιμος για μεταγλώττιση!

Μεταγλώττιση του πυρήνα

Πρέπει να αποκτήσετε ένα αρχείο ρυθμίσεων για τον πυρήνα. Ένα αντίγραφό του βρίσκεται στο φάκελο /lib/modules.

Στη συνέχεια, ακολουθήστε αυτά τα βήματα:

Cd/ usr/ src/ linux-2.6.18.1

Εάν υπάρχει αρχείο .config, αντιγράψτε το κάπου προσωρινά ή μετονομάστε το.

κάνε καθαρό make mrproper

Αντιγράψτε το αρχείο .config για το Puppy στο /usr/src/linux-2.6.18.1... Θα έχει διαφορετικά ονόματα στο /lib/modules, επομένως μετονομάστε σε .config... Τα παρακάτω βήματα διαβάστε το αρχείο .config και δημιουργήστε ένα καινούργιο.

κάντε το menuconfig

...κάντε όποιες αλλαγές θέλετε και αποθηκεύστε τις. Τώρα θα έχετε ένα νέο αρχείο .config και θα πρέπει να το αντιγράψετε κάπου για φύλαξη. Δείτε τη σημείωση παρακάτω

φτιάξτε το bzImage

Τώρα έχετε μεταγλωττίσει τον πυρήνα.

Τέλεια, θα βρείτε τον πυρήνα του Linux στο /usr/src/linux-2.6.18.1/arch/i386/boot/bzImage

Μεταγλώττιση ενοτήτων

Τώρα μεταβείτε στο /lib/modules και αν υπάρχει ήδη ένας φάκελος με το όνομα 2.6.18.1, μετονομάστε τον φάκελο 2.6.18.1 σε 2.6.18.1-old.

Τώρα εγκαταστήστε νέες ενότητες:

Cd/ usr/ src/ linux-2.6.18.1 make modules make modules_install

...μετά από αυτό θα πρέπει να βρείτε τις νέες μονάδες εγκατεστημένες στο φάκελο /lib/modules/2.6.18.1.

Σημειώστε ότι το τελευταίο βήμα παραπάνω εκτελεί το πρόγραμμα "depmod" και αυτό μπορεί να δώσει μηνύματα σφάλματος σχετικά με τα σύμβολα που λείπουν για ορισμένες από τις μονάδες. Μην ανησυχείτε για αυτό - ένας από τους προγραμματιστές τα χάλασε και αυτό σημαίνει ότι δεν μπορούμε να χρησιμοποιήσουμε αυτήν τη μονάδα.

Τρόπος χρήσης του νέου πυρήνα και λειτουργικών μονάδων

Είναι καλύτερα αν έχετε εγκαταστήσει το Puppy Unleashed. Στη συνέχεια, το tarball επεκτείνεται και υπάρχουν 2 κατάλογοι: "boot" και "kernels".

Το "Boot" περιέχει τη δομή του αρχείου και το σενάριο για τη δημιουργία του αρχικού εικονικού δίσκου. Θα πρέπει να βάλετε κάποιες μονάδες πυρήνα εκεί.

Ο κατάλογος "πυρήνες" έχει έναν κατάλογο πυρήνες/2.6.18.1/ και θα χρειαστεί να αντικαταστήσετε τις ενότητες με τις ενημερωμένες σας. Δεν χρειάζεται να το αντικαταστήσετε εάν μεταγλωττίσετε ξανά την ίδια έκδοση πυρήνα (2.6.18.1).

Σημειώστε ότι στους πυρήνες/2.6.18.1 υπάρχει ένα αρχείο που ονομάζεται "System.map". Θα πρέπει να το μετονομάσετε και να το αντικαταστήσετε με το νέο από το /usr/src/linux-2.6.18.1. Περιηγηθείτε στον φάκελο kernels/2.6.18.1/ και θα πρέπει να ξέρετε τι πρέπει να ενημερωθεί.

Εάν έχετε μεταγλωττίσει τον πυρήνα σε μια πλήρη εγκατάσταση Puppy, μπορείτε να κάνετε επανεκκίνηση χρησιμοποιώντας τον νέο πυρήνα. make modules_install είναι το παραπάνω βήμα για την εγκατάσταση νέων λειτουργικών μονάδων στο /lib/modules/2.6.18.1 , αλλά πρέπει επίσης να εγκαταστήσετε έναν νέο πυρήνα. Εκκινώ με το Grub και απλώς αντιγράφω τον νέο πυρήνα στον κατάλογο /boot (και μετονομάζω το αρχείο από bzImage σε vmlinuz).

Σημείωση σχετικά με τη διαμόρφωση μενού. Το χρησιμοποιώ εδώ και πολύ καιρό, οπότε θεωρήστε ορισμένα πράγματα δεδομένα, ωστόσο ένας αρχάριος μπορεί να μπερδευτεί εάν θέλει να εγκαταλείψει το πρόγραμμα. Υπάρχει ένα μενού στο μενού ανώτατου επιπέδου για αποθήκευση της διαμόρφωσης - αγνοήστε το. Απλώς πατήστε το πλήκτρο TAB (ή το πλήκτρο δεξιού βέλους) για να επισημάνετε το κουμπί "Έξοδος" και πατήστε το πλήκτρο ENTER. Στη συνέχεια θα ερωτηθείτε εάν θέλετε να αποθηκεύσετε τη νέα διαμόρφωση και η απάντησή σας θα πρέπει να είναι Ναι.


Μπλουζα