Страница 1 из 1

Не очень коротко про udev

Добавлено: 11 ноя 2017, 23:46
xor
Источник: http://illumium.org/node/103

Не очень коротко про udev

Есть такая замудрёная штука в этих наших linux-ах, как udev. Это сервис, который подхватывает и конфигурирует периферию, получая уведомления от ядра. Он гибко настраивается под оборудование и задачи с помощью специальных правил. Стандартные системные правила обычно лежат в директории /lib/udev/rules.d, а наших инвалидов мы можем смело сохранять в /etc/udev/rules.d.

Правила очень простые и работают обычно влоб без всяких выкрутасов, однако, написать их с первого раза без хорошего понимания принципов устройства sysfs ядра linux бывает весьма не просто. В этом то мы сейчас и будем разбираться. Конечно, документации и примеров превеликое множество, но они, как правило, касаются стандартных вещей и тривиальных задач. Мы же будем делать несколько выходящее за рамки и с пониманием того, откуда что на самом деле берётся.

sysfs
Про sysfs (да-да ту самую, которая обычно монтируется в /sys), можно рассказать на большую статью, но здесь мы коснёмся только того, что нам чаще всего может понадобиться. Итак, вот некоторые важные для нас подкаталоги:

/bus — здесь расположены устройства по шинам обмена данными (неожиданно не правда ли ^_^ ). Как правило, сюда смотреть нам не понадобится, но будем иметь ввиду.
/class — тут устройства по классам. Это нам однозначно пригодится.
/devices — устройства по типам. Часто ядро бросает уведомления с путями устройств здесь, а из других подкаталогов сюда смотрят символьные ссылки.
/kernel/debug — файловая система отладочного интерфейса ядра. Может пригодится, чтобы смотерть, что не так с устройствами.
Будем иметь ввиду, что поскольку udev работает с sysfs, то все пути (syspath) следует рассматривать относительно /sys, то есть вместо /sys/devices/xyz писать /devices/xyz.

udevadm
Полезный инструмент для работы с udev, наверно, единственное, без чего нельзя обойтись при настройке.

Исследуем sysfs командой info
Вот как мы можем узнать, что udev думает об интересующем нас устройстве или подсистеме, для примера возьмём gpio на Raspberry Pi:
kayo@amaya ~ $ udevadm info --query=all --path=/class/gpio --attribute-walk

Код: Выделить всё

  looking at device '/class/gpio':
    KERNEL=="gpio"
    SUBSYSTEM=="subsystem"
    DRIVER==""
В этом примере мы не получили ничего интересного, но сможем сделать вывод, что это территория ядра. Записи вида ABCD=="abcd" это селекторы, которые могут быть использованы в качестве условий в правилах.
Пойдём дальше, исследуем контроллер gpio:
kayo@amaya ~ $ udevadm info --query=all --path=/class/gpio/gpiochip0 --attribute-walk

Код: Выделить всё

  looking at device '/devices/virtual/gpio/gpiochip0':
    KERNEL=="gpiochip0"
    SUBSYSTEM=="gpio"
    DRIVER==""
    ATTR{base}=="0"
    ATTR{label}=="bcm2708_gpio"
    ATTR{ngpio}=="54"
Уже интереснее, подсистема gpio и появились атрибуты: начальный порт: 0, количество портов: 54, метка указывает на драйвер контроллера: bcm2708_gpio. Кстати путь изменился потому, что на самом деле /sys/class/gpio/gpiochip0 это символьная ссылка, заруленая на /sys/devices/virtual/gpio/gpiochip0, в чём не сложно убедиться.

Давайте посмотрим что-нибудь другое, например устройство i2c:
kayo@amaya ~ $ udevadm info --query=all --path=/class/i2c-dev/i2c-1 --attribute-walk

