Linux Device Drivers, 3. Auflage (LDD3)

Zusammenfassung

Standardwerk von Jonathan Corbet, Alessandro Rubini und Greg Kroah-Hartman (O’Reilly, 2005) zur Entwicklung von Gerätetreibern für Linux (Kernel 2.6). Die Auswertung stützt sich auf die Kapitel ch00–ch09 (Vorwort bis Hardware-Kommunikation) sowie ch11–ch17 (Datentypen bis Netzwerk-Treiber); Kern-APIs und Code-Muster sind hier verdichtet. Grundkonzepte sind stabil, konkrete APIs teils veraltet.

Inhalt

Grundlagen (Kap. 1–2)

  • Leitprinzip: Treiber liefern mechanism not policy – Hardware-Fähigkeiten ohne Nutzungsvorgaben bereitstellen.
  • Drei Geräteklassen: Char-Geräte (Bytestrom, /dev-Node), Block-Geräte (Dateisystem-fähig, eigene Kernel-Schnittstelle), Netzwerk-Interfaces (Pakete, kein /dev-Eintrag).
  • Minimalmodul:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void) { printk(KERN_ALERT "Hello, world\n"); return 0; }
static void hello_exit(void) { printk(KERN_ALERT "Goodbye\n"); }
module_init(hello_init);
module_exit(hello_exit);
  • Modul ≠ Applikation: event-getrieben, nur Kernel-Symbole nutzbar (keine libc), sehr kleiner Stack (ab 4096 Byte – große Variablen dynamisch allokieren), keine Fließkomma-Arithmetik; current liefert struct task_struct des aufrufenden Prozesses (current->comm, current->pid).
  • Build: obj-m := hello.o plus make -C /lib/modules/$(uname -r)/build M=$(PWD) modules. Werkzeuge: insmod/rmmod (direkt), modprobe (löst Abhängigkeiten auf), lsmod (/proc/modules). Module sind an die exakte Kernel-Version gebunden (vermagic.o); Versionsprüfungen mit KERNEL_VERSION(a,b,c) vs. LINUX_VERSION_CODE.

Char-Treiber (Kap. 3)

  • scull = Speicherbereich als Gerät: scull0–3 (global/persistent), scullpipe (FIFO, blocking I/O ohne Interrupts), scullsingle/scullpriv/sculluid/scullwuid (Zugriffsbeschränkungen).
  • Device Numbers: dev_t (12 Bit Major, 20 Bit Minor); Makros MAJOR(dev), MINOR(dev), MKDEV(major, minor).
  • Nummern allozieren: int alloc_chrdev_region(dev_t *dev, firstminor, count, name) (dynamisch, empfohlen) bzw. register_chrdev_region(first, count, name); Freigabe mit unregister_chrdev_region() im Cleanup.
  • struct file_operations verbindet Syscalls mit dem Treiber: .owner = THIS_MODULE, read(struct file *, char __user *, size_t, loff_t *), write, llseek, poll (Backend von select/epoll/poll), ioctl, mmap, open, release; NULL-Felder haben definierte Defaults (z. B. read → -EINVAL). __user markiert Zeiger, die nicht direkt dereferenziert werden dürfen.

Debugging und Nebenläufigkeit (Kap. 4–5)

  • printk-Loglevels: KERN_EMERGKERN_DEBUG (ohne Komma vor dem Formatstring); Oops-Meldungen auswerten; /proc-Schnittstellen für Statusinformationen.
  • Semaphoren (<asm/semaphore.h>, schlafend): DECLARE_MUTEX(name), init_MUTEX(sem), P/V via down() / down_interruptible() (rückgibt -EINTR) / down_trylock() und up().
  • Spinlocks (<linux/spinlock.h>, nicht schlafend, für Interrupt-Kontext): spinlock_t lock = SPIN_LOCK_UNLOCKED; spin_lock_init(&lock); spin_lock()/spin_unlock(); Varianten mit IRQ-Sperre nötig, wenn der Lock auch von Handlern genutzt wird.

Blocking I/O, Zeit und Speicher (Kap. 6–8)

  • User-Datenaustausch: copy_to_user()/copy_from_user(), Einzelwerte via get_user()/put_user().
  • Warten: wait_queue_head_t + wait_event_interruptible(queue, condition); Aufwecken von der anderen Seite (z. B. ISR) mit wake_up_interruptible().
  • Zeit: jiffies (read-only, Wrap-around-sichere Vergleiche time_after(a,b)); Umrechnung j + HZ*n/1000; Verzögerungen ndelay/udelay/mdelay (busy-wait) vs. msleep (schläft); Kernel-Timer: struct timer_list, add_timer(), del_timer(); Wanduhrzeit do_gettimeofday(struct timeval *).
  • Speicher: void *kmalloc(size_t size, int flags) / kfree(); Flags: GFP_KERNEL (darf schlafen, Prozesskontext), GFP_ATOMIC (Interrupt-Kontext, kann fehlschlagen), dazu GFP_USER, __GFP_DMA, __GFP_HIGHMEM; vmalloc() für große, virtuell zusammenhängende Bereiche; Slab-Caches (kmem_cache_t) für gleichförmige Objekte.

Hardware-Zugriff (Kap. 9)

  • I/O-Ports (x86): Reservierung struct resource *request_region(unsigned long first, unsigned long n, char *name) / release_region(); Zugriff unsigned inb(unsigned port) / outb(byte, port) (je nach Breite inw/outw, inl/outl; _p-Varianten pausieren; String-Varianten wie insw).
  • I/O-Memory (portabel, auch ARM/Zynq): Mapping void *ioremap(unsigned long phys_addr, unsigned long size) / iounmap(addr); Zugriff ausschließlich über readb/readw/readl und writeb/writew/writel – niemals den Zeiger direkt dereferenzieren; Reihenfolgeabsicherungen über Memory Barriers (wmb, rmb).

