Udev – Funcionamento e Regras

Detecção e ativação de dispositivos e a função do udev nesse processo

Introdução

Udev (Userspace devfs) é um sistema de arquivos presente desde o kernel 2.6 e substitui o devfs completamente.

É responsável por gerenciar os arquivos de dispositivos do diretório /dev, criando e removendo os mesmos dinamicamente. O udev também tem função do hotplug, trabalhando em conjunto com o HAL (Hardware Abstraction Layer), adicionando os dispositivos conforme são instalados na máquina. Sem ele, seria impossível acessar seu pendrive, webcam e impressora, por exemplo, quando conectados.

Funcionamento

Entendendo como os dispositivos são detectados e adicionados no sistema GNU/Linux.

O sistema GNU/Linux faz uso do kernel e de serviços para detectar e adicionar dispositivos ao sistema, para que os mesmos fiquem disponíveis para uso do usuário e do próprio sistema.

Abaixo, é mostrada a hierarquia no processo de detecção e adição de dispositivos:

Linux: Udev - Funcionamento e Regras

Como visto na figura acima, existe todo um processo até os dispositivos estarem disponíveis para uso do sistema e/ou usuários, que vai desde a detecção do device pelo kernel até a criação de um arquivo de dispositivo no diretório /dev pelo udev.

Todo processo começa com o kernel detectando os dispositivos e exportando informações sobre os mesmos para o sistema de arquivos virtual Sysfs (que está montado no diretório /sys) e ao mesmo tempo, enviando um evento para o daemon do serviço udev.

O evento informa ao udev uma ação no dispositivo, esta ação pode ser um adição, remoção ou alteração de um dispositivo conectado à máquina.

Quando o udev é informado pelo kernel sobre o evento, trata de usar as informações do diretório /sys para identificar o device conectado, carregar o módulo correto e criar um arquivo especial no diretório /dev com exceção da placa de rede que é tratada no kernel.

Aí você pergunta: E o HAL?

Simples, o HAL é um serviço que informa aos programas do sistema se um dispositivo está disponível, foi alterado ou removido a todo tempo.

Na verdade, o HAL já não é essencial para este trabalho, pois algumas distribuições atualmente, conseguem fazer todo este trabalho somente pelo udev, ou em conjunto com o serviço D-Bus. No entanto, para outras distribuições, isso torna-se essencial.

É importante destacar que todo este processo de detectar e ativar os devices explicado anteriormente, não só acontece em Hotplug, ou seja, os dispositivos são instalados com o sistema em uso, mas também em Coldplug, os dispositivos são conectados (instalados) com a máquina desligada e o sistema, quando em uso, detecta-os.

Observe que quem realmente faz o trabalho de hotplug, é o udev.

Os arquivos de dispositivos criados no diretório /dev nada mais são que arquivos especiais, obtidos pela combinação de dois endereços do kernel, um chamado de major number e outro de minor number.

Sendo que:

  • major number → É usado para identificar uma categoria de dispositivos.
  • minor number → Identifica o dispositivo.

Veja na figura abaixo, um exemplo destes números:

Hierarquia dos dispositivos e chaves do udev

Hierarquia dos dispositivos

Antes de começar a trabalhar com regras no udev, é necessário saber como o kernel representa os dispositivos em sua estrutura. O kernel do Linux organiza e representa os dispositivos conectados à máquina em uma estrutura de árvore, ou seja, existe uma hierarquia onde cada device é “filho” de um device “pai”.

Toda esta estrutura está exposta no diretório /sys contendo informações de cada device. Para poder ter mais detalhes sobre os devices, use o comando udevadm e poderá ver todas as chaves dos dispositivos.

Veja o exemplo do arquivo de dispositivo /dev/usb/lp0 da minha máquina, que representa a impressora, rodando o comando abaixo como root:

# udevadm info -a -p $(sudo udevadm info -q path -n /dev/usb/lp0)

A saída do comando mostra bem a organização e hierarquia do device. Sendo que /dev/usb/lp0 é filho do device “usb”, e neste device o kernel nomeia-o de “lp0”, justamente o nome dado pelo udev, que por sua vez é filho do device pai, também chamado de “usb” e o kernel nomeia-o de “2-2:1.1”, carregando o driver “usblp”, que por sua vez é filho do device “pci” e o kernel nomeia-o neste subsistema de “0000:00:1d.0”, carregando o driver “uhci_hcd”.

Veja o exemplo do arquivo de dispositivo /dev/sda que representa o primeiro disco rígido da minha máquina:

# udevadm info -a -p $(sudo udevadm info -q path -n /dev/sda)

