Powered By Blogger

Sunday, November 11, 2007

udev в GNU/Linux

Содержание

* 1 udev - реализация devfs на уровне пользователя
* 2 Вступление
* 3 /dev
* 4 Проблемы. Какому файлу в /dev соответствует какое устройство?
* 5 Недостаточно номеров
* 6 объём статического /dev слишком велик
* 7 devfs
* 8 Цели, стоящие перед udev
* 9 namedev
* 10 libsysfs
* 11 udev

udev - реализация devfs на уровне пользователя

Вступление

Начиная с ядра версии 2.5 все физические и виртуальные устройства в системе видны пользователю через файловую систему sysfs, которая представляет все устройства ввиде иерархической древовидной структуры. Когда в системе появляется новое устройство или удаляется старое, hotplug уведомляет об этом окружение пользователя. Используя два этих момента, можно создать пользовательскую реализацию динамической файловой системы в /dev, которая предоставляла бы более гибкие правила именования устройств, нежели это было в devfs. В этом документе обсуждается udev - программа, которая заменяет devfs без потерь для функциональности системы. Более того, udev создаёт в /dev файлы только для тех устройств, которые присутствуют на данный момент в системе. udev предоставляет и новые возможности, которых не было в devfs:

1. постоянно закреплённые за устройствами имена, которые не зависят от того, какое положение они занимают в дереве устройств.
2. уведомление внешних по отношению к ядру программ, если устройство было заменено.
3. гибкие правила именования устройств.
4. динамическое использование нижних и верхних номеров устройств.
5. переход правил именования устройств из ядра в пространство пользователя.

Этот документ объясняет, почему программа, выполняемая в режиме пользователя предпочтительнее чем devfs, которая работает в режиме ядра. Далее также будет объяснено, как работает udev, как создавать для неё расширения (разные схемы именования устройств).

/dev

На каждом Linux-box'е /dev - это директория, в которой находятся файлы устройств, известных системе. Файл устройства - это интерфейс между пользовательскими приложениями и аппаратурой, механизм, предоставляемый ядром для доступа к физическим устройствам системы. Например устройство /dev/hda, как правило, представляет первый (master) IDE-накопитель на первичном канале. Имени hda соответствуют два числа - верхний и нижний номер устройства. Эти номера используются ядром для того, чтобы узнать, к какому физическому устройству необходимо обратиться (да, имена устройств существуют лишь для отвода глаз, реально же используются номера). Обычно каждому типу устройств отводится свой диапазон номеров. На данный момент есть ряд устройств одного типа с разными именами, но одинаковыми номерами (правда, их не так уж и много). Присвоение имён и номеров разным устройствам было проведено LANANA - The Linux Assigned Names And Numbers Authority. Список всех устройств можно найти в http://www.lanana.org/docs/device-list/devices.txt или в директории с исходными кодами ядра в Documentation/devices.txt. По мере того как в ядре появляется поддержка новых устройств, им необходимо выделать новые диапазоны номеров, с тем чтобы к ним можно было получить доступ через /dev. Одна из возможностей преодолеть дефицит номеров в статической /dev - организовать её как файловую систему (devfs). В ядрах серии 2.4 и более ранних диапазоны доступных номеров были 1-255 для нижней и 1-255 для верхней части.

Проблемы. Какому файлу в /dev соответствует какое устройство?

Когда ядро обнаруживает устройство, оно присваивает ему нижний/верхний номер, соответствующий типу устройства. Допустим, при загрузке ядро обнаружило USB-принтер. Оно присвоило этому устройству номера major:180 minor:0, в /dev этому устройству будет соответствовать файл /dev/usb/lp0. Второй USB-принтер получает номера 180:1 и к нему можно обращаться как к /dev/usb/lp1. Но если пользователь изменит топологию USB-устройств, скажем добавив USB-хаб? Тогда при следующей загрузке порядок обнаружения ядром принтеров может измениться. Принтер, на который раньше ссылались как на /dev/usb/lp1 превратиться в /dev/usb/lp0 и наоборот. Это справедливо и для устройств, которые могут быть подключены или удалены во время работы компьютера. Сказанное касается шин PCI hotplug, IEEE 394, USB, CardBus. Появление sysfs в ядрах 2.5 позволяет узнать какое именно устройство данного класса "скрывается" за данным minor-номером. Для системы с двумя подключенными USB-принтерами sysfs sys/class/usb может выглядеть следующим образом