Kernel-Datentypen und Portabilität (Kap. 11)

  • Drei Typklassen: Standard-C (int), explizit dimensioniert (u8/u16/u32/u64, <asm/types.h>) und kernelspezifisch (pid_t, sector_t). long und Pointer sind architekturabhängig (64 Bit auf LP64); Speicheradressen daher als unsigned long.
  • Verknüpfte Listen: eingebettetes struct list_head; list_add()/list_add_tail() (FIFO-Warteschlangen), Traversal mit list_for_each(), Containerobjekt via container_of(ptr, type, member).

PCI-Treiber (Kap. 12)

  • Identifikation: Tabelle struct pci_device_id mit Makros PCI_DEVICE(vendor, device) / PCI_DEVICE_CLASS(...), Export an User Space via MODULE_DEVICE_TABLE(pci, ids) – Grundlage für Hotplug.
  • Rahmen: struct pci_driver (.id_table, .probe, .remove, suspend/resume) mit pci_register_driver() / pci_unregister_driver(). In probe: pci_enable_device(), für Busmaster-DMA pci_set_master().
  • BARs nie hart kodieren: pci_resource_start/end/flags(dev, bar) liefern die Regionen; danach wie üblich request_region bzw. ioremap. Suche ohne Treiberbindung: pci_get_device().

USB-Treiber (Kap. 13)

  • Hierarchie: Device → Konfiguration → Interface → Endpoint; Endpoints sind unidirektionale Pipes, vier Typen: control (Endpoint 0, immer vorhanden), interrupt (festes Intervall), bulk (große Mengen), isochronous (bandbreitengarantiert, verlusttolerant).
  • Treiber: struct usb_driver (.probe, .disconnect, .id_table) mit usb_register() / usb_deregister().
  • Transfer primär über URBs: usb_alloc_urb(), usb_submit_urb() (asynchron, Completion-Callback auch in Interrupt-Kontext); synchrone Kurzformen usb_control_msg() / usb_bulk_msg().

Device Model und sysfs (Kap. 14)

  • Kern ist struct kobject: Referenzverwaltung, Hierarchie, sysfs-Darstellung und uevents (Hotplug an udev). kobjects werden in Domänenobjekte eingebettet (z. B. struct cdev.kobj), Rückgewinnung per container_of().
  • Gruppierung über kset/subsystem; das Modell beschreibt Busse (bus_type) und die device↔driver-Zuordnung über den .probe-Mechanismus.
  • Kommunikation mit User Space über Attribute (sysfs-Dateien statt Debug-ioctls).

mmap und DMA (Kap. 15)

  • mmap: remap_pfn_range(vma, vm_start, pfn, size, prot) bildet physischen RAM/I/O-Speicher en bloc ab; alternativ seitenweise über die nopage-Methode von vm_operations_struct.
  • DMA-Busadressen haben den opaken Typ dma_addr_t; Adressierbarkeit prüfen mit dma_set_mask(dev, mask).
  • Kohärent: dma_alloc_coherent(dev, size, &handle, flag) ohne Cache-Kohärenzaufwand. Streaming: dma_map_single() mit Richtung DMA_TO_DEVICE/DMA_FROM_DEVICE; Ownership-Wechsel nur via dma_sync_single_for_cpu()/_for_device(). Mehrere Puffer als Scatterliste mappen.

Block-Treiber (Kap. 16)

  • Major optional via register_blkdev(); eigentliche Registrierung über struct gendisk plus block_device_operations (open/release/ioctl/media_changed/revalidate_disk).
  • Requests landen in der request_queue (blk_init_queue(fn, lock)); Abarbeitung im Loop mit elv_next_request(q), Abschluss je Request mit end_request(req, ok).
  • Vor add_disk(gd) vollständig initialisieren – der Kernel liest sofort Partitionstabellen: set_capacity() (512-Byte-Sektoren), Sektorgröße via blk_queue_hardsect_size().

Netzwerk-Treiber (Kap. 17)

  • Interface = struct net_device: alloc_netdev(sizeof_priv, "sn%d", setup) bzw. alloc_etherdev() (+ ether_setup-Defaults), aktivieren mit register_netdev().
  • Sendepfad: hard_start_xmit(struct sk_buff *, dev); Paketdaten in skb->data (skb->len Bytes). Flusssteuerung netif_stop_queue()/netif_wake_queue().
  • Empfang: skb aufbauen und mit netif_rx() dem Stack zuführen; unter Last polled Betrieb über NAPI (Interrupts deaktiviert).

Restliche Kapitel (Übersicht)

  • Interrupt-Handling (Kap. 10): Top/Bottom Half. TTY-Treiber (Kap. 18).

Quellen

  • raw/ch01.pdfraw/ch09.pdf, raw/ch11.pdfraw/ch17.pdf – Kapitel 1–9 und 11–17 als Einzeldateien (ausgewertet)
  • raw/ch00.pdf (Vorwort), raw/ch10.pdf, raw/ch18.pdf, raw/TITLE.pdf, raw/COPYRIGHT.pdf, raw/AUTHOR.COLO.pdf, raw/ldr3TOC.fm.pdf, raw/ldr3IX.fm.pdf – übrige Buchteile

Verwandte Artikel