Product SiteDocumentation Site

9.11. Hot Plugging: hotplug

9.11.1. Introdução

O subsistema do núcleo hotplug dinamicamente lida com a adição e remoção de dispositivos carregando os drives apropriados e criando os arquivos de dispositivos correspondentes (com a ajuda do udevd). Com hardware e virtualização modernos, quase tudo pode ser adicionado/removido dinamicamente (hotplugged): dos usuais periféricos 1394 USB/PCMCIA/IEEE até discos rígidos SATA, mas também a CPU e a memória.
O núcleo tem um banco de dados que associa cada ID de dispositivo com o driver necessário. Esse banco de dados é usado durante a inicialização para carregar todos os drivers para dispositivos detectados nos diferentes barramentos, mas também quando um dispositivo hotplug adicional é conectado. Uma vez que o dispositivo esteja pronto para uso, uma mensagem é enviada para o udevd para que ele seja capaz de criar a entrada correspondente em /dev/.

9.11.2. O Problema da nomeação

Antes do aparecimento das conexões hotplug, era fácil determinar um nome fixo para um dispositivo. Isso era baseado simplesmente na posição dos dispositivos em seu respectivo barramento. Mas isso não é possível quando dispositivos deste tipo podem ir e vir no barramento. O típico caso é o uso de uma câmera digital e um pendrive, os dois aparecem para o computador como discos. O primeiro conectado pode ser /dev/sdb e o segundo /dev/sdc (com /dev/sda representando o próprio disco rígido do computador). O nome do dispositivo não é fixo; ele depende da ordem na qual o dispositivo é conectado.
Adicionalmente, mais e mais drivers usam valores dinâmicos para os números principal/secundário de dispositivos, o que torna impossível ter entradas estáticas para determinados dispositivos, já que essas características essenciais podem variar após uma reinicialização.
O udev foi criado precisamente para resolver esse problema.

9.11.3. Como o udev Funciona

Quando o udev é notificado pelo núcleo do aparecimento de um novo dispositivo, ele coleta várias informações do referido dispositivo consultando as entradas correspondentes em /sys/, especialmente aquelas que o identificam como único (endereço MAC para uma placa de rede, número serial para alguns dispositivos USB, etc.).
Armado com toda essa informação, o udev então consulta todas as regras contidas em /etc/udev/rules.d/ e /lib/udev/rules.d/. Neste processo ele decide como nomear o dispositivo, quais ligações simbólicas criar (para dar nomes alternativos), e quais comandos executar. Todos esses arquivos são consultados, e as regras são todas avaliadas sequencialmente (exceto quando um arquivo usa a diretiva “GOTO”). Assim, pode haver várias regras que correspondem a um determinado evento.
A sintaxe dos arquivos de regras é bem simples: cada linha contém critérios de seleção e atribuições de variáveis. Os primeiros são usados para selecionar eventos para os quais existe uma necessidade de reagir, e os últimos definem a ação a ser tomada. Todos são simplesmente separados com vírgulas, e o operador implicitamente diferencia entre um critério de seleção (com operadores de comparação, como == ou !=) ou uma diretiva de atribuição (com operadores como =, += ou :=).
Operadores de comparação são usados nas seguintes variáveis:
  • KERNEL: o nome que o núcleo atribui ao dispositivo;
  • ACTION: a ação correspondente ao evento (“add” quando o dispositivo tiver sido adicionado, "remove” quando ele tiver sido removido);
  • DEVPATH: o caminho da entrada /sys/ do dispositivo;
  • SUBSYSTEM: o subsistema do núcleo que gerou a requisição (existem muitos, mas alguns exemplos são “usb”, “ide”, “net”, “firmware”, etc.);
  • ATTR{attribute}: conteúdo do arquivo attribute no diretório /sys/$devpath/ do dispositivo. É onde você encontra o endereço MAC e outros identificadores específicos de barramento;
  • KERNELS, SUBSYSTEMS e ATTRS{attributes} são variações que irão tentar combinar as diferentes opções sobre um dos dispositivos pai do atual dispositivo;
  • PROGRAM: delega o teste ao programa indicado (verdadeiro se retorna 0, falso caso não). O conteúdo da saída padrão do programa é armazenado para que ele possa ser reusado pelo teste RESULT;
  • RESULT: executa testes na saída padrão armazenada durante a última chamada ao PROGRAM.
Os operadores da direita podem usar expressões padrão para casar com vários valores ao mesmo tempo. Por exemplo, * casa com qualquer cadeia de caracteres (mesmo uma vazia); ? casa com qualquer caractere, e [] casa com um conjunto de caracteres listados entre o par de colchetes (ou o oposto do mesmo se o primeiro caractere for um ponto de exclamação, e intervalos contíguos de caracteres são indicados como a-z).
Em consideração aos operadores de atribuição, = atribui um valor (e substitui o valor corrente); no caso de uma lista, ela é esvaziada e contém apenas o valor atribuído. := faz o mesmo, mas previne alterações posteriores a mesma variável. Quanto a +=, ele adiciona um item a lista. As seguintes variáveis podem ser alteradas:
  • NAME: o nome de arquivo do dispositivo a ser criado em /dev/. Apenas a primeira atribuição conta; as outras são ignoradas;
  • SYMLINK: a lista de ligações simbólicas que irão apontar para o mesmo dispositivo;
  • OWNER, GROUP e MODE definem o usuário e grupo a quem pertence o dispositivo, assim como as permissões associadas;
  • RUN: a lista de programas a executar em resposta a este evento.
Os valores atribuídos a essas variáveis podem usar um número de substituições:
  • $kernel ou %k: equivalente a KERNEL;
  • $number ou %n: o número de ordem do dispositivo, por exemplo, para sda3, ele seria “3”;
  • $devpath ou %p: equivalente a DEVPATH;
  • $attr{attribute} ou %s{attribute}: equivalente a ATTRS{attribute};
  • $major ou %M: o maior número de núcleo do dispositivo;
  • $minor ou %m: o menor número do núcleo do dispositivo ;
  • $result ou %c: a cadeia de caracteres emitida ("output") pelo último programa invocado pelo PROGRAM;
  • e, finalmente, %% e $$ para os sinais de porcento e dólar, respectivamente.
As listas acima não são completas (elas incluem apenas os parâmetros mais importantes), mas a página de manual udev(7) deve ser exaustiva.

9.11.4. Um exemplo concreto

Vamos considerar o caso de uma simples chave USB e tentar atribuir um nome fixo para ela. Primeiro, você tem que encontrar os elementos que iram identificar ela de uma maneira única. Para isso, conecte ela e rode udevadm info -a -n /dev/sdc (substituindo /dev/sdc pelo real nome atribuído a chave).
# 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 criar uma nova regra, você pode usar testes nas variáveis do dispositivo, bem como aquelas de um dos dispositivos mãe. O caso acima permite-nos criar duas regras como essa:
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"
Uma vez que essas regras estejam definidas em um arquivo, nomeado por exemplo como /etc/udev/rules.d/010_local.rules, você pode simplesmente remover e reconectar o dispositivo USB. Você pode então ver que /dev/usb_key/disk representa o disco associado ao dispositivo USB, e /dev/usb_key/part1 é sua primeira partição.