Пока к компьютеру подключена одна флешка, путь /dev/sdb1 кажется надёжным. Стоит добавить второй накопитель или два одинаковых USB-адаптера, и имена начинают меняться после каждой перезагрузки. Скрипты пишут не туда, правила монтирования ломаются. Ядро выдаёт имена по порядку обнаружения, а не по смыслу устройства. udev — механизм systemd, который превращает временные имена в стабильные, задаёт права и запускает обработчики событий.
Как udev встроен в систему
Когда ядро обнаруживает устройство, оно отправляет uevent в пространство пользователя. Демон systemd-udevd получает событие, читает атрибуты из /sys, применяет правила и создаёт недостающие узлы в /dev. Базовые узлы делает devtmpfs, а udev дополняет их символическими ссылками, владельцами, группами и правами.
На старте выполняется coldplug: udevadm trigger проигрывает события для уже существующих устройств. Поэтому правило должно работать и при горячем подключении, и при загрузке. Если устройство зависит от другого, дождитесь его через udevadm settle, а не sleep внутри правила.
Где лежат правила и что важнее
Правила — текстовые файлы .rules. Места поиска идут по возрастанию приоритета:
/usr/lib/udev/rules.d/— правила пакетов, редактировать не нужно;/run/udev/rules.d/— временные, исчезают после перезагрузки;/etc/udev/rules.d/— ваши постоянные правила, перекрывают системные с тем же именем.
Файлы читаются в лексикографическом порядке, поэтому номер задаёт последовательность. Не называйте файл 10-my.rules, если он должен сработать после 60-persistent-storage.rules: берите 99-local.rules. Для NAME действует «первый победил», а SYMLINK может накапливать имена.
Анатомия правила
Строка состоит из пар «ключ, оператор, значение». == проверяет, = присваивает, += добавляет. KERNEL, SUBSYSTEM и ENV смотрят на событие, ATTR — на текущее устройство, ATTRS — на него и родителей. ATTRS нужен, когда idVendor находится на USB-родителе, а не на конечном интерфейсе.
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", ENV{ID_SERIAL_SHORT}=="A50285BI", SYMLINK+="tty-gps", GROUP="dialout", MODE="0660"
Правило создаёт /dev/tty-gps для конкретного переходника, выдаёт группе dialout чтение и запись и не трогает остальные устройства. Значения заключаются в кавычки; пробелы внутри значений лучше не оставлять.
Ключ PROGRAM запускает внешнюю команду и сравнивает её вывод с RESULT, а TEST проверяет существование файла. Эти проверки нужны, когда одного идентификатора мало. Помните: PROGRAM и RUN выполняются от root, поэтому не передавайте им непроверенные данные.
Диагностика: сначала смотреть, потом писать
Не угадывайте атрибуты. udevadm показывает всё, что видит демон, и проверяет правило без перезагрузки:
udevadm info --attribute-walk --name=/dev/ttyUSB0
udevadm info --query=property --name=/dev/ttyUSB0
udevadm monitor --udev --property
udevadm test /sys/class/tty/ttyUSB0
--attribute-walk выводит цепочку родителей и подсказывает ATTRS. monitor показывает события при подключении. test прогоняет правила для sysfs-пути и печатает, какие ссылки и права были бы применены. Если вывод слишком длинный, ищите блоки по строке looking at и проверяйте, на каком уровне находится нужный атрибут. После правки перечитайте правила и проиграйте события:
sudo udevadm control --reload-rules
sudo udevadm trigger --action=change --subsystem-match=tty
sudo udevadm settle
Стабильные имена для USB-устройств
Лучший идентификатор — серийный номер. Он есть не у всех дешёвых переходников: клоны CH340 и PL2303 часто сообщают один номер или не сообщают ничего. Проверьте ID_SERIAL_SHORT и ID_PATH. Если серийник уникален, привязывайтесь к нему. Если нет — к физическому порту:
SUBSYSTEM=="tty", KERNELS=="1-2.3:1.0", SYMLINK+="tty-radio"
SUBSYSTEM=="tty", ENV{ID_PATH}=="pci-0000:00:14.0-usb-0:2.3:1.0", SYMLINK+="tty-radio"
Привязка к порту требует вставлять устройство в тот же разъём. Для двух одинаковых адаптеров без серийника остаётся привязка к разным физическим портам. Для накопителей правило не нужно: используйте /dev/disk/by-uuid, by-partuuid или by-id в /etc/fstab. Эти ссылки создаёт штатный набор правил, и они переживают перестановку дисков.
Постоянные имена сетевых интерфейсов
Для сети NAME= в udev не является правильным инструментом: переименованием управляет systemd через .link-файлы. Создайте файл в /etc/systemd/network/, сопоставьте по MAC и задайте имя:
sudo tee /etc/systemd/network/10-lan.link <<'EOF'
[Match]
MACAddress=aa:bb:cc:dd:ee:ff
[Link]
Name=lan0
EOF
sudo udevadm control --reload
Имя применится при следующей загрузке или переподключении. Проверьте MAC через ip link и ethtool -P eth0. Не используйте Name=eth0 для двух адаптеров: конфликт оставит один интерфейс без имени.
Права доступа и uaccess
MODE="0666" — плохая привычка: устройство получает любой пользователь. Для рабочего места корректнее TAG+="uaccess": systemd-logind выдаёт ACL активному пользователю, а после выхода снимает доступ.
SUBSYSTEM=="usb", ATTR{idVendor}=="1050", ATTR{idProduct}=="0407", TAG+="uaccess"
SUBSYSTEM=="tty", GROUP="dialout", MODE="0660"
Для последовательных портов группа dialout удобна на сервере без графической сессии. Добавьте пользователя и перелогиньтесь: sudo usermod -aG dialout $USER. Проверка — ls -l /dev/tty-gps и getfacl.
Отладка и типичные ошибки
Если правило не срабатывает, включите подробный лог, повторите событие и посмотрите журнал. Не забудьте вернуть обычный уровень:
sudo udevadm control --log-priority=debug
sudo udevadm trigger --action=change --subsystem-match=tty
journalctl -u systemd-udevd -n 100 --no-pager
sudo udevadm control --log-priority=info
Частые причины: перепутаны ATTR и ATTRS; RUN+= оставляет долгий процесс работать в фоне; файл читается раньше системного; не выполнен --reload-rules. Для сложных действий — монтирования или запуска службы — используйте RUN+= только как короткий вызов, а работу передавайте systemd-юниту.
Чек-лист
- Найдите уникальный атрибут через
udevadm info --attribute-walk. - Кладите правило в
/etc/udev/rules.d/99-local.rules. - Проверьте его через
udevadm testдо перезагрузки. - Для дисков используйте UUID, для сети —
.link, для прав —uaccess. - После изменений выполните reload, trigger и
ls -l.
Итог
udev решает инженерную задачу: превращает порядок обнаружения в предсказуемые имена и права. Правило из одной строки может годами удерживать /dev/tty-gps на нужном адаптере, но только если вы опираетесь на реальные атрибуты. Смотрите udevadm info, проверяйте udevadm test, держите локальные файлы в /etc и не давайте устройствам лишних прав. Тогда горячее подключение перестанет быть лотереей.