Le problème

Pour les nouveaux venus sous Linux, le nommage des périphériques restent souvent assez obscurs. Encore tant que l'on reste dans les périphériques standards et connectés en permanence (disques durs IDE, etc) les choses restent assez simples. Mais lorsque l'on se met en tête de vouloir connecter des périphériques extractibles à chaud (disques durs USB, Firewire, SCSI) les choses se compliquent. Pourquoi ? Parce que les périphériques se voient alors attribués des noms de manière dynamique. La conséquence la plus immédiate de cela est qu'entre deux branchements à chaud du périphérique son nom aura changé (il dépendra de l'historique des branchements). Par exemple si l'on veut brancher à la fois une clé USB et un disque externe, selon que l'on branche la clé en premier, ou le disque externe les noms des périphériques seront interchangés. Pas facile de s'y retrouver pour les points de montage !

Les périphériques et le répertoire /dev

La gestion des périphériques sous Linux est inspirée de celle d'Unix et de sa philosophie générale, à savoir tout est fichier. Pour cela, la plupart (mais pas la totalité) des périphériques sous accessibles par le biais d'un fichier situé dans le répertoire /dev. Les périphériques sont de deux types:

  • en mode caractère.

Cela signifie que l'on peut extraire/envoyer un flux d'octets du/vers le périphérique. C'est le cas de la majorité des périphériques (lecteurs de bandes, imprimantes, liaisons séries/parallèles, etc)

  • en mode bloc.

Cela signifie que l'on a affaire à un périphérique de stockage qui permet d'accéder à des données par blocs de taille déterminée, et qu'il est possible d'accéder à un bloc quelconque sans avoir accéder à tout ceux qui le précèdent.

Regardons de plus près par exemple, le fichier de périphérique associé au disque IDE de la machine sur laquelle je rédige ce billet:

server-gid:~# ls -la /dev/hda
brw-rw---- 1 root disk 3, 0 Mar 14 2002 /dev/hda
Quelle est la signification de cette ligne ? Elle nous explique que /dev/hda est un périphérique en mode bloc (le b initial), puis qu'il n'est accessible en lecture/écriture qu'à l'utilisateur root, ainsi qu'aux membres du groupe disk. Suivent deux indications essentielles pour le fonctionnement correct du matériel, ce sont les numéros majeur (3) et mineurs (0) du périphérique. Ces numéros correspondent respectivement:

  • au numéro sous lequel est enregistré le pilote du périphérique auprès des développeurs du noyau (numéro majeur)
  • au numéro d'identification du périphérique géré par le pilote. Cela est nécessaire car un même pilote peut gérer plusieurs

périphérique. Par exemple dans le cas du pilote de disque IDE, chaque partition se voit attribuée un périphérique dédié.

On peut retrouver les différents pilotes (aussi bien en mode bloc, qu'en mode caractère) géré par le noyau, en listant le contenu de /proc/devices. Sur la machine server-gid, cela donne:

server-gid:~# cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 ttyS
5 cua
7 vcs
10 misc
29 fb
108 ppp
109 lvm
128 ptm
136 pts
162 raw
180 usb

Block devices:
1 ramdisk
3 ide0
7 loop
22 ide1
58 lvm
On voit en effet que la première nappe IDE (ide0) est géré par le pilote dont le numéro majeur est 3. Avec cette façon de gérer les périphériques, il est nécessaire de créer dans le répertoire /dev, le plus grand nombre de périphériques possibles, afin qu'ils accessibles le plus simplement possible. Un des avantages de cette solution réside dans sa grande simplicité (rusticité dirait Luc :)), au prix d'une importante consommation d'espace disque. Et il manque finalement toujours un périphérique dont le support n'existait pas au moment de la création du répertoire.

devfs: Une solution (noyau 2.4.x)

Afin de pallier ces problèmes, il a été proposé lors du développement du noyau 2.4.x, de mettre en place un nouveau système de fichiers appelé devfs qui serait monté sur /dev (qui devient donc un simple répertoire vide) dont le contenu serait dynamiquement créée au démarrage du système (avec les entrées nécessaires et suffisantes au bon fonctionnement du système) et mis à jour lors des insertions/retraits de matériels. Cela correspondait par ailleurs à l'arrivée du bus USB qui ouvrait enfin la porte aux périphériques externes branchables à chaud.

Si l'idée initiale est excellente et déjà implémenté par d'autres Unix (BSD entre autres), des erreurs de conception initiales ont rendu le développement du projet très lent. De nombreuses erreurs n'ont pu être corrigées entraînant finalement l'abandon du projet pour le noyau 2.6.

udev: La solution pour les noyaux actuels (2.6.x)

Les enseignements appris lors du développement de devfs ont été retenus. Il a été décidé de garder le concept initial, à savoir la création à la volée des périphériques nécessaires au fonctionnement du système. Mais au lieu de placer la plus grande partie du code dans le noyau, avec toutes les difficultés de développement, de mise au point et de stabilité que cela entraîne, la totalité du code de udev réside dans l'espace utilisateur. Un démon nommé udevd reçoit des événements provenant du noyau lors de l'insertion/retrait d'un nouveau pilote de périphériques, qui décide grâce à des règles (modifiables par l'administrateur du sytème) le nom, les permissions à donner au nouveau fichier périphérique à faire apparaître sous /dev.