/sys/class/usb

|--  lp0
|      |--dev
|      |--device -> ../../../devices/pci0/00:09.0/usb1/1-1/1-1:0
|      +--driver -> ../../../bus/usb/drivers/usblp
+--  lp1
       |--dev
       |--device -> ../../../devices/pci0/00:0d.0/usb3/3-1/3-1:0
       +--driver -> ../../../bus/usb/drivers/usblp


$ cat /sys/class/usb/lp0/device/serial HXOLL0012202323480 $cat /sys/class/usb/lp1/device/serial W09090207101241330

Симлинки lp0/device и lp1/device ссылаются на директории, содержащие дополнительную информацию об устройствах. Таким образом можно выяснить производителя устройства и его (устройства) уникальный (надеемся) серийный номер. Как мы выяснили, принтеру lp0 соответствует S/N HXOLL0012202323480, а lp1 - W09090207101241330. Если поменять порядок подключения принтеров или кинуть их через хаб, поменяются и их имена (но не серийные номера, конечно!). Вот что у нас получится:

/sys/class/usb

|--  lp0
|      |--dev
|      |--device -> ../../../devices/pci0/00:09.0/usb1/1-1/1-1.1/1-1.1:0
|      +--driver -> ../../../bus/usb/drivers/usblp
+--  lp1
       |--dev
       |--device -> ../../../devices/pci0/00:09.0/usb1/1-1/1-1.4/1-1.4:0
       +--driver -> ../../../bus/usb/drivers/usblp


$ cat /sys/class/usb/lp0/device/serial W09090207101241330 $cat /sys/class/usb/lp1/device/serial HXOLL0012202323480

Теперь принтеру lp0 соответствует S/N W09090207101241330, а lp1 - HXOLL0012202323480. sysfs позволяет с лёгкостью узнать, какому именно устройству соответствует какой файл. Это достаточно мощный механизм сопоставления физическим устройствам файлов устройств в /dev. Однако пользователя не интересует то, что lp0 и lp1 поменялись местами и что не мешало бы внести изменения в некоторые конфигурационные файлы. Пользователь просто хочет печатать на том принтере, на котором ему нужно, не задумываясь, к какому порту подключено USB-устройство и не подключено ил оно вообще через хаб.

Недостаточно номеров