Код: Выделить всё

  looking at device '/devices/platform/bcm2708_i2c.1/i2c-1/i2c-dev/i2c-1':
    KERNEL=="i2c-1"
    SUBSYSTEM=="i2c-dev"
    DRIVER==""
    ATTR{name}=="bcm2708_i2c.1"
 
  looking at parent device '/devices/platform/bcm2708_i2c.1/i2c-1':
    KERNELS=="i2c-1"
    SUBSYSTEMS=="i2c"
    DRIVERS==""
    ATTRS{name}=="bcm2708_i2c.1"
 
  looking at parent device '/devices/platform/bcm2708_i2c.1':
    KERNELS=="bcm2708_i2c.1"
    SUBSYSTEMS=="platform"
    DRIVERS=="bcm2708_i2c"
 
  looking at parent device '/devices/platform':
    KERNELS=="platform"
    SUBSYSTEMS==""
    DRIVERS==""
То, что мы здесь видим представляет собой иерархию вложенных друг в друга узлов от частного к общему. Выдавать такую ботву, как вы уже догадались, заставляет параметр --attribute-walk. Стоит обратить внимание на пути и на значения селекторов. У меня на этой шине сидят часы реального времени, поэтому можем сделать так:
kayo@amaya ~ $ udevadm info --query=all --path=/bus/i2c/devices/1-0068 --attribute-walk

Код: Выделить всё

  looking at device '/devices/platform/bcm2708_i2c.1/i2c-1/1-0068':
    KERNEL=="1-0068"
    SUBSYSTEM=="i2c"
    DRIVER=="rtc-ds1307"
    ATTR{name}=="ds1307"
 
  looking at parent device '/devices/platform/bcm2708_i2c.1/i2c-1':
    KERNELS=="i2c-1"
    SUBSYSTEMS=="i2c"
    DRIVERS==""
    ATTRS{name}=="bcm2708_i2c.1"
 
  looking at parent device '/devices/platform/bcm2708_i2c.1':
    KERNELS=="bcm2708_i2c.1"
    SUBSYSTEMS=="platform"
    DRIVERS=="bcm2708_i2c"
 
  looking at parent device '/devices/platform':
    KERNELS=="platform"
    SUBSYSTEMS==""
    DRIVERS==""
Поменялся только самый нижний (то есть верхний) узел, теперь там собственно сами часы.

Ловим события командой monitor
Часто бывает полезно мониторить события ядра и udev, чтобы понять, что происходит, например при добавлении или удалении интересующих нас устройств.
Мы можем запустить мониторинг так:
kayo@amaya ~ $ udevadm monitor --property --kernel --udev

Код: Выделить всё

monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent
Здесь мы ловим все события ядра и самого udev со всеми свойствами. Конечно, можно указать фильтры, но обычно и так в выводе легко увидеть то, что нас интересует. Запускаем монитор, присоединяем устройство и смотрим, что мы имеем.
Давайте для примера попробуем проинициализировать порт gpio:
kayo@amaya ~ $ echo 30 | sudo tee /sys/class/gpio/export >/dev/null
При этом в мониторе будет примерно следующее:

Код: Выделить всё

KERNEL[5232.656509] add      /devices/virtual/gpio/gpio30 (gpio)
ACTION=add
DEVPATH=/devices/virtual/gpio/gpio30
SEQNUM=869
SUBSYSTEM=gpio
 
UDEV  [5232.702553] add      /devices/virtual/gpio/gpio30 (gpio)
ACTION=add
DEVPATH=/devices/virtual/gpio/gpio30
SEQNUM=869
SUBSYSTEM=gpio
USEC_INITIALIZED=32657665
Пришло два события добавления устройства: одно от ядра, другое сгенерировал сам udev.
Теперь можно исследовать появившееся устройство как мы это уже делали:
kayo@amaya ~ $ udevadm info --query=all --path=/class/gpio/gpio30 --attribute-walk