Comment ça marche ?

udev repose sur deux innovations apportés par le noyau 2.6:

  • hotplug: Chaque nouveau périphérique détecté par le noyau entraîne l'émission d'un événement vers l'espace utilisateur qui est prend en compte par le démon hoplug. Celui-ci par la consultation d'une base de données de périphériques décide du pilote à charger dynamiquement (module).
  • sysfs: L'ensemble des périphériques attachés au système sont décrits par ce système de fichiers virtuel (dont l'existence

n'est pas lié à un support physique, à l'instar de procfs monté sous /proc) monté sous /sys. De nombreux détails à propos des périphériques sont disponibles (dont les numéros de séries).

L'ensemble de ces mécanismes rend possible la solution proposée par udev, à savoir:

  • La réception des événements destinés à hotplug, pour détecter l'insertion ou le retrait d'un nouveau périphérique
  • La récupération de données permettant l'identification unique d'un périphérique grâce à sysfs.
  • La création ou la destruction d'un périphérique sous /dev en fonction de règles fournies par l'administrateur du système.

Quelques exemples d'utilisation

Les règles afférentes à la construction des périphériques par udev sont stockés sous /etc/udev/rules.d. Une telle règle se présente sous la forme d'un suite de clé suivi d'un nom à donner au périphérique, ainsi qu'un certain nombre de liens à créer vers ce périphérique. Pour qu'une règle agisse, il faut que l'ensemble des clés soient vérifiées. Si aucune des règles ne s'applique, alors le périphérique est créé avec le nom par défaut attribué par le noyau.

Les clés utilisables pour identifier les périphériques sont entre autres:

  • BUS: Le type de périphérique que l'on détecter (usb, pci, pcmcia, ieee, etc)
  • KERNEL: Le nom que le noyau donne à ce périphérique
  • ID: L'identificateur donné au périphérique par le bus concerné.
  • PLACE: La position du périphérique sur le bus.
  • SYSFS{nom_de_fichier}. Le contenu d'une entrée précisée par le nom de fichier dans le répertoire /sys.

Par exemple, la règle suivante:
BUS="usb", SYSFS{serial}="W09090207101241330", NAME="lp_color", SYMLINK="lp0"
attribuera le périphérique lp_color à l'imprimante USB dont le numéro de série est W09090207101241330. Par ailleurs, un lien symbolique nommé lp0 sera créé vers ce périphérique. Cela permet de conserver une compatibilité arrière pour les applications qui s'attendent à des périphériques nommés avec les anciennes convention.

Comment récupérer les numéros de série et autres informations sous /sys

On voit donc qu'il est possible d'obtenir un nommage persistent des périphériques grâce à udev. Il suffit pour cela de disposer de suffisamment d'informations sur le périphérique concerné pour être sûr qu'il sera toujours accessible via le même fichier de périphérique sous /dev. Ces informations sont disponibles sous /sys. Encore faut-il pouvoir trouver facilement ce que l'on recherche. Pour cela, il convient d'appliquer la méthodologie suivante:

  • Insertion du périphérique, sans réglage de udev particulier.
  • Vérification dans /var/log/syslog du ou des périphérique que udev a créé. Pour la suite, on suppose qu'il s'agit de /dev/sda, et de /dev/sda1.