Major- и minor-части номера устройства занимают по 8 бит (т.е., как было уже упомянуто 1-255 для верхней и столько же для нижней части). На данный момент для символьных и блочных устройств осталось не так уж и много major-номеров. Символьные и блочные устройства могут использовать один и тот же major-номер, ядро относит их к разным типам, поэтому конфликтов не возникает. Казалось бы, ещё море свободных номеров. Но на некоторых системах может использоваться, скажем, 4000 дисков, а 8 бит нижней части номера для этого уже явно не достаточно. Для каждого диска ядро резервирует 16 младших номеров, т.к. на диске может быть до 16 разделов. 4000 дисков требуют 64000 (!) файлов-устройств (не хило, не так ли, господа?). Но для того чтобы получить к ним доступ, есть лищь 255 номеров. Конечно и 8 битовая схема сработала бы, если распределить все диски по всему диапазону от 1 до 65535, но ведь большинство старших номеров уже зарезервировано. Дисковая подсистема не может просто оттяпать кусок из диапазона старших номеров. Если же это произошло, необходимо уведомить окружение пользователя, что отныне часть старших номеров переназначена под использование подсистемой SCSI. Из-за этого ограничения многие настаивают на увеличении диапазонов старших и младших номеров. Не стоит забывать и о том, что 4000 дисков это ещё не предел, на некоторых системах есть необходимость подключить одновременно 10000 накопителей. Похоже, что увеличение разрядности номера устройства могло бы произойти в ядрах серии 2.6, но по прежнему проблемой остаётся уведомление пользовательского окружения, какие major- и minor-номера заняты, а какие нет. Даже если диапазон верхних половин номеров будет увеличен, необходимо определить, какому устройству соответствует какой участок этого диапазона. Это требует наличия некоего органа, который будет заниматься распределением блоков номеров и стандартизированием имён файлов-устройств. Кроме того, неисключено, что в будущем и этот ресурс будет исчерпан. Если бы ядро автоматически распределяло диапазоны старших и младших номеров между устройствами, которые представлены в данный момент в системе, необходимость в таком органе отпала бы и верхний предел номеров, вероятно, не будет превышен. Идея с динамическим распределением номеров наталкивается на одно "но": окружение пользователя по прежнему не имеет понятия, какое оборудование подключено к машине и какие номера соответствуют каким устройствам. Пока ещё лишь немногие подсистемы ядра умеют динамически распределять номера устройств. Так например с успехом поступает подсистема USB2Serial (ещё со времён ядер серии 2.2). И вновь старая проблема, неведение пользователя. Было бы бесчеловечно заставлять его просматривать логи ядра, чтобы выяснить, какому устройству соответствуют определённые номера... sysfs, появившаяся в ветке 2.5, позволяет легко выяснить то, что нам так нужно.

Объём статического /dev слишком велик

Далеко не все устройства, для которых есть файлы в /dev рально присутствуют в системе. Некоторые могут быть просто не подключены на данный момент, а некоторых в ней никогда и не было. /dev как правило генерируется при установке ОС на жёстский диск и содержит файлы для всех устройств, известных на данный момент. На машине с установленным Red Hat 9 около 18000 файлов устройств. Иногда это сбивает с толку пользователей, которые пытаются определить, какие устройства реально присутствуют в системе. Поэтому в некоторых Unix-like ОС и дистрибутивах Linux управление /dev отдаётся на откуп ядру, которое знает, какие устройства подключены и которое создаёт в памяти devfs-систему. Такая схема стала достаточно популярной.

devfs

devfs используется в некоторых Unix-like ОС. Её реализация есть и в Linux. Однако в Linux devfs по-прежнему не решает многих проблем. devfs содержит лишь те устройства, которые представлены в системе и таким образом решает проблему с диким объёмом /dev. Но имена файлов в devfs не соответствуют именам, утверждённым LANANA. По этой причине полный переход со статического /dev на динамическую devfs проблематичен. Необходимы кардинальные изменения в файлах конфигурации. Создатели devfs попытались решить эту проблему, введя дополнительный слой совместимости (devfsd-демон), чтобы эмулировать /dev. Однако при том, что совместимость достигнута, правила именования устройств находятся в ведении ядра. Первый IDE-накопитель на первичном канале будет называться /dev/hda или /dev/ide/hd/c0b0t0u0 (лично у меня - /dev/ide/host0/bus0/target0/lun0/disc) и тут ничего не сделаешь. Вообще разработчики ядра не любят выносить правила именования устройств за пределы ядра. Но правила присвоения имён должны быть убраны из ядра. Это позволит разработчикам драйверов не заострять внимание на схемах именования. Короче, ядро не должно волновать то, как пользователь называет устройство. В случае с devfs это невозможно. devfs не позволяет распределять номера устройств динамически, в ней используются номера определённые LANANA. Хотя devfs можно модифицировать, чтобы она позволяла использовать динамические номера, никто до сих пор этого не сделал. Все данные (база данных, имена) devfs хранит в памяти ядра, которая не выгружается на диск. При больших количествах подключенных устройств (те же 4000 дисков) затраты памяти становятся весьма существенными.

Цели, стоящие перед udev

В свете упомянутых проблем началась работа на проектом udev. Цели этого проекта следующие:

1 переход в пространство пользователя
2 создание динамического /dev
3 предоставление пользователю возможности, самому формировать и назначать имена файлов-устройств
4 создание пользовательского API для доступа к информации об устройствах, подключённых к системе

