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 2004
Geä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
Weiter
Vorwort