O device /dev/sda nesta hierarquia, é filho do device “block”, ou seja, é um dispositivo de bloco e o kernel nomeia-o de “sda”, que por sua vez, é filho do device chamado de “scsi”, e o kernel nomeia-o de “0:0:0:0”, carregando o driver “sd”, que por sua vez é filho do device chamado também de “scsi”, e o kernel nomeia-o de “target0:0:0”.

Observe que, na hierarquia mostrada na saída do comando udevadm, os devices de baixo são pais dos de cima “filhos”.

As chaves do udev

Chaves no udev são informações de dispositivos e podem ser classificadas em dois tipos, existindo as chaves de combinação e de atributos. Ambas serão abordadas a seguir, e o seu uso torna-se necessário para aplicar as regras do udev.

As chaves de combinação mais usadas, são as listadas abaixo:

  • KERNEL → Esta chave informa o nome atribuído pelo kernel ao dispositivo em questão;
  • SUBSYSTEM → Chave que informa o tipo de dispositivo, seja o dispositivo pai ou filho;
  • DRIVER → Chave que informa o driver para o dispositivo;
  • ATTR → Esta chave tem informações adicionais sobre o dispositivo;
  • OWNER → Usuário que será o proprietário do dispositivo criado no /dev;
  • GROUP → Grupo do dispositivo criado no /dev;
  • MODE → Atribui a umask padrão para o dispositivo criado em /dev.

Além das chaves de combinação já vistas, é possível usar outras descritas abaixo:

  • NAME → Esta chave contém o nome que será usado para o dispositivo;
  • SYMLINK → Nesta chave, configura-se um link simbólico;
  • RUN → Adiciona um programa que será executado;
  • ACTION → Ação do evento do dispositivo, se o dispositivo está sendo conectado, removido ou alterado.

Chaves de combinação KERNELS, SUBSYSTEMS e DRIVERS, contêm informações similares às chaves de combinação apresentadas acima, no entanto, estas chaves de combinação são usadas em dispositivos pais (parent devices).

As chaves de atributo ATTR, contêm informação adicional a um dispositivo e a chave de atributo ATTRS é similar a ATTR, no entanto, é usada em devices pais (parent devices).

A observação é que em uma regra, não pode ser atribuído mais de uma chave de atributo e/ou de combinação de dispositivos “pai” (device parent) junto à chave de combinação “filho” e nem combinar chaves de atributos de diferentes dispositivos pais em um único dispositivo pai. Caso isso aconteça, a regra não irá funcionar.

Para a regra funcionar, podemos colocar uma ou mais chaves de combinação “filho” seguida de uma chave de atributo ou de combinação “pai” na mesma regra e/ou, caso queira atribuir mais de uma chave de atributo “pai”, é necessário informar o dispositivo a qual ela pertence, basta usar a chave de combinação “pai” SUBSYSTEMS.

Se ficou confuso, na próxima página irei exemplificar para ter um melhor entendimento.

Para aplicar as regras, além das chaves já mencionadas, podemos fazer uso de strings e é necessário usar operadores. Veja abaixo as strings e operadores que podemos usar.

Strings que podem ser usadas nas regras:

  • * → Corresponde a tudo naquela posição (é similar ao coringa asterisco).
  • ? → Qualquer caractere naquela posição.
  • [] → Corresponde a qualquer caractere especificado dentro dos colchetes.

    Por exemplo, a sequência de caracteres ‘tty[SR]’ iria corresponder tanto ‘ttyS’ quanto a ‘ttyR’. Você pode também especificar intervalos usando a faixa através do caractere ‘-‘. Se o primeiro caractere após o ‘[‘ é um ‘!’, há uma exceção.

Operadores mais usados nas regras:

  • == → Compara se é igual (atribui uma valor de igualdade);
  • != → Compara se é diferente (atribui um valor de diferença);
  • = → Atribui um valor à chave (é usado quando se quer mudar o nome do device, por exemplo);
  • += → Adiciona um valor à chave e pode manter uma lista de entradas – Pode, por exemplo, usar na chave RUN e especificar vários comandos.

Depois de toda esta explicação, vamos começar a aplicar as regras na próxima página.

Regras do udev

O udev é muito poderoso quanto à aplicação das regras, pois permite desde nomear um dispositivo até executar aplicações, e/ou scripts quando conectamos ou desconectamos, ou quando o dispositivo é alterado.

Para aplicar tais regras, é necessário ter informações obtidas através das chaves de cada device.

Os arquivos que contém as regras do udev estão contidos no diretório /etc/udev/rules.d e devem ter a extensão “.rules”, caso algum arquivo dentro do diretório não use essa extensão, o seu conteúdo não será processado.

Eles são lidos em ordem numérico alfabética, por exemplo: se dentro do diretório /etc/udev/rules.d, temos os arquivos “025_usb- regras.rules” e “035_usb-regras.rules”, o arquivo “025_usb-regras.rules” será lido antes que o “035_usb-regras.rules”.

Agora, sintaxe das regras e exemplos de regras que funcionam e não funcionam.

Cada arquivo com extensão “.rules” tem regras que seguem a seguinte sintaxe:

<chave de combinação>, <chave de combinação e ou chave de atributo>, <Ação>

Para obter as informações de cada chave dos devices, usamos o comando udevadm para pegar um relatório.

Não vou entrar em detalhes de uso do comando, mas deixo o link para o manual:

Será usado, para extrair o relatório, a opção “info” que é utilizada para obter informações e “-p”, que indica a localização do dispositivo, e “-a” irá listar informações das propriedades do dispositivo.

Vou listar as propriedades do arquivo de dispositivo /dev/sdc1 que é meu pendrive da SanDisk, usando o comando udevadm como root:

# udevadm info -a -p $(udevadm info -q path -n /dev/sdc1)

looking at device '/devices/pci0000:00/0000:00:1d.7/usb1/1-8/1-8:1.0/host4/target4:0:0/4:0:0:0/block/sdc/sdc1':
  KERNEL=="sdc1"
  SUBSYSTEM=="block"
  DRIVER==""
  ATTR{partition}=="1"
  ATTR{start}=="2048"
  ATTR{size}=="7852032"
  ATTR{ro}=="0"
  ATTR{alignment_offset}=="0"
  ATTR{discard_alignment}=="0"
  ATTR{stat}=="      54      112      426       36        5        1       48        0        0       36       36"
  ATTR{inflight}=="       0        0"
looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb1/1-8/1-8:1.0/host4/target4:0:0/4:0:0:0/block/sdc':
  KERNELS=="sdc"
  SUBSYSTEMS=="block"
  DRIVERS==""
  ATTRS{range}=="16"
  ATTRS{ext_range}=="256"
  ATTRS{removable}=="1"
  ATTRS{ro}=="0"
  ATTRS{size}=="7856127"
  ATTRS{alignment_offset}=="0"
  ATTRS{discard_alignment}=="0"
  ATTRS{capability}=="51"
  ATTRS{stat}=="     251     1491     2002      216        5        1       48        0        0      216      216"
  ATTRS{inflight}=="       0        0"
  ATTRS{events}=="media_change"
  ATTRS{events_async}==""
  ATTRS{events_poll_msecs}=="-1"
looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb1/1-8/1-8:1.0/host4/target4:0:0/4:0:0:0':
  KERNELS=="4:0:0:0"
  SUBSYSTEMS=="scsi"
  DRIVERS=="sd"
  ATTRS{device_blocked}=="0"
  ATTRS{type}=="0"
  ATTRS{scsi_level}=="0"
  ATTRS{vendor}=="SanDisk "
  ATTRS{model}=="Cruzer Blade"
  ATTRS{rev}=="8.02"
  ATTRS{state}=="running"
  ATTRS{timeout}=="30"
  ATTRS{iocounterbits}=="32"
  ATTRS{iorequest_cnt}=="0x2763"
  ATTRS{iodone_cnt}=="0x2763"
  ATTRS{ioerr_cnt}=="0x19"
  ATTRS{modalias}=="scsi:t-0x00"
  ATTRS{evt_media_change}=="0"
  ATTRS{queue_depth}=="1"
  ATTRS{queue_type}=="none"
  ATTRS{max_sectors}=="240"
looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb1/1-8/1-8:1.0/host4/target4:0:0':
  KERNELS=="target4:0:0"
  SUBSYSTEMS=="scsi"
  DRIVERS==""
looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb1/1-8/1-8:1.0/host4':
  KERNELS=="host4"
  SUBSYSTEMS=="scsi"
  DRIVERS==""
looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb1/1-8/1-8:1.0':
  KERNELS=="1-8:1.0"
  SUBSYSTEMS=="usb"
  DRIVERS=="usb-storage"
  ATTRS{bInterfaceNumber}=="00"
  ATTRS{bAlternateSetting}==" 0"
  ATTRS{bNumEndpoints}=="02"
  ATTRS{bInterfaceClass}=="08"
  ATTRS{bInterfaceSubClass}=="06"
  ATTRS{bInterfaceProtocol}=="50"
  ATTRS{modalias}=="usb:v0781p5567d0200dc00dsc00dp00ic08isc06ip50"
  ATTRS{supports_autosuspend}=="1"

Veja que não postei toda a saída do comando pois é muito longa. Agora, abaixo mostro duas regras:

ACTION==”add”, SUBSYSTEM==”block”, KERNEL==”sd??”, ATTRS{vendor}==”SanDisk”, DRIVERS==”usb-storage”, SYMLINK+=”PENDRIVE”
ACTION==”add”, SUBSYSTEM==”block”, KERNEL==”sd??”, ATTRS{vendor}==”SanDisk”, SYMLINK+=”PENDRIVE”

Ambas as regras têm a ação de criar um link para a partição do pendrive. No entanto, somente a segunda regra irá funcionar.

Explicação da primeira regra:

  • 1º → É conferido se o dispositivo que está sendo conectado;
  • 2º → É conferido se o dispositivo conectado é do tipo bloco (armazenamento), ou seja, se filho do dispositivo de bloco;
  • 3º → É conferido se o nome do dispositivo atribuído pelo kernel na chave de combinação KERNEL “filho” é igual a o informado na regra;
  • 4º → Verifica em um dos atributos “pais” se existe o atributo com valor SanDisk, isso garante que a regra só será executada se o pendrive for do fabricante SanDisk;
  • 5º → Verifica se o driver carregado em um dos devices parents é igual ao especificado;
  • 6º → Caso tudo esteja certo na regra, o link chamado PENDRIVE é atribuído à partição do pendrive.

Mesmo toda informação conferindo na primeira regra, não pode funcionar, pois foi atribuído junto à chave de combinação do dispositivo filho (como já foi informado na segunda página), uma chave de atributo (ATTRS) e outra de combinação (DRIVERS) de dispositivos “pais”.

Deixo mais uma vez claro, que a regra não irá funcionar caso seja atribuído mais de uma chave de atributo ou de combinação de um parent device junto à uma chave de combinação de um device filho.

A segunda regra tem quase todos as chaves da primeira regra, com exceção da chave de combinação DRIVERS de um dispositivo pai. E esta regra irá funcionar por que apenas uma chave, seja de atributo ou de combinação (no exemplo, uma foi de atributo) de dispositivo pai está atribuída junto às chaves de combinação do dispositivo filho que são ACTION, SUBSYSTEM e KERNEL.

Agora, veja um exemplo de regra usada para executar um script:

ACTION==”add”, SUBSYSTEM==”block”, KERNEL==”sd??”, SUBSYSTEMS==”usb”, RUN+=”/usr/local/bin/usb-mount.sh”

Veja que esta regra é similar às duas mostradas anteriormente. No entanto, o valor atribuído à chave de combinação SUBSYSTEM do dispositivo filho, indica que deve ser aplicada a um dispositivo de bloco, ou seja, de armazenamento.

E a chave de combinação (do device parent) SUBSYSTEMS, indica que a regra só será aplicada a dispositivos pais do tipo USB; dessa forma, a regra só será aplicada a um pendrive, HD externo e/ou cartão de memória, e não a discos rígidos internos, por exemplo.

Veja abaixo, o conteúdo do script /usr/local/bin/usb-mount.sh:

#!/bin/bash
usb="/dev/"$(ls -1 /sys/block/"$(ls -1 /sys/block |grep sd |tail -1)" |grep sd |tail -1)""
tipo=$(blkid -o list "$usb" |awk -F" " '{print $2}' |tail -1)
label=$(blkid -o list "$usb" |awk -F" " '{print $3}' |tail -1)
dir="/media/"$label""
[ -d "$dir" ]&& sleep 1 || mkdir "$dir"
if [ "$tipo" = "ext3" ];then
mount -t "$tipo" -o relatime,noexec,rw,nodev,user "$usb" "$dir"
elif [ "$tipo" = "ext4" ];then
mount -t "$tipo" -o relatime,noexec,rw,nodev,user "$usb" "$dir"
elif [ "$tipo" = "vfat" ];then
mount -t "$tipo" -o relatime,noexec,rw,user,umask=0022,uid=1000,gid=1000 "$usb" "$dir"
elif [ "$tipo" = "ntfs" ];then
mount -t ntfs-3g -o relatime,noexec,rw,user,umask=0022,uid=1000,gid=1000 "$usb" "$dir"
elif [ "$tipo" = "xfs" ];then
mount -t "$tipo" -o relatime,noexec,rw,nodev,user "$usb" "$dir"
elif [ "$tipo" = "jfs" ];then
mount -t "$tipo" -o relatime,noexec,rw,nodev,user "$usb" "$dir"
elif [ "$tipo" = "btrfs" ];then
mount -t "$tipo" -o relatime,noexec,rw,nodev,user "$usb" "$dir"
fi

A finalidade do script é montar o pendrive, HD externo ou cartões de memória desabilitando a execução de executáveis, dando mais segurança quando “espetar” um destes dispositivos na máquina (afinal, nunca se sabe o que pode ter em um HD externo ou pendrive de alguém, não é verdade?) evitando a execução de vírus e outras pragas.

Podemos também renomear dispositivos como placas de rede. Para ver como fazer isso, veja a saída do comando udevadm para o dispositivo Wlan0 e em seguida, irei mostrar uma regra para isso:

# udevadm info -a -p /sys/class/net/wlan0

looking at device '/devices/pci0000:00/0000:00:1d.7/usb1/1-8/1-8:1.0/net/wlan0':
  KERNEL=="wlan0"
  SUBSYSTEM=="net"
  DRIVER==""
  ATTR{addr_assign_type}=="0"
  ATTR{addr_len}=="6"
  ATTR{dev_id}=="0x0"
  ATTR{ifalias}==""
  ATTR{iflink}=="5"
  ATTR{ifindex}=="5"
  ATTR{type}=="1"
  ATTR{link_mode}=="1"
  ATTR{address}=="00:1a:3f:7c:2e:f4"
  ATTR{broadcast}=="ff:ff:ff:ff:ff:ff"
  ATTR{carrier}=="0"
  ATTR{dormant}=="0"
  ATTR{operstate}=="down"
  ATTR{mtu}=="1500"
  ATTR{flags}=="0x1003"
  ATTR{tx_queue_len}=="1000"
  ATTR{netdev_group}=="0"
looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb1/1-8/1-8:1.0':
  KERNELS=="1-8:1.0"
  SUBSYSTEMS=="usb"
  DRIVERS=="ath9k_htc"
  ATTRS{bInterfaceNumber}=="00"
  ATTRS{bAlternateSetting}==" 0"
  ATTRS{bNumEndpoints}=="06"
  ATTRS{bInterfaceClass}=="ff"
  ATTRS{bInterfaceSubClass}=="00"
  ATTRS{bInterfaceProtocol}=="00"
  ATTRS{modalias}=="usb:v0CF3p7015d0202dcFFdscFFdpFFicFFisc00ip00"
  ATTRS{supports_autosuspend}=="0"

O adaptador USB está recebendo este nome de “wlan0” porque, quando o sistema o detectou, o udev atribuiu a regra abaixo:

SUBSYSTEM==”net”, ACTION==”add”, DRIVERS==”?*”, ATTR{address}==”00:1a:3f:7c:2e:f4″, ATTR{dev_id}==”0x0″, ATTR{type}==”1″, KERNEL==”wlan*”, NAME=”wlan0″

Mas, com estas informações da saída do comando udevadm podemos mudar o nome do device para, por exemplo, Wireless0 como mostra a regra abaixo:

SUBSYSTEM==”net”, ACTION==”add”, DRIVERS==”?*”, ATTR{address}==”00:1a:3f:7c:2e:f4″, ATTR{dev_id}==”0x0″, ATTR{type}==”1″, KERNEL==”*”, NAME=”wireless0″

Explicando a regra:

  • 1º → É verificado se o dispositivo filho conectado é do tipo net (rede);
  • 2º → Verifica se o dispositivo acabou de se conectado;
  • 3º → Na chave de combinação DRIVERS informa que pode ser usado qualquer driver para o respectivo device parent;
  • 4º → Na chave de atributo ATTR{address} é verificado o endereço MAC address do device;
  • 5º e 6º → São verificados mais duas chaves de atributos da mesma (note que ambas foram atribuídas junto ao dispositivo filho, por isso a regra funciona);
  • 7º → Na chave KERNEL é verificado qual o nome atribuído pelo kernel, no exemplo informei que pode ser qualquer um;
  • 8º → Na última chave de combinação informei o novo nome do dispositivo que é wireless0.

Podemos fazer muitas personalizações, além das quais já foram mostradas, podemos, por exemplo, automatizar um processo de backup toda vez que o dispositivo for conectado à máquina de forma similar à regra acima, que executa um script.

Após aplicar uma regra em um dos arquivos com extensão “.rules”, torna-se necessário reiniciar o serviço do udev para as alterações entrarem em vigor (pelo menos nas distribuições Debian e CentOS, por exemplo).

Rolar para cima