Linux-Treiber entwickeln

Gerätetreiber für Kernel 2.6 systematisch eingeführt

Jürgen Quade

Eva-Katharina Kunst

Versionsgeschichte
Version $Revision: 1.223 $Di Dez 21 16:28:50 CET 2004Geändert durch: $Author: quade $
Rechtlicher Hinweis
Inhaltsverzeichnis
Vorwort
1. Einleitung
2. Theorie ist notwendig
2.1. Betriebssystemarchitektur
2.2. Abarbeitungskontext und Unterbrechungsmodell
2.3. Quellensuche
3. Treiberentwicklung in der Praxis
3.1. Auf der Kommandoebene entwickeln
3.2. Techniken der Treiberprogrammierung
3.3. Nicht vergessen: Auswahl einer geeigneten Lizenz
4. Treiber aus Sicht der Applikation
4.1. Die Programmierschnittstelle der Applikation
4.2. Zugriffsmodi
5. Einfache Treiber
5.1. Bevor es losgeht ...
5.2. Den Kernel erweitern
5.3. Die Treiber-Einsprungspunkte
5.4. Daten zwischen Kernel- und User-Space transferieren
5.5. Hardware anbinden
5.6. Zugriffsmodi im Treiber realisieren
5.7. Treiberinstanzen
6. Fortgeschrittene Treiberentwicklung
6.1. Zunächst die Übersicht
6.2. Interruptbetrieb
6.3. Softirqs
6.4. Kernel-Threads
6.5. Kritische Abschnitte sichern
6.6. Vom Umgang mit Zeiten
7. Systemaspekte
7.1. Proc-Filesystem
7.2. Das neue Gerätemodell
7.3. Device-Filesystem
7.4. Treiber parametrieren
7.5. Systemintegration
7.6. Kernel Build System
7.7. Intermodul-Kommunikation
8. Sonstige Treibersubsysteme
8.1. Blockorientierte Gerätetreiber
8.2. USB-Subsystem
8.3. Netzwerk-Subsystem
9. Über das Schreiben eines guten, performanten Treibers
9.1. Konzeption
9.2. Realisierung
9.3. Echtzeitaspekte
A. Kernel generieren und installieren
B. Portierungs-Guide
C. Makros und Funktionen des Kernels kurz gefasst
Literatur- und Quellennachweis
Stichwortverzeichnis
Tabellenverzeichnis
3-1. Die wichtigsten Kommandos in der Kurzübersicht
3-2. Priorisierung von Kernelnachrichten
5-1. Checkliste zum Programmierstart [Editors Note: möglicherweise auch als Kasten zu setzen.]
5-2. Die wichtigsten Flags der driver_poll-Funktion
5-3. Schlüsselworte der impliziten Ressourcenverwaltung
5-4. Die 7 Elemente des PCI-Treibers [EDITORS NOTE: als Kasten ausprägen]
6-1. Atomare Operationen in der Übersicht
6-2. Der Schutz kritischer Abschnitte aufgrund der beteiligten Instanzen
7-1. Definitionen für Zugriffsrechte
7-2. Unterstützte Datentypen der Treiberparameter
8-1. USB-Sendefunktionen für Standardkommandos
Abbildungsverzeichnis
2-1. Betriebssystem-Architektur
2-2. Verarbeitung mehrerer Applikationen auf einer CPU
2-3. Rechenprozesszustände
2-4. Rechenprozesszustände in Linux
2-5. Prozess-Kontrollblock
2-6. Treiberstruktur eines geschichteten Treibers
2-7. Treiberfunktionsübersicht
2-8. Unterbrechungsmodell im Linux-Kernel
2-9. Verzeichnisstruktur des Kernel-Quellcodes
3-1. Komponenten der Treiberentwicklung
3-2. Datenflüsse in der Shell (stark vereinfacht)
3-3. Printk-Debugging
3-4. Verkettete Liste
3-5. Lebenszyklus eines Kernel-Objektes
3-6. Schutz vor vorzeitiger Freigabe
4-1. Blockierender Zugriff
4-2. Nicht blockierender Zugriff
5-1. Abbildung der alten Major- und Minor-Nummern auf die neuen Gerätenummern
5-2. Einbindung des Gerätetreibers in das System
5-3. Reservierung von Daten nach einem select-Aufruf
5-4. Funktionen zum Datentransfer zwischen Applikation und Hardware
5-5. Wirkung der Schlüsselworte init und exit
5-6. Ausgerichtete Daten
5-7. »Gepackte« Datenstruktur
5-8. Das Steuerregister des PIT
5-9. Struktogramm Automatische Hardware-Erkennung
5-10. Zusammenspiel mit dem PCI-Subsystem
5-11. Die Initialisierung der Struktur pci_device_id
5-12. PCI-Konfigurationsspeicher
5-13. Prinzipielle Programmstruktur der driver_read-Funktion
5-14. Die Möglichkeit einer Race Condition beim Schlafenlegen eines Rechenprozesses
5-15. Erweitern der Treiberinstanz um instanzenspezifische Parameter
6-1. Asynchrone Verarbeitung im Kernel
6-2. Datenfluss beim Aufruf von fread
6-3. Vordefinierte Softirqs
6-4. Tasklets als Teil der Softirq-Verarbeitung
6-5. Typischer Aufbau eines Kernel-Threads
6-6. Datenstrukturen der Workqueues
6-7. Race Condition beim Zugriff auf eine gemeinsam genutzte Variable
6-8. Kritischer Abschnitt beim Zugriff auf eine Liste
6-9. Einsatz eines Semaphors
6-10. Lesender Zugriff bei Sequencelocks
6-11. Beispiel einer Race Condition
6-12. Race Condition durch Reordering
7-1. Interne Informationen lesbar aufbereiten
7-2. Einsparung von Code durch Verwendung von »#define«
7-3. Aufruf von proc_read aus der Kernelfunktion proc_file_read heraus
7-4. Berechnung des neuen Offsets
7-5. Komponenten eines Sequence-Files
7-6. Repräsentierung des Gerätemodells im Sys-Filesystem
7-7. Unterstützung des Gerätemodells innerhalb eines Treibers
7-8. Erstellung der Gerätedatei durch den Anwender oder durch den Treiber
7-9. Automatisches Laden von Treibern
7-10. Hotplug-Mechanismus
8-1. Blockorientierter Datenaustausch zwischen Haupt- und Hintergrundspeicher
8-2. Blockgerätetreiber können sich an zwei Stellen einklinken
8-3. Die Request-Datenstruktur
8-4. Struktogramm der Transferfunktion eines Blockgerätetreibers
8-5. Die Block-IO-Datenstruktur
8-6. Physikalische Struktur des USB
8-7. Softwaremodell des USB-Subsystems
8-8. Datentransfer zwischen USB-Controller und Geräten
8-9. Datenstrukturen und Funktionen eines USB-Treibers ohne die Funktionen zum Datenaustausch
8-10. Asynchrone USB-Kommunikation
8-11. Einbettung von Netzwerk-Subsystem und Netzwerktreiber im Kernel
8-12. Struktur eines Netzwerktreibers
8-13. Datenmanagement im Socket-Buffer
9-1. Qualitätsaspekte beim Design
A-1. In acht Schritten zum Kernel
Beispiele
2-1. Datei und Gerätedatei
3-1. Einfaches Makefile
3-2. Makefile mit Versionsverwaltung
3-3. Auskommentieren von Codeblöcken
3-4. Verkettete Liste mit und ohne objektbasierter Datenhaltung
4-1. Lesezugriff innerhalb einer Applikation
4-2. Schreibzugriff innerhalb einer Applikation
4-3. Verwendung von ioctl in einer Applikation
4-4. Auswahl des Zugriffsmodus in einer Applikation
4-5. Beispielcode mit select
5-1. Der erste Kernelcode
5-2. Makefile zum ersten Kernelcode
5-3. Codegerüst für einfache Treiber
5-4. Anmelden des Treibers beim IO-Management
5-5. An- und Abmelden eines Treibers, der mehr als 256 Geräte unterstützt
5-6. Die Struktur struct file_operations
5-7. Dedizierter Zugriffsschutz in driver_open
5-8. Hello-World-Treiber
5-9. Makefile zum Hello-World-Treiber
5-10. Implementierung des logischen Gerätes /dev/null
5-11. Implementierung einer driver_ioctl-Funkion
5-12. Komplettbeispiel IO-Control
5-13. Die Funktion driver_poll
5-14. Aufruf der Funktion select aus der Applikation
5-15. Realisierung einer Poll-Funktion im Treiber
5-16. Datentransfer zwischen Kernel- und User-Space
5-17. Datentransfer mit Hilfe der Funktion put_user
5-18. Optimierter Datentransfer
5-19. Reservierung von Ressourcen
5-20. Eine einfache Interrupt-Service-Routine
5-21. Zugriff auf Portadressen am Beispiel PC-Speaker
5-22. Hardware-Erkennung
5-23. PCI-Treiber-Initialisierung
5-24. PCI-Geräteinitialisierung
5-25. Template für einen eigenen PCI-Treiber
5-26. Schlafenlegen und Aufwecken eines Rechenprozesses
5-27. Die Verwaltung instanzenspezifischer Parameter
6-1. Einfache Verwendung von Interrupts
6-2. Statische Initialisierung einer Tasklet-Struktur
6-3. Dynamische Initialisierung einer Tasklet-Struktur
6-4. Einfaches Tasklet
6-5. Initialisierung der timer_list
6-6. Stoppen periodischer Funktionen
6-7. Verwendung eines Timers
6-8. Sicheres Einhängen eines Timer-Objekts
6-9. Ein einfacher Kernel-Thread
6-10. Einhängen einer Funktion in die Workqueue
6-11. Codebeispiel für das Anlegen einer Workqueue
6-12. Codebeispiel zur Verwendung der Event-Workqueue
6-13. Verwendung von Semaphoren in Kernel-Threads
6-14. Verwendung eines Lese-/Schreib-Semaphors
6-15. Kritische Abschnitte bei der Listenverarbeitung
6-16. Zeitvergleiche per Makro
7-1. Proc-Dateien werden durch Aufruf von create_proc_entry angelegt
7-2. Initialisierung der Struktur proc_dir_entry
7-3. Programmierung einer Proc-Datei
7-4. Ausgabe von umfangreichen Daten in einer Proc-Datei
7-5. Schreibender Zugriff auf eine Proc-Datei
7-6. Verwendung des Sequence-File-Interface
7-7. Single-File
7-8. Anmeldung eines Treibers beim Gerätemodell
7-9. Eigene Klassen im Gerätemodell
7-10. Unterstützung des Gerätemodells im Treiber
7-11. Anmeldung beim Device-Filesystem
7-12. Löschen von Symlinks im Device-Filesystem
7-13. Device-Filesystem für ein eingebettetes System
7-14. Selbstdefinierte Datentypen bei der Parameterübergabe
7-15. Eintrag für ein neues Modul in der Kernelkonfiguration »kconfig«
7-16. Modulübergreifende Symbole (Teil 1, Definition)
7-17. Modulübergreifende Symbole (Teil 2, Deklaration)
7-18. Importieren eines Symbols
7-19. Export einer Funktion und einer Variablen (icm.c)
7-20. Import einer Funktion und einer Variablen (stacked.c)
8-1. Ein einfacher Ramdisk-Treiber
8-2. Verwendung von BIO-Blöcken
8-3. Blockgerätetreiber ohne Request-Queue
8-4. Codefragment USB-Probefunktion
8-5. Treibergerüst zum Test des USB-Subsystems
8-6. Setup-Funktion des Netzwerktreibers
8-7. Netzwerk Null-Device
9-1. Kodierung von Minor-Nummern