Код: Выделить всё

  looking at device '/devices/virtual/gpio/gpio30':
    KERNEL=="gpio30"
    SUBSYSTEM=="gpio"
    DRIVER==""
    ATTR{edge}=="none"
    ATTR{value}=="0"
    ATTR{active_low}=="0"
    ATTR{direction}=="in"
Как видим, всё так, как и ожидается.
А теперь удалим порт:
kayo@amaya ~ $ echo 30 | sudo tee /sys/class/gpio/unexport >/dev/null
Будет всё тоже самое, только действие удаление вместо добавления:

Код: Выделить всё

KERNEL[5462.444765] remove   /devices/virtual/gpio/gpio30 (gpio)
ACTION=remove
DEVPATH=/devices/virtual/gpio/gpio30
SEQNUM=870
SUBSYSTEM=gpio
 
UDEV  [5462.450600] remove   /devices/virtual/gpio/gpio30 (gpio)
ACTION=remove
DEVPATH=/devices/virtual/gpio/gpio30
SEQNUM=870
SUBSYSTEM=gpio
USEC_INITIALIZED=62446329
Пробуем писать правила
Итак, откуда что берётся мы более-менее разобрались, настало время написать какие-нибудь полезные правила.

Допустим, нам нужно предоставить доступ к устройствам gpio группе gpio. Создаём файл /etc/udev/rules.d/90-gpio.rules, цифра по традиции это приоритет правила. Для управления gpio, то есть активации/деактивации портов служат файлы /sys/class/gpio/export и /sys/class/gpio/unexport. Для управления направлением работы порта и получения/установки значения используются файлы /sys/class/gpio/gpioN/direction и /sys/class/gpio/gpioN/value соответственно. Всё, что нам нужно, переназначить группу для всех этих файлов в gpio, а затем дать права на запись в них для группы.
Вот мой вариант правил:

Код: Выделить всё

SUBSYSTEM=="gpio", KERNEL=="gpiochip[0-9]*", ACTION=="add", DEVPATH=="/devices/virtual/gpio/gpiochip[0-9]*", PROGRAM="/bin/sh -c 'cd /sys%p/subsystem; chown :gpio export unexport; chmod g+w export unexport'"

SUBSYSTEM=="gpio", KERNEL=="gpio[0-9]*", ACTION=="add", DEVPATH=="/devices/virtual/gpio/gpio[0-9]*", PROGRAM="/bin/sh -c 'cd /sys%p; chown :gpio direction value; chmod g+w direction value'"
Первое правило настраивает доступ к интерфейсам export/unexport, а второе — к direction и value каждого настроенного порта. Как было сказано выше, записи ABCD=="xyz" — это селекторы, задающие условие срабатывания правила. Правило действует, если все условия соблюдены. Записи ABCD="xyz" — это действия, то есть то, что будет выполнено при активации правила. В селекторах можно использовать простые шаблоны:

«*» — любое количество символов, в том числе и не одного
«?» — один или ниодного символа
«[abcd]» — один из символов группы (можно задавать диапазоны вида [a-z], [0-9] и тд)
Слишком многого от шаблонов ожидать не следует, это вам не регулярные выражения, однако функционала вполне достаточно для всего. В наших правилах мы использовали шаблон [0-9]* для обобщения имени KERNEL номером устройства. Чтобы инвертировать действие селектора, надо писать его в виде ABCD!="xyz".

В действиях можно использовать подстановки вида %xyz. Вот некоторые из них:

%k — имя KERNEL устройства
%n — номер KERNEL устройства
%p — значение DEVPATH
В наших правилах мы использовали подстановку %p, чтобы не писать длинное значение DEVPATH. Мы использовали действие вызова внешней программы в котором запустили оболочку, чтобы выполнить назначение группы и предоставить права доступа. Здесь может возникнуть мысль, что запускать оболочку — действие избыточное и не обязательное, однако, udev не выполнит более одной программы, указать дважды PROGRAM в одном правиле нельзя, потому что последнее присвоение заменит первое. Более хорошей идеей может быть написать правило дважды, но я не уверен, так ли это на самом деле.

