Product SiteDocumentation Site

9.11. Горячее подключение: hotplug

9.11.1. Введение

The hotplug kernel subsystem dynamically handles the addition and removal of devices, by loading the appropriate drivers and by creating the corresponding device files (with the help of udevd). With modern hardware and virtualization, almost everything can be “hotplugged“: from the usual USB/PCMCIA/IEEE 1394 peripherals to SATA hard drives, but also the CPU and the memory.
У ядра есть база данных для сопоставления идентификатора каждого устройства необходимому драйверу. Эта база данных используется при загрузке для подключения драйверов всех периферийных устройств, обнаруженных на разных шинах, а также при горячем подключении дополнительного устройства. Когда устройство готово к использованию, отправляется сообщение 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}: содержимое файла свойства файла в каталоге /sys/$devpath/ устройства. Здесь вы можете найти MAC адрес и другие специфические идентификаторы шины;
  • 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;
  • и наконец, %% и $$ означают, соответственно, знак процента и знак доллара.
The above lists are not complete (they include only the most important parameters), but the udev(7) manual page should be exhaustive.

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.0/usb2/2-1/2-1:1.0/host4/target4:0:0/4:0:0:0/block/sdc':
    KERNEL=="sdc"
    SUBSYSTEM=="block"
    DRIVER==""
    ATTR{hidden}=="0"
    ATTR{events}=="media_change"
    ATTR{ro}=="0"
    ATTR{discard_alignment}=="0"
    ATTR{removable}=="1"
    ATTR{events_async}==""
    ATTR{alignment_offset}=="0"
    ATTR{capability}=="51"
    ATTR{events_poll_msecs}=="-1"
    ATTR{stat}=="130  0  6328  435  0  0  0  0  0  252  252  0  0  0  0"
    ATTR{size}=="15100224"
    ATTR{range}=="16"
    ATTR{ext_range}=="256"
    ATTR{inflight}=="0  0"
[...]

  looking at parent device '/devices/pci0000:00/0000:00:10.0/usb2/2-1/2-1:1.0/host4/target4:0:0/4:0:0:0':
[...]
    ATTRS{max_sectors}=="240"
[...]
  looking at parent device '/devices/pci0000:00/0000:00:10.0/usb2/2-1':
    KERNELS=="2-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{busnum}=="2"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{ltm_capable}=="no"
    ATTRS{speed}=="480"
    ATTRS{product}=="TF10"
    ATTRS{manufacturer}=="TDK LoR"
[...]
    ATTRS{serial}=="07032998B60AB777"
[...]
Чтобы создать новое правило, можно использовать проверки переменных как устройства, так и его родительских устройств. В приведённом примере можно создать два правила вроде этих:
KERNEL=="sd?", SUBSYSTEM=="block", ATTRS{serial}=="07032998B60AB777", SYMLINK+="usb_key/disk"
KERNEL=="sd?[0-9]", SUBSYSTEM=="block", ATTRS{serial}=="07032998B60AB777", SYMLINK+="usb_key/part%n"
После того, как эти правила прописаны в файле с именем, например, /etc/udev/rules.d/010_local.rules, можно просто отсоединить и заново подключить USB-брелок. После этого можно будет убедиться, что /dev/usb_key/disk представляет диск, ассоциированный с USB-брелоком, а /dev/usb_key/part1 — его первый раздел.