Le premier périphérique créé correspond au disque complet. Il est possible d'utiliser l'utilitaire fdisk sur ce périphérique afin de créer de nouvelles partitions. Le second périphérique correspond à l'unique partition créée sur ce disque.

  • Grâce à l'utilitaire udevinfo, il est possible de récupérer le chemin d'accès dans /sys qui contient les informations relatives à ces périphériques. Pour cela il convient de lancer la commande suivante dans un terminal:

server-gid:~#udevinfo -q path -n /dev/sda
/block/sda
Cela signifie que le répertoire /sys/block/sda contient les informations récupérer par le noyau, et le pilote de périphérique concernant /dev/sda.

  • Toujours grâce à l'utilitaire udevinfo, il est possible de récupérer l'ensemble des informations utilisables par udev pour identifier le périphérique de manière unique. Il s'agit de l'ensemble des clés de type SYSFS permettant d'identifier un périphérique. Pour cela, on lance la commande suivante dans un terminal:

server-gid:~#udevinfo -a -p /block/sda
Notons qu'il est possible de regrouper les deux commandes précédentes en une seule (remarquez bien les antiquotes utilisées):
server-gid:~#udevinfo -a -p `udevinfo -q path -n /dev/sda`
follow the class device's "device"
looking at the device chain at '/sys/devices/pci0000:00/0000:00:02.1/usb3/3-3/3-3:1.0/host0/0:0:0:0':
BUS="scsi"
ID="0:0:0:0"
SYSFS{detach_state}="0"
SYSFS{type}="0"
SYSFS{max_sectors}="240"
SYSFS{device_blocked}="0"
SYSFS{queue_depth}="1"
SYSFS{scsi_level}="3"
SYSFS{vendor}=" "
SYSFS{model}="USB 2.0M DSC "
SYSFS{rev}="1.00"
SYSFS{online}="1"

looking at the device chain at '/sys/devices/pci0000:00/0000:00:02.1/usb3/3-3':
BUS="usb"
ID="3-3"
SYSFS{detach_state}="0"
SYSFS{bNumInterfaces}=" 1"
SYSFS{bConfigurationValue}="1"
SYSFS{bmAttributes}="c0"
SYSFS{bMaxPower}=" 0mA"
SYSFS{idVendor}="052b"
SYSFS{idProduct}="1514"
SYSFS{bcdDevice}="0100"
SYSFS{bDeviceClass}="00"
SYSFS{bDeviceSubClass}="00"
SYSFS{bDeviceProtocol}="00"
SYSFS{bNumConfigurations}="1"
SYSFS{speed}="12"
SYSFS{manufacturer}="Tekom Technologies, Inc"
SYSFS{product}="USB 2.0M DSC"

Ceci permet alors d'écrire une règle du type:
BUS="usb", SYSFS{manufacturer}="Tekom Technologies, Inc", NAME="%k" SYMLINK="cleusb%n"
Ceci provoquera la création de périphérique avec le nom proposé par le noyau (le ''%k' dans la clé NAME), sur lesquels pointeront des liens nommés cleusb. Il devient alors possible de créer une entrée dans /etc/fstab qui pointe en permanence vers le périphérique correct:
/dev/cleusb1 /mnt/cleusb vfat rw,users,uid=utilisateur,gid=utilisateur 0 0

Où stocker les régles

Afin de ne pas pertuber les mises à jour du système, et pour ne pas interférer avec les règle fournies par les paquets officiels, on stockera de préférences les nouvelles règles udev dans un fichier séparés du fichier /etc/udev/rules.d/udev.rules, par exemple/etc/udev/rules.d/00-myrules.rules en s'arrageant pour que l'ordre alphabétique sur les noms de fichiers fasse en sorte que ce fichier soit exploré le premier, et par conséquent prenne le pas sur les règles par défaut.

Conclusion

Ceci conclue ce petit billet sur udev. Il existe encore de nombreuses possibilités que je vous laisse découvrir grâce aux liens suivants. En particulier, en le combinant à autofs, il devient possible d'avoir un montage/démontage automatique et transparent des périphériques de stockage externes, comparable à celui que l'on peut trouver sous Windows XP.

La documentation officielle de udev
Un article sur udev dans Linux Journal
Écriture de règles pour udev