Раньше, когда я исследовал внутренности sysfs, мне казалось странным огромное количество символьных ссылок в разных каталогах, которые порой заставляют следовать длинными зацикленными цепочками. Вся их полезность становится очевидна когда начинаешь писать правила udev. В нашем случае мы использовали символьную ссылку subsystem, чтобы прийти к интерфейсам управления портами, хоть в данном случае пример не очень показателен.

Тестируем правила
Чтобы понять, не накосячили ли мы случайно где-нибудь, можем сделать следующее:
kayo@amaya ~ $ udevadm test --action=add /class/gpio/gpiochip0

Код: Выделить всё

calling: test
version 204
This program is for debugging only, it does not run any program
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.
 
=== trie on-disk ===
tool version:          204
file size:         5632867 bytes
header size             80 bytes
strings            1260755 bytes
nodes              4372032 bytes
load module index
read rules file: /lib/udev/rules.d/10-local-rpi.rules
read rules file: /etc/udev/rules.d/40-scratch.rules
read rules file: /lib/udev/rules.d/42-usb-hid-pm.rules
read rules file: /lib/udev/rules.d/50-firmware.rules
read rules file: /lib/udev/rules.d/50-udev-default.rules
read rules file: /lib/udev/rules.d/55-dm.rules
read rules file: /lib/udev/rules.d/60-cdrom_id.rules
read rules file: /lib/udev/rules.d/60-crda.rules
read rules file: /lib/udev/rules.d/60-fuse.rules
read rules file: /lib/udev/rules.d/60-gnupg.rules
read rules file: /lib/udev/rules.d/60-i2c-tools.rules
read rules file: /lib/udev/rules.d/60-ifplugd.rules
read rules file: /lib/udev/rules.d/60-libgphoto2-2.rules
read rules file: /lib/udev/rules.d/60-persistent-alsa.rules
read rules file: /lib/udev/rules.d/60-persistent-input.rules
read rules file: /lib/udev/rules.d/60-persistent-serial.rules
read rules file: /lib/udev/rules.d/60-persistent-storage-dm.rules
read rules file: /lib/udev/rules.d/60-persistent-storage-tape.rules
read rules file: /lib/udev/rules.d/60-persistent-storage.rules
read rules file: /lib/udev/rules.d/60-persistent-v4l.rules
read rules file: /lib/udev/rules.d/60-triggerhappy.rules
read rules file: /lib/udev/rules.d/61-accelerometer.rules
read rules file: /lib/udev/rules.d/64-btrfs.rules
read rules file: /lib/udev/rules.d/64-xorg-xkb.rules
read rules file: /lib/udev/rules.d/70-power-switch.rules
read rules file: /lib/udev/rules.d/70-uaccess.rules
read rules file: /lib/udev/rules.d/70-udev-acl.rules
read rules file: /lib/udev/rules.d/71-seat.rules
read rules file: /lib/udev/rules.d/73-seat-late.rules
read rules file: /lib/udev/rules.d/75-cd-aliases-generator.rules
read rules file: /lib/udev/rules.d/75-net-description.rules
read rules file: /lib/udev/rules.d/75-persistent-net-generator.rules
read rules file: /lib/udev/rules.d/75-probe_mtd.rules
read rules file: /lib/udev/rules.d/75-tty-description.rules
read rules file: /lib/udev/rules.d/78-sound-card.rules
read rules file: /lib/udev/rules.d/80-drivers.rules
read rules file: /etc/udev/rules.d/80-ds1307.rules
read rules file: /lib/udev/rules.d/80-net-name-slot.rules
read rules file: /lib/udev/rules.d/80-networking.rules
read rules file: /lib/udev/rules.d/80-udisks.rules
read rules file: /lib/udev/rules.d/85-hwclock.rules
read rules file: /lib/udev/rules.d/85-lirc.rules
read rules file: /lib/udev/rules.d/85-regulatory.rules
read rules file: /lib/udev/rules.d/85-usbmuxd.rules
read rules file: /lib/udev/rules.d/90-alsa-restore.rules
read rules file: /etc/udev/rules.d/90-gpio.rules
read rules file: /lib/udev/rules.d/90-pulseaudio.rules
read rules file: /lib/udev/rules.d/91-permissions.rules
read rules file: /lib/udev/rules.d/95-keyboard-force-release.rules
read rules file: /lib/udev/rules.d/95-keymap.rules
read rules file: /lib/udev/rules.d/95-udev-late.rules
read rules file: /lib/udev/rules.d/95-wedo.rules
read rules file: /lib/udev/rules.d/97-ofono-speedup.rules
read rules file: /lib/udev/rules.d/97-ofono.rules
read rules file: /lib/udev/rules.d/98-lircd.rules
GOTO 'begin' has no matching label in: '/lib/udev/rules.d/98-lircd.rules'
read rules file: /etc/udev/rules.d/99-input.rules
read rules file: /lib/udev/rules.d/99-systemd.rules
read rules file: /etc/udev/rules.d/99-uinput.rules
read rules file: /etc/udev/rules.d/dpchid-udev.rules
rules contain 196608 bytes tokens (16384 * 12 bytes), 26966 bytes strings
14530 strings (129944 bytes), 12355 de-duplicated (105154 bytes), 2176 trie nodes used
PROGRAM '/bin/sh -c 'cd /sys/devices/virtual/gpio/gpiochip0/subsystem; chown :gpio export unexport; chmod g+w export unexport'' /etc/udev/rules.d/90-gpio.rules:2
starting '/bin/sh -c 'cd /sys/devices/virtual/gpio/gpiochip0/subsystem; chown :gpio export unexport; chmod g+w export unexport''
'/bin/sh -c 'cd /sys/devices/virtual/gpio/gpiochip0/subsystem; chown :gpio export unexport; chmod g+w export unexport'' [982] exit with return code 1
ACTION=add
DEVPATH=/devices/virtual/gpio/gpiochip0
SUBSYSTEM=gpio
USEC_INITIALIZED=2070477496
unload module index
Здесь мы форсируем событие, а udev читает правила и выполняет действия при совпадении условий. Я выполнил это под непривилегированным пользователем, поэтому получил ненулевой код возврата.

