Product SiteDocumentation Site

9.11. Conexión en caliente: hotplug

9.11.1. Introducción

El subsistema hotplug del núcleo administra dinámicamente el agregar y eliminar dispositivos mediante la carga de los controladores apropiados y la creación de los archivos de dispositivo correspondientes (con la ayuda de udevd). Con el hardware moderno y la virtualización, casi todo puede ser conectado en caliente: desde los periféricos USB/PCMCIA/IEEE 1394 usuales hasta discos duros SATA, pero también la CPU y la memoria.
El núcleo tiene una base de datos que asocia cada ID de dispositivo con el controlador necesario. Se utiliza esta base de datos durante el inicio para cargar todos los controladores de los periféricos detectados en los diferentes canales, pero también cuando se conecta un dispositivo en caliente. Una vez el dispositivo está listo para ser utilizado se envía un mensaje a udevd para que pueda crear los elementos correspondientes en /dev/.

9.11.2. El problema de nombres

Antes que existieran las conexiones en caliente, era sencillo asignar un nombre fijo a un dispositivo. Simplemente estaba basado en la posición del dispositivo en su canal correspondiente. Pero esto no es posible cuando dichos dispositivos puede aparecer y desaparecer del canal. El caso típico es el uso de una cámara digital y una llave USB, ambos serán un disco para el equipo. El primero en conectarse puede ser /dev/sdb y el segundo /dev/sdc (siempre que /dev/sda represente el disco duro del equipo en sí). El nombre del dispositivo no es fijo, depende del orden en el que se conecte los dispositivos.
Además, más y más controladores utilizan valores dinámicos para los números mayor/menor de los dispositivos, lo que hace imposible tener elementos estáticos para dichos dispositivos ya que estas características esenciales puede cambiar luego de reiniciar el equipo.
Se creó udev precisamente para solucionar este problema.

9.11.3. Cómo funciona udev

Cuando el núcleo le informa a udev de la aparición de un nuevo dispositivo, recolecta mucha información sobre el dispositivo consultando los elementos correspondientes en /sys/; especialmente aquellos que lo identifican unívocamente (dirección MAC para una tarjeta de red, número de serie para algunos dispositivos USB, etc.).
Con esta información, udev luego consulta todas las reglas en /etc/udev/rules.d y /lib/udev/rules.d. En este proceso decide cómo nombrar al dispositivo, los enlaces simbólicos que creará (para darle nombres alternativos) y los programas que ejecutará. Se consultan todos estos archivos y se evalúan las reglas secuencialmente (excepto cuando un archivo utiliza la directiva «GOTO»). Por lo tanto, puede haber varias reglas que correspondan a un evento dado.
La sintaxis de los archivos de reglas es bastante simple: cada fila contiene criterios de selección y asignaciones de variables. El primero se utiliza para seleccionar los eventos ante los que reaccionar y el último define las acciones a tomar. Se los separa simplemente con comas y el operador implícitamente diferencia entre un criterio de selección (con operaciones de comparación como == o !=) o una directiva de asignación (con operadores como =, += o :=).
Se utilizan los operadores de comparación en las siguientes variables:
  • KERNEL: el nombre que el núcleo le asigna al dispositivo;
  • ACTION: la acción que corresponde al evento («add» cuando se agregó un dispositivo, «remove» cuando fue eliminado);
  • DEVPATH: la ruta al elemento del dispositivo en /sys/;
  • SUBSYSTEM: el subsistema del núcleo que generó el pedido (hay muchos, pero unos pocos ejemplos son «usb», «ide», «net», «firmware», etc.);
  • ATTR{atributo}: el contenido del archivo attribute en el directorio /sys/ruta_de_dispositivo/ del dispositivo. Aquí es donde encontrará la dirección MAC y otros identificadores específicos del canal;
  • KERNELS, SUBSYSTEMS y ATTRS{atributos} son variaciones que intentarán coincidir las diferentes opciones en alguno de los dispositivos padre del dispositivo actual;
  • PROGRAM: delega la prueba al programa indicado (coincidirá si devuelve 0, no lo hará de lo contrario). Se almacenará el contenido de la salida estándar del programa para que pueda utilizarse en la prueba RESULT;
  • RESULT: ejecuta pruebas en la salida estándar almacenada durante la última ejecución de una sentencia PROGRAM.
Los operadores correctos puede utilizar expresiones con patrones para que coincidan varios valores simultáneamente. Por ejemplo, * coincide con cualquier cadena (inclusive una vacía); ? coincide con cualquier carácter y [] coincide el conjunto de caracteres enumerados entre los corchetes (lo opuesto si el primer carácter es un signo de exclamación y puede indicar rangos de caracteres de forma similar a a-z).
En cuanto a los operadores de asignación, = asigna un valor (y reemplaza el valor actual); en el caso de una lista, es vaciada y sólo contendrá el valor asignado. := realiza lo mismo pero evita cambios futuros en la misma variable. Respecto a +=, agrega elementos a una lista. Puede modificar las siguientes variables:
  • NAME: el nombre del archivo de dispositivo que se creará en /dev/. Sólo se tiene en cuenta la primera asignación, las demás son ignoradas;
  • SYMLINK: la lista de enlaces simbólicos que apuntarán al mismo dispositivo;
  • OWNER, GROUP y MODE definen el usuario y el grupo dueños del dispositivo así como también los permisos asociados, respectivamente;
  • RUN: la lista de programas a ejecutar en respuesta a este evento.
Los valores asignados a estas variables pueden utilizar algunas substituciones:
  • $kernel o %k: equivalente a KERNEL;
  • $number o %n: el número de orden del dispositivo; por ejemplo, para sda3 sería «3»;
  • $devpath o %p: equivalente a DEVPATH;
  • $attr{atributo} o %s{atributo}: equivalentes a ATTRS{atributo};
  • $major o %M: el número mayor del dispositivo en el núcleo;
  • $mior o %m: el número menor del dispositivo en el núcleo;
  • $result o %c: la cadena de salida del último programa ejecutado por PROGRAM;
  • finalmente, %% y $$ para los signos de porcentaje y el símbolo de moneda respectivamente.
La lista anterior no está completa (sólo incluye los parámetros más importantes), pero la página de manual udev(7) debería serlo.

9.11.4. Un ejemplo concreto

Consideremos el caso de una simple llave USB e intentemos asignarle un nombre fijo. Primero debe encontrar los elementos que la identificarán de manera unívoca. Para ello, conéctela y ejecuta udevadm info -a -n /dev/sdc (reemplazando /dev/sdc con el nombre real asignado a la llave).
# 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"
[...]
Para crear una nueva regla, puede utilizar las pruebas en las variables del dispositivo así como también en los dispositivos padre. El caso anterior le permite crear dos reglas como las siguientes:
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 vez que haya guardado estas reglas en un archivo, llamado por ejemplo /etc/udev/rules.d, puede desconectar y conectar la llave USB. Podrá ver que /dev/usb_key/disk representa el disco asociado con la llave USB y /dev/usb_key/part1 como su primera partición.