9.11. Wechsel im laufenden Betrieb: hotplug
Das Kernel-Subsystem hotplug steuert das Anschließen und Wegnehmen von Geräten, indem die passenden Treiber geladen und die zugehörigen Gerätedateien (mit Hilfe von udevd
) erzeugt werden. Mit moderner Hardware und Virtualisierung kann fast alles im Betrieb angeschlossen und entfernt werden: nicht nur die üblichen via USB/PCMCIA/IEEE oder 1394 angeschlossenen Peripheriegeräte, sondern auch SATA-Festplatten genauso wie die CPU und der Hauptspeicher.
Der Kernel verfügt über eine Datenbank, die jeder Geräte-ID den erforderlichen Treiber zuordnet. Diese Datenbank wird während des Hochfahrens dazu benutzt, alle Treiber für die Peripheriegeräte zu laden, die auf den verschiedenen oben genannten Bussen erkannt wurden, aber auch wenn ein zusätzliches Hotplug-Gerät verbunden wird. Sobald das Gerät einsatzbereit ist, wird eine Nachricht an udevd
geschickt, so dass es einen entsprechenden Eintrag in /dev/
erstellen kann.
9.11.2. Das Namensproblem
Vor dem Aufkommen von Hotplug-Verbindungen war es leicht, einem Gerät einen festen Namen zuzuordnen. Er beruhte einfach auf der Position des Gerätes auf seinem jeweiligen Bus. Dies ist jedoch nicht möglich, wenn solche Geräte auf dem Bus kommen und gehen können. Der typische Fall ist die Benutzung einer Digitalkamera und eines USB-Sticks, die dem Rechner beide als Plattenlaufwerke erscheinen. Das als erstes angeschlossene könnte /dev/sdb
sein und das zweite /dev/sdc
(während /dev/sda
die eigene Festplatte des Rechners darstellt). Der Gerätename ist nicht festgelegt; er hängt von der Reihenfolge ab, in der die Geräte angeschlossen werden.
Zusätzlich verwenden Treiber in zunehmendem Maße dynamische Werte für die Haupt- und Nebennummern der Geräte, wodurch es unmöglich wird, feststehende Einträge für bestimmte Geräte zu verwenden, da diese wesentlichen Kennzeichen sich nach einem Neustart ändern können.
udev wurde entwickelt, um genau dieses Problem zu lösen.
9.11.3. Wie udev funktioniert
Wenn udev vom Kernel über das Erscheinen eines neuen Gerätes informiert wird, sammelt es verschiedene Informationen über das betreffende Gerät, indem es die entsprechenden Einträge in /sys/
zurate zieht, vor allem diejenigen, die es eindeutig identifizieren (MAC-Adresse bei einer Netzwerkkarte, Seriennummer bei einigen USB-Geräten usw.).
Mit all diesen Informationen versehen konsultiert udev dann alle in /etc/udev/rules.d/
und /lib/udev/rules.d/
enthaltenen Regeln. Im Verlauf dieses Vorgangs entscheidet es, wie das Gerät bezeichnet werden soll, welche symbolische Verknüpfung erstellt werden soll (um ihm einen alternativen Namen zu geben), und welche Befehle ausgeführt werden sollen. All diese Dateien werden der Reihe nach konsultiert und alle Regeln nacheinander ausgewertet (außer wenn eine Datei „GOTO“-Anweisungen verwendet). So kann es mehrere Regeln geben, die sich auf einen bestimmten Vorgang beziehen.
Die Syntax von Regeldateien ist recht einfach: jede Zeile enthält Auswahlkriterien und die Festsetzung von Variablen. Erstere werden gebraucht, um Vorgänge auszuwählen, auf die reagiert werden muss, und letztere bestimmen die zu ergreifenden Maßnahmen. Sie sind alle einfach durch Kommata getrennt, und der Operator teilt sie implizit in Auswahlkriterien (durch Vergleichsoperatoren wie ==
oder !=
) und Zuordnungsanweisungen ein (durch Operatoren wie =
, +=
oder :=
).
Vergleichsoperatoren werden bei den folgenden Variablen verwendet:
KERNEL
: die Bezeichnung, die der Kernel dem Gerät gibt;
ACTION
: die dem Vorgang entsprechende Aktion („add“, wenn ein Gerät hinzugefügt wurde, „remove“, wenn es entfernt worden ist);
DEVPATH
: der Pfad zum /sys/
-Eintrag des Geräts;
SUBSYSTEM
: das Kernel-Subsystem, das die Anfrage gestellt hat (es gibt viele, einige Beispiele sind „usb“, „ide“, „net“, “firmware“ usw.);
ATTR{attribut}
: Dateiinhalt der attribute-Datei im /sys/$devpath/
-Verzeichnis des Geräts. Hier finden Sie die MAC-Adresse und andere bus-spezifische Kennungen;
KERNELS
, SUBSYSTEMS
und ATTRS{attributes}
sind Varianten, die versuchen, den verschiedenen Optionen auf einem der übergeordneten Geräte des vorliegenden Geräts zu entsprechen;
PROGRAM
: delegiert den Test an das angegebene Programm (wahr, falls es 0 ausgibt, unwahr, falls nicht). Der Inhalt der Standardausgabe des Programms wird gespeichert, so dass er vom RESULT
-Test erneut verwendet werden kann;
RESULT
: führt Tests auf der Standardausgabe durch, die während des letzten Aufrufs von PROGRAM
gespeichert wurden.
Die richtigen Operanden können Strukturausdrücke benutzen, um mehreren Werten gleichzeitig zu entsprechen. Zum Beispiel entspricht *
jeder Zeichenkette (selbst einer leeren); ?
entspricht jedem Zeichen und []
entspricht dem Zeichensatz zwischen den eckigen Klammern (oder seinem Gegenteil, falls das erste Zeichen ein Ausrufezeichen ist und zusammenhängende Reihen von Zeichen, wie a-z
, angegeben sind).
Was die Zuweisungsoperatoren betrifft, so weist =
einen Wert zu (und ersetzt den gegenwärtigen Wert); im Falle einer Liste wird sie geleert und enthält dann nur den zugewiesenen Wert. :=
macht dasselbe, aber unterbindet spätere Veränderungen derselben Variablen. +=
fügt einer Liste eine Position hinzu. Die folgenden Variablen können verändert werden:
NAME
: der Dateiname des Geräts, der in /dev/
erstellt wird. Nur die erste Zuweisung zählt; die übrigen werden ignoriert;
SYMLINK
: die Liste symbolischer Verknüpfungen, die auf dasselbe Gerät zeigen;
OWNER
, GROUP
und MODE
legen den Eigentümer und die Gruppe fest, denen das Gerät gehört, sowie die dazugehörige Berechtigung;
RUN
: die Liste der als Reaktion auf dieses Ereignis auszuführenden Programme.
Die Werte, die diesen Variablen zugewiesen werden, können eine Reihe von Ersetzungen benutzen:
$kernel
oder %k
: entspricht KERNEL
;
$number
or %n
: die Ordnungszahl des Geräts, zum Beispiel wäre dies „3“ für sda3
;
$devpath
oder %p
: entspricht DEVPATH
;
$attr{attribute}
oder %s{attribute}
: entspricht ATTRS{attribute}
;
$major
oder %M
: die Hauptgerätenummer im Kernel;
$minor
oder %m
: die Nebengerätenummer im Kernel;
$result
oder %c
: die Zeichenkettenausgabe des letzten von PROGRAM
aufgerufenen Programms;
und schließlich %%
und $$
für das Prozent- beziehungsweise Dollarzeichen.
Die oben stehenden Listen sind nicht vollständig (sie enthalten nur die wichtigsten Parameter), jedoch sollte die Handbuchseite udev(7) es sein.
9.11.4. Ein konkretes Beispiel
Lassen Sie uns den Fall eines einfachen USB-Sticks betrachten und versuchen, ihm eine feststehende Bezeichnung zu geben. Zunächst müssen Sie die Elemente finden, die ihn eindeutig identifizieren. Hierzu stecken Sie ihn ein und führen den Befehl udevadm info -a -n /dev/sdc
aus (wobei Sie /dev/sdc durch den Namen ersetzen, der dem Stick tatsächlich zugewiesen ist).
#
udevadm info -a -n /dev/sdc
[...]
looking at device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2/1-2.2:1.0/host9/target9:0:0/9:0:0:0/block/sdc':
KERNEL=="sdc"
SUBSYSTEM=="block"
DRIVER==""
ATTR{range}=="16"
ATTR{ext_range}=="256"
ATTR{removable}=="1"
ATTR{ro}=="0"
ATTR{size}=="126976"
ATTR{alignment_offset}=="0"
ATTR{capability}=="53"
ATTR{stat}==" 51 100 1208 256 0 0 0 0 0 192 25 6"
ATTR{inflight}==" 0 0"
[...]
looking at parent device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2/1-2.2:1.0/host9/target9:0:0/9:0:0:0':
KERNELS=="9:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{device_blocked}=="0"
ATTRS{type}=="0"
ATTRS{scsi_level}=="3"
ATTRS{vendor}=="I0MEGA "
ATTRS{model}=="UMni64MB*IOM2C4 "
ATTRS{rev}==" "
ATTRS{state}=="running"
[...]
ATTRS{max_sectors}=="240"
[...]
looking at parent device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2':
KERNELS=="9:0:0:0"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}=="iCfg"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bmAttributes}=="80"
ATTRS{bMaxPower}=="100mA"
ATTRS{urbnum}=="398"
ATTRS{idVendor}=="4146"
ATTRS{idProduct}=="4146"
ATTRS{bcdDevice}=="0100"
[...]
ATTRS{manufacturer}=="USB Disk"
ATTRS{product}=="USB Mass Storage Device"
ATTRS{serial}=="M004021000001"
[...]
Um eine neue Regel zu erstellen, können Sie Tests für die Variablen des Geräts wie auch für die eines übergeordneten Geräts durchführen. Der oben stehende Fall ermöglicht es uns, zwei Regeln wie die folgenden zu erstellen:
KERNEL=="sd?", SUBSYSTEM=="block", ATTRS{serial}=="M004021000001", SYMLINK+="usb_key/disk"
KERNEL=="sd?[0-9]", SUBSYSTEM=="block", ATTRS{serial}=="M004021000001", SYMLINK+="usb_key/part%n"
Sobald diese Regeln in einer Datei festgehalten sind, die zum Beispiel /etc/udev/rules.d/010_local.rules
heißt, können Sie den USB-Stick einfach entfernen und wieder einstecken. Sie sehen dann, dass /dev/usb_key/disk
die dem USB-Stick entsprechende Platte ist und /dev/usb_key/part1
ihre erste Partition.