9.11. Collegamento a caldo: hotplug
Il sottosistema hotplug del kernel gestisce dinamicamente l'aggiunta e la rimozione dei dispositivi, caricando i driver appropriati e creando i corrispondenti file di device (con l'aiuto di udevd
). Con l'hardware moderno e la virtualizzazione, quasi tutto può essere inserito a caldo: dalle comuni periferiche USB/PCM/IEEE 1394 agli hard disk SATA, ma anche la CPU e la memoria.
Il kernel ha un database che associa ogni ID di dispositivo con il driver richiesto. Questo database viene utilizzato durante l'avvio per caricare tutti i driver per le periferiche rilevate sui diversi bus, ma anche quando viene aggiunto un dispositivo supplementare collegato a caldo. Una volta che il dispositivo è pronto all'uso, viene inviato un messaggio a udevd
che quindi sarà in grado di creare la voce corrispondente in /dev/
.
9.11.2. Il problema dei nomi
Prima della comparsa dei collegamenti a caldo, era facile assegnare un nome fisso ad un dispositivo. Esso era basato semplicemente sulla posizione dei dispositivi sui loro rispettivi bus. Ma questo non è possibile quando tali dispositivi possono andare e venire sul bus. Il caso tipico è l'uso di una macchina fotografica digitale e una chiave USB che appaiono entrambe al computer come unità disco. La prima ad essere collegata potrebbe essere /dev/sdb
e la seconda /dev/sdc
(con /dev/sda
che rappresenta il disco rigido del computer). Il nome del dispositivo non è fisso, ma dipende dall'ordine in cui sono collegati i dispositivi.
Inoltre, sempre più driver usano valori dinamici per i numeri maggiori/minori di device, il che rende impossibile avere voci statiche per i dispositivi indicati, in quanto tali caratteristiche essenziali possono variare dopo un riavvio.
udev è stato creato proprio per risolvere questo problema.
9.11.3. Come funziona udev
Quando il kernel notifica a udev la comparsa di un nuovo dispositivo, quest'ultimo raccoglie diverse informazioni sul dispositivo dato consultando le voci corrispondenti in /sys/
, specialmente quelle che lo identificano in modo univoco (indirizzo MAC di una scheda di rete, numero di serie per alcuni dispositivi USB, ecc.).
Armato di tutte queste informazioni, udev consulta allora tutte le regole contenute in /etc/udev/rules.d/
e /lib/udev/rules.d/
. In base a ciò decide quale nome dare al device, quali collegamenti simbolici creare (per avere nomi alternativi), e quali comandi eseguire. Tutti questi file vengono consultati e tutte le regole vengono valutate in sequenza (tranne quando un file utilizza direttive «GOTO»). Così, vi possono essere diverse regole che corrispondono ad un dato evento.
La sintassi dei file delle regole è molto semplice: ogni riga contiene i criteri di selezione e le assegnazioni delle variabili. I primi sono utilizzati per selezionare gli eventi per i quali esiste una necessità di reagire, mentre le seconde definiscono l'azione da eseguire. Sono tutti semplicemente separati da virgole e l'operatore distingue implicitamente tra un criterio di selezione (con operatori di confronto, come ==
o !=
) e una direttiva di assegnazione (con operatori come =
, +=
o :=
).
Gli operatori di confronto vengono utilizzati per le seguenti variabili:
KERNEL
: il nome che il kernel assegna al device;
ACTION
: l'azione corrispondente all'evento («add» quando un dispositivo è stato aggiunto, «remove» quando è stato rimosso);
DEVPATH
: il percorso della voce in /sys/
per il dispositivo;
SUBSYSTEM
: il sottosistema del kernel che ha generato la richiesta (ce ne sono molti, ma alcuni esempi sono «usb», «ide», «net», «firmware», ecc.);
ATTR{attributo}
: il contenuto del file attributo nella directory /sys/$devpath/
del dispositivo. Qui è possibile trovare l'indirizzo MAC e altri identificatori specifici dei bus;
KERNELS
, SUBSYSTEMS
e ATTRS{attributi}
sono variazioni che cercheranno di soddisfare le diverse opzioni su uno dei dispositivi progenitori del dispositivo di corrente;
PROGRAM
: delega il test al programma indicato (true se restituisce 0, false in caso contrario). Il contenuto dello standard output del programma è memorizzato in modo da poter essere riutilizzato dal test RESULT
;
RESULT
: esegue test sullo standard output memorizzato durante l'ultima chiamata a PROGRAM
.
Gli operandi di destra possono utilizzare modelli di espressioni per trovare corrispondere a diversi valori allo stesso tempo. Ad esempio, *
corrisponde a qualsiasi stringa (anche vuota), ?
corrisponde a qualsiasi carattere e []
corrisponde all'insieme di caratteri elencati tra le parentesi quadre (o l'opposto se il primo carattere è un punto esclamativo, e gli intervalli di caratteri contigui sono indicati come a-z
).
Per quanto riguarda gli operatori di assegnazione, =
assegna un valore (e sostituisce il valore corrente); nel caso di un elenco, questo viene svuotato e contiene solo il valore assegnato. :=
fa la stessa cosa, ma impedisce successive modifiche alla stessa variabile. Per quanto riguarda +=
, esso aggiunge un elemento a un elenco. Le seguenti variabili possono essere modificate:
NAME
: il nome del file di device da creare in /dev/
. Vale solo la prima assegnazione, le altre vengono ignorate;
SYMLINK
: l'elenco dei collegamenti simbolici che puntano allo stesso device;
OWNER
, GROUP
e MODE
definiscono l'utente e il gruppo che possiedono il device, nonché i permessi associati;
RUN
: l'elenco dei programmi da eseguire in risposta a questo evento.
I valori assegnati a queste variabili possono utilizzare diverse sostituzioni:
$kernel
o %k
: equivalente a KERNEL
;
$number
o %n
: il numero d'ordine del dispositivo, per esempio, per sda3
sarebbe «3»;
$devpath
o %p
: equivalente a DEVPATH
;
$attr{attributo}
o %s{attributo}
: equivalente a ATTRS {attributo}
;
$major
o %M
: il numero kernel maggiore del device;
$minor
o %m
: il numero kernel minore del device;
$result
o %c
: la stringa prodotta in output dell'ultimo programma invocato da PROGRAM
;
e, infine, %%
e $$
per, rispettivamente, il segno di percentuale e di dollaro.
Gli elenchi precedenti non sono completi (comprendono solo i parametri più importanti), ma la pagina di manuale udev(7) dovrebbe essere completa.
9.11.4. Un esempio concreto
Consideriamo il caso di una semplice penna USB e tentiamo di assegnarle un nome fisso. In primo luogo, è necessario individuare gli elementi che la identificano in modo univoco. Per far questo, collegarla ed eseguire udevadm info -a -n /dev/sdc
(sostituendo /dev/sdc con il nome effettivo assegnato alla chiave).
#
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"
[...]
Per creare una nuova regola, è possibile utilizzare i test sulle variabili del device, così come quelle di uno dei device genitore. Il caso di cui sopra permette di creare due regole come queste:
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"
Una volta che queste regole sono specificate in un file, chiamato ad esempio /etc/udev/rules.d/010_local.rules
, si può semplicemente rimuovere e ricollegare la chiave USB. È quindi possibile vedere che /dev/usb_key/disk
rappresenta il disco associato alla chiave USB e /dev/usb_key/part1
è la sua prima partizione.