Чтобы добавленные правила начали работать, надо заставить демон их перечитать:
kayo@amaya ~ $ sudo udevadm control --reload
Теперь добавляем устройства, как мы это уже делали и смотрим, установились ли привилегии.

Давайте напишем ещё каких-нибудь правил. Как я говорил, у нас есть часы реального времени на шине i2c, однако, система не в состоянии правильно связать устройство часов с шиной без дополнительных правил. Логично использовать для этих целей событие добавления устройства i2c, возникающее при инициализации драйвера:

Код: Выделить всё

KERNEL=="i2c-1", SUBSYSTEM=="i2c-dev", ACTION=="add", PROGRAM="/bin/sh -c 'echo ds1307 0x68 > /sys/bus/i2c/devices/i2c-1/new_device'"
KERNEL=="i2c-1", SUBSYSTEM=="i2c-dev", ACTION=="remove", PROGRAM="/bin/sh -c 'echo 0x68 > /sys/bus/i2c/devices/i2c-1/delete_device'"
Правило для удаления здесь приведено до кучи, я не уверен, что оно необходимо, поскольку когда драйвер i2c выгружается, связанные устройства автоматически удаляются.

Типа заключение
Как видим, всё не так уж и страшно, а напротив, просто и логично. Так что пора заканчивать разводить бардак в /etc/rc.local и писать udev правила для всей этой ядерной кухни.