9.11. Горячее подключение: hotplug
Подсистема ядра hotplug динамически обрабатывает подключение и отключение устройств, загружая соответствующие драйверы и создавая файлы устройств (с помощью udevd
). С современным оборудованием и виртуализацией можно подключать «на лету» почти всё: от обычных периферийных устройств USB/PCMCIA/IEEE 1394 до жёстких дисков SATA, и даже процессоров и памяти.
У ядра есть база данных для сопоставления идентификатора каждого устройства необходимому драйверу. Эта база данных используется при загрузке для подключения драйверов всех периферийных устройств, обнаруженных на разных шинах, а также при горячем подключении дополнительного устройства. Когда устройство готово к использованию, отправляется сообщение udevd
, чтобы он создал соответствующую запись в /dev/
.
9.11.2. Проблема именования
До появления горячих подключений было очень просто присвоить устройству фиксированное имя. Оно основывалось просто на расположении устройств на их шине. Но это невозможно, когда такие устройства могут появиться и начать использовать шину. Типичным случаем является использование цифрового фотоаппарата или USB-брелока, которые представляются компьютеру как жёсткие диски. Первый подключённый может стать /dev/sdb
, а второй — /dev/sdc
(если /dev/sda
представляет собой локальный жёсткий диск компьютера). Имя устройства не фиксировано; оно зависит от порядка, в котором устройства подключаются.
Кроме того, всё больше устройств используют динамические значения своих старшего и младшего номеров, из-за чего становится невозможным использовать для данных устройств статические записи, ведь эти важнейшие характеристики могут меняться после перезагрузки.
udev был создан специально для решения этой проблемы.
9.11.3. Как работает udev
Когда ядро уведомляет udev о появлении нового устройства, последний собирает различную информацию о данном устройстве из соответствующих записей в /sys/
, особенно тех, которые позволяют уникально идентифицировать его (MAC-адрес сетевой карты, серийный номер некоторых USB-устройств и т. п.).
Вооружившись этой информацией, udev сверяется со всеми правилами, содержащимися в /etc/udev/rules.d/
и /lib/udev/rules.d/
. В ходе этого процесса он принимает решение, какое имя присвоить устройству, какие символьные ссылки создать (чтобы дать альтернативные имена) и какие команды запустить. Проверяются все эти файлы, и все правила выполняются последовательно (если в файлах не используются директивы «GOTO»). Так что может быть несколько правил, соответствующих отдельному событию.
Синтаксис файлов правил довольно прост: каждый ряд содержит критерии выбора и присваивание значений переменным. Первые используются для отбора событий, на которые нужно реагировать, а последние определяют действие, которое нужно предпринять. Они все разделяются запятыми, и оператор используется для того, чтобы косвенным образом отличить критерий выбора (с операторами сравнения, такими как ==
или !=
) от директивы присваивания (с такими операторами как =
, +=
или :=
).
Операторы сравнения используются со следующими переменными:
KERNEL
— имя, которое ядро присваивает устройству;
ACTION
— действие, соответствующее событию («add» при добавлении устройства, «remove» при его удалении);
DEVPATH
— путь к записи устройства в /sys/
;
SUBSYSTEM
— подсистема ядра, от которой пришёл запрос (их много, например «usb», «ide», «net», «firmware» и т. п.);
ATTR{attribute}
: file contents of the attribute file in the /sys/$devpath/
directory of the device. This is where you find the MAC address and other bus specific identifiers;
KERNELS
, SUBSYSTEMS
и ATTRS{атрибуты}
— это вариации, которые пытаются найти соответствие разным опциям одного из устройств, являющихся родительскими по отношению к текущему;
PROGRAM
— делегирует проверку указанной программе (истина если она возвращает 0, ложь в противном случае). Содержимое стандартного вывода программы сохраняется, так что его можно использовать в проверке RESULT
;
RESULT
— выполняет проверки стандартного вывода, сохранённого при последнем вызове PROGRAM
.
В правых операндах можно использовать шаблонные выражения, соответствующие нескольким значениям одновременно. Например, *
соответствует любой строке (даже пустой); ?
соответствует любому символу, а []
соответствует набору символов, перечисленных внутри квадратных скобок (или наоборот, если первым символом является восклицательный знак, а непрерывные диапазоны символов указываются как a-z
).
Что касается операторов присваивания, =
присваивает значение (и заменяет текущее значение); в случае списка он очищается и содержит только присвоенное значение. :=
делает то же самое, но запрещает изменение переменной в дальнейшем. +=
добавляет запись в список. Можно изменять следующие переменные:
NAME
— имя файла устройства, который надлежит создать в /dev/
. Учитывается только первое присваивание, остальные игнорируются;
SYMLINK
— список символьных ссылок, которые будут указывать на то же устройство;
OWNER
, GROUP
и MODE
определяют пользователя и группу, владеющих устройством, а также назначенные ему разрешения;
RUN
— список программ, которые должны быть запущены в ответ на событие.
В значениях, присваиваемых этим переменным, могут использоваться следующие подстановки:
$kernel
или %k
— эквивалент KERNEL
;
$number
или %n
— порядковый номер устройства, например для sda3
он был бы равен «3»;
$devpath
или %p
— эквивалент DEVPATH
;
$attr{атрибут}
или %s{атрибут}
— эквивалент ATTRS{атрибут}
;
$major
или %M
— старший номер устройства в ядре;
$minor
или %m
— младший номер устройства в ядре;
$result
или %c
— строковый вывод последней программы, вызванной PROGRAM
;
и наконец, %%
и $$
означают, соответственно, знак процента и знак доллара.
Вышеуказанный перечень не является полным (в него включены только наиболее важные параметры), но страница руководства udev(7) содержит более исчерпывающую информацию.
9.11.4. Конкретный пример
Рассмотрим случай простого USB-брелока и попробуем присвоить ему фиксированное имя. Во-первых, необходимо найти элементы, которые идентифицируют его уникальным образом. Для этого надо подключить его и запустить udevadm info -a -n /dev/sdc
(заменив /dev/sdc на действительное имя, присвоенное брелоку).
#
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"
[...]
Чтобы создать новое правило, можно использовать проверки переменных как устройства, так и его родительских устройств. В приведённом примере можно создать два правила вроде этих:
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"
После того, как эти правила прописаны в файле с именем, например, /etc/udev/rules.d/010_local.rules
, можно просто отсоединить и заново подключить USB-брелок. После этого можно будет убедиться, что /dev/usb_key/disk
представляет диск, ассоциированный с USB-брелоком, а /dev/usb_key/part1
— его первый раздел.