Probleme der Vergangenheit
Bevor udev entwickelt wurde und als devfs noch kein Thema war, wurden die Device Nodes entweder bei der Installation angelegt oder mussten manuell mit mknod hinzugefügt werden. Es gab keinen Automatismus, der dynamisch im laufenden Betrieb neue Device Nodes für Geräte erstellen konnte. Somit gab es unter /dev viele Dateien, die auf kein Device zeigten, das wirklich im System vorhanden war. Dies konnte dazu führen, dass sich im Verzeichnis eine unübersichtlich große Menge von Einträgen wiederfand.
Außerdem war es nicht möglich, eine persistente Benennung der Devices sicherzustellen. Ein Beispiel mit USB-Massenspeichergeräten kann dieses Problem am besten verdeutlichen:
Beim Anschluss eines USB-Sticks bekommt dieser z. B. /dev/sda als Device Node zugeordnet. Wird nun noch eine Digitalkamera per USB an das System angeschlossen, erhält diese z. B. den Devicenamen /dev/sdb. Werden die Geräte beim nächsten Mal in einer anderen Reihenfolge angeschlossen, ist auch die Benennung eine andere. Es war also nicht möglich, dass eine Festplatte, ein USB-Stick usw. einen festen Namen bekamen.
Neue Zielsetzung
Aus den genannten Problemen ergaben sich folgende Ziele:
- udev soll nur die Geräte anzeigen, die zur Zeit wirklich in dem System vorhanden sind.
- Die Vergabe der Device-Namen soll sich an bestimmte Regeln halten, die eine dauerhafte Benennung der Geräte sicherstellen.
Zusätzlich zu diesen Zielen schufen die Kernel-Entwickler Voraussetzungen für eine neue Geräteverwaltung:
- Sämtliche Regeln zur Benennung der Geräte sollen sich außerhalb des Kernels befinden, udev soll im Userspace laufen.
- Die Standards der Linux Standard Base (LSB) müssen befolgt werden.
- udev soll möglichst klein sein, damit es auch in Embedded Systems zum Einsatz kommen kann.
War devfs die Lösung?
Ein erster Versuch, die oben genannten Probleme zu lösen, soll an dieser Stelle nicht verschwiegen werden. Bevor udev entwickelt wurde, stand mit devfs ebenfalls ein eigenes Pseudodateisystem zur Verfügung.
Im Wesentlichen hat es nur das erste der beiden genannten Ziele erreicht: In diesem Dateisystem waren nur die Geräte sichtbar, die auch wirklich im System vorhanden waren. Es war jedoch keine dauerhafte Benennung der Geräte möglich. Darüber hinaus gab es mit devfs einige Probleme. So hat es nicht den Namensstandard der LSB verfolgt und lief im Kernelspace. Dies barg das Problem, dass Speicherbereiche nicht im Kernelspace ausgelagert werden konnten. Da auch die komplette Gerätedatenbank im Kernel-Speicher lag, konnte dieser nicht von Userspace-Programmen genutzt werden.
Ist udev die Lösung für alle Probleme?
Udev läuft komplett im Userspace und auch die Regeln für die Benennung der Geräte befinden sich nicht mehr im Kernel, wie noch bei devfs, sondern liegen im Dateisystem. Die Standards der LSB werden befolgt und das udev Binary ist zur Zeit nur 79 KB groß. Somit spricht nichts gegen den Einsatz in Embedded Devices. Wie schon bei devfs, zeigt udev nur die Geräte an, die auch wirklich im System vorhanden sind. Und auch die Vergabe der Gerätenamen erfolgt nun nach bestimmten Regeln. Auf die Regeln wird im Verlauf des Artikels noch näher eingegangen. Fazit: Es wurde also eine Lösung für die beschriebenen Probleme wie auch für die neuen Anforderungen der Kernel-Entwickler gefunden.
Aufbau und Funktionsweise von udev
Was passiert, wenn ein Gerät hinzugefügt wird? Der Kernel erzeugt ein Event (uevent), um den Userspace von dem Ereignis zu informieren. Bisher wurde für jedes Ereignis ein Prozess /sbin/hotplug gestartet. Dieser hat Skripte ausgeführt, die zum Beispiel Module geladen oder das Gerät für den Userspace vorbereitet haben.
Dieser Hotplug-Mechanismus hat es udev ermöglicht, nur die Devices unter /dev anzuzeigen, die auch im System vorhanden sind. Es sind jedoch einige Probleme in dem Zusammenspiel von udev und Hotplug aufgetreten, sodass udev immer mehr Funktionen von Hotplug in sich aufgenommen hat. Mittlerweile ersetzt udev das Hotplug-Paket komplett und hat alle Funktionen übernommen. Der udevd bekommt die Hotplug Events jetzt direkt über einen netlink-Socket vom Kernel.
Während des Hochfahrens werden alle initialen und statischen Device Nodes von der /lib/udev/devices in das leere /dev-Verzeichnis kopiert. Nachdem dies geschehen ist, wird der udev-Daemon gestartet, der ab sofort auf die uevents vom Kernel lauscht. Wird jetzt ein Gerät hinzugefügt oder entfernt, wird ein uevent an den udevd geschickt. Dieser Event setzt einen Prozess in Gang, der prüft, ob Regeln für dieses Device vorhanden sind. Falls erforderlich, legt er einen Device Node an, kreiert einen Symlink auf dem Node und führt gegebenenfalls ein Programm aus. Die Regeln befinden sich in den Dateien /etc/udev/rules.d/*.rules, die beim Starten des Daemons in den Speicher geladen werden.
Geräte, die vor dem Starten des Systems angeschlossen sind, werden "Coldplugged Devices" genannt. Da udev im Userspace läuft und somit nach dem Hochfahren des Kernels gestartet wird, wird ein spezieller Mechanismus benötigt, um diese Geräte zu unterstützen. Der Kernel erstellt für jedes Coldplugged Device eine uevent-Datei im sysfs. Wenn udev gestartet wird, liest es diese Dateien ein und imitiert Hotplug Events für die Geräte.
Die Events, die vom Kernel an den Daemon gereicht werden, sind nicht unbedingt in der richtigen Reihenfolge und müssen daher erst vom udev-Daemon geordnet werden. Ein Kindprozess wird erst dann ausgeführt, wenn der Vaterprozess abgeschlossen ist.
Wird zum Beispiel eine Festplatte dem System hinzugefügt, dann wird erst der Main Block Device Event abgearbeitet bis der Event für die Partitionen ausgeführt wird. Dieser kann dann schon auf die Informationen zurückgreifen, die in der udev-Datenbank für den Vaterprozess gespeichert wurden.
Der aktuelle Status der Eventqueue kann unter /dev/.udev/queue eingesehen werden. Wenn dieser Ordner existiert, werden in diesem Moment Events ausgeführt oder befinden sich in der Schlange. Jeder Event in diesem Ordner ist ein Symlink auf den entsprechenden sysfs-Eintrag. Alle fehlgeschlagen Ereignisse werden im Verzeichnis /dev/.udev/failed gelistet. Sollte später für dasselbe Gerät ein Event erfolgreich sein, wird der Eintrag wieder gelöscht.
udevinfo -a -p /sys/block/sda
...
looking at device '/block/sda':
KERNEL=="sda"
SUBSYSTEM=="block"
DRIVER==""
ATTR{capability}=="12"
ATTR{stat}==" 30552 44708 1295850 576664 57819 76611 1202372 3461024 1821724 4037688"
ATTR{size}=="156301488"
ATTR{removable}=="0"
ATTR{range}=="16"
ATTR{dev}=="8:0"
looking at parent device '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0':
KERNELS=="0:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{modalias}=="scsi:t-0x00"
ATTRS{ioerr_cnt}=="0x2e"
ATTRS{iodone_cnt}=="0x1d4be"
ATTRS{iorequest_cnt}=="0x1d4be"
ATTRS{iocounterbits}=="32"
ATTRS{timeout}=="60"
ATTRS{state}=="running"
ATTRS{rev}=="3.03"
ATTRS{model}=="ST98823AS "
ATTRS{vendor}=="ATA "
ATTRS{scsi_level}=="6"
ATTRS{type}=="0"
ATTRS{queue_type}=="simple"
ATTRS{queue_depth}=="31"
ATTRS{device_blocked}=="0"
...
|
| Abb. 1: Ausgabe von udevinfo. |
udevmonitor UEVENT[1132632714.285362] add@/devices/pci0000:00/0000:00:1d.1/usb2/2-2 UEVENT[1132632714.288166] add@/devices/pci0000:00/0000:00:1d.1/usb2/2-2/2-2:1.0 UEVENT[1132632714.309485] add@/class/input/input6 UEVENT[1132632714.309511] add@/class/input/input6/mouse2 UEVENT[1132632714.309524] add@/class/usb_device/usbdev2.12 UDEV [1132632714.348966] add@/devices/pci0000:00/0000:00:1d.1/usb2/2-2 UDEV [1132632714.420947] add@/devices/pci0000:00/0000:00:1d.1/usb2/2-2/2-2:1.0 UDEV [1132632714.427298] add@/class/input/input6 UDEV [1132632714.434223] add@/class/usb_device/usbdev2.12 UDEV [1132632714.439934] add@/class/input/input6/mouse2 udevmonitor --env UDEV [1132633002.937243] add@/class/input/input7 UDEV_LOG=3 ACTION=add DEVPATH=/class/input/input7 SUBSYSTEM=input SEQNUM=1043 PHYSDEVPATH=/devices/pci0000:00/0000:00:1d.1/usb2/2-2/2-2:1.0 PHYSDEVBUS=usb PHYSDEVDRIVER=usbhid PRODUCT=3/46d/c03e/2000 NAME="Logitech USB-PS/2 Optical Mouse" PHYS="usb-0000:00:1d.1-2/input0" UNIQ="" EV=7 KEY=70000 0 0 0 0 0 0 0 0 REL=103 |
| Abb. 2: udevmonitor in Aktion. |
Eigene Regeln schreiben
Die Regeln bestehen aus Übereinstimmungs- und Anweisungsschlüssel und sind simpel aufgebaut. Es müssen immer alle Übereinstimmungsschlüssel treffen, damit die Anweisungen, die sich hinter den Anweisungsschlüsseln verbergen, ausgeführt werden. Nun stellt sich die Frage, welche Übereinstimmungsschlüssel - oder auf Englisch Match-Keys - können für ein Gerät benutzt werden?
Hier hilft das Kommando udevinfo aus dem udev-Paket weiter. Mit ihm lassen sich Details zu dem angegebenen Gerät anzeigen. Diese Details entsprechen schon den Schlüsseln, die später in den Regeln verwendet werden können. Es muss jedoch darauf geachtet werden, dass eine Regel nur die Schlüssel aus einem Abschnitt der Ausgabe von udevinfo enthalten darf. Abbildung 1 zeigt eine gekürzte Ausgabe von udevinfo auf /dev/sda.
Für diese Festplatte könnte die folgende Regel erstellt werden, die zusätzlich zu dem Device Node noch einen Symlink auf den Device Node erstellt:
BUS="scsi", ATTRS{model}="ST98823AS", SYMLINK+="ersteFestplatte"
Diese Regel muss mit der Endung .rules in einer Datei unter /etc/udev/rules.d/ gespeichert werden. Für eigene Regeln wird häufig eine eigene Datei, wie beispielsweise 10-eigene.rules, erstellt. Sofern bei einem Update die ursprünglichen Dateien überschrieben werden, sind die eigenen Regeln noch vorhanden. Die Dateien mit den verschiedenen Regeln werden, beginnend mit der niedrigsten Nummer, der Reihenfolge nach abgearbeitet. Auf einem Standardsystem, bei dem die Nummer 10 die niedrigste ist, hat dies zur Folge, dass die eigenen Regeln zuerst abgearbeitet werden.
Dieser Abschnitt kann und will keine vollständige Referenz zum Schreiben von udev-Regeln sein. Wer sich näher mit dem Thema beschäftigen möchte, kann unter [1] eine sehr gute Referenz finden.
udev debuggen
Ein wichtiges Tool zum Debuggen von udev ist udevmonitor. Es ist wie udevinfo Bestandteil des udev-Pakets. Mit udevmonitor kann man sichtbar machen, welche uevents vom Kernel an udev weitergereicht werden und wie lange es dauert, diese zu bearbeiten. Auch ist zu sehen, dass udev die Events sortiert und in einer anderen Reihenfolge abarbeitet.
Die Ausgabe in Abbildung 2 zeigt die normale Ausgabe von udevmonitor, wenn eine USB-Maus angeschlossen ist und im Anschluss einen Teil der Ausgabe von udevinfo -env. Dies zeigt die gesamte Umgebung des Events.
Standardmäßig protokolliert udev auch in das syslog. Mit welcher Priorität protokolliert wird, kann in der /etc/udev/udev.conf konfiguriert werden. Im laufenden Betrieb lässt sich der Loglevel über udevcontrol verändern. udevcontrol log_priority=debug ändert den Loglevel auf debug.
Fazit
Udev ist seit einiger Zeit der Standard in allen bekannten Distributionen und es hat sich bei den Kernel-Entwicklern durchgesetzt. Udev erleichtert bei der Administration die Arbeit an vielen Stellen. Mit Hilfe der Regeln bietet es die Möglichkeit benutzerspezifischer Konfigurationen der Geräte, die an dem System angeschlossen sind.
Marius Dorlöchter (info@ordix.de).