Переход в режим пользователя возможен благодаря тем фактам, что hotplug генерирует события при добавлении или удалении устройств, а с помощью sysfs получить информацию об этих устройствах не сложно. Остальные цели позволяют разбить проект на три подсистемы:

1 namedev - работа с именами устройств
2 libsysfs - библиотека доступа к информации в sysfs
3 udev - динамический аналог /dev

namedev

Ввиду необходимости разных схем именования, эта часть проекта была выделена в отдельную подсистему. Это было сделано чтобы отграничить, собственно udev от правил назначения имён и обеспечить поддержку подключаемых схем другими группами разработчиков. Подсистема именования, namedev, - это стандартный интерфейс, который udev использует, чтобы присвоить имя данному устройству. В первых релизах проекта namedev пока компонуется в один исполняемый файл с udev. Вместе с этими релизами идёт и простая схема именования, основанная на спецификации LANANA. Она скорее всего подойдёт большинству пользователей Linux. namedev позволяет присвоить устройству имя исходя из пяти признаков:

1 метка или серийный номер
2 номер шины
3 номер устройства на шине
4 другое (подстановочное) имя
5 имя устройства в ядре

Первый признак - метка или серийный номер, проверяется при подключении устройства. Этот признак интерпретируется в зависимости от типа устройства. В случае SCSI - это UUID, для USB - серийный номер, для прочих блочных устройств - метка файловой системы. Второй признак - номер шины. Номер шины на большинстве систем меняется редко. Это может произойти при апгрейде BIOS или при подключении hotplug-контроллера. В кажды момент времени номер шины постоянен и уникален. Трерий признак - топология шины. Это может оказаться полезным в случае с USB-шиной. Подстановочное имя используется вместо имени устройства в ядре. И, наконец, если ни один из 4 предыдущих признаков не прошёл, то используется имя устройства в ядре. Это имя совпадает с именем, которое использовалось бы на системе без devfs или udev со статическим /dev. Вот пример конфига, который позволяет назначить устройствам имена, отличные от тех, которые использует ядро:

   1. USB Epson printer to be called lp_epson

LABEL, BUS="usb", serial="HXOLL0012202323480", NAME="lp_epson"

   1. USB HP printer to be called lp_hp

LABEL, BUS="usb", serial=@W09090207101241330", NAME="lp_hp"

   1. sound card with PCI bus id 00:0b.0 to be the first

NUMBER, BUS="pci", id="00:0b.0", NAME="dsp"

   1. sound card with PCI bus id 00:07.1 to be the second

NUMBER, BUS="pci", id="00:0b.0", NAME="dsp1"
libsysfs

libsysfs предоставляет интерфейс для доступа к данным на sysfs. Собственно libsysfs может быть использована не только в проекте udev. Подсистема присвоения имён udev должна запрашивать из sysfs довольно много информации о подключённом устройстве. Было бы глупо дублировать код доступа к sysfs в каждом проекте, который её использует. Поэтому libsysfs была выделена в отдельную подсистему.

udev

Чтобы приводить в действие правила именования устройств udev пользутся услугами namedev и libsysfs. udev вызывается каждый раз, когда ядро обращается к hotplug. Это происходит благодаря ссылке на udev в /etc/hotplug.d/default. Ядро сообщает hotplug массу интересной информации: что за устройство было добавлено (USB, PCI или ещё что-нибудь), а может оно было удалено, а не добавлено, где в sysfs находятся данные об устройстве, вызвавшем событие. Всё это передаётся в udev, который обращается в свою очередь к namedev, чтобы выяснить, какое имя присвоить устройству или просто удалить имя отключенного устройства (если action=remove). Т.о. содержимое /dev динамически изменяется на протяжении работы системы.

По материалам Linux Symposium Версия 2. Публикуется с небольшими изменениями.

Перевод (c) by Ваш покорный слуга, red f0x

No comments:

ПОСЕТИТЕЛИ

free counters