В начало → Linux не для идиотов → Секреты /dev |
Ядро Linux реализует поддержку двух типов устройств – символьных и блочных. Основное их отличие в том, что для блочных устройств операции ввода вывода осуществляются не отдельными байтами (символами), а блоками фиксированного размера.
В Linux вся работа с устройствами ведется через специальные файлы, которые обычно расположены в каталоге /dev
. Специальные файлы не содержат данных, а просто служат точками, через которые можно обратиться к драйверу соответствующего
устройства. У каждого специального файла есть три характеристики – тип устройства (character или block), старший номер устройства
(major number) и младший номер (minor number). Для примера, посмотрим на содержимое каталога /dev
:
$
ls -lL /dev/hd* /dev/ttyS*
brw------- 1 root root 3, 0 Окт 1 20:16 /dev/hda brw------- 1 root root 3, 1 Окт 1 20:16 /dev/hda1 brw------- 1 root root 3, 2 Окт 1 20:16 /dev/hda2 brw------- 1 dalth disk 22, 0 Янв 1 1970 /dev/hdc crw------- 1 root root 4, 64 Янв 1 1970 /dev/ttyS0 crw------- 1 root root 4, 65 Янв 1 1970 /dev/ttyS1 crw------- 1 root root 4, 66 Янв 1 1970 /dev/ttyS2
Как видно, в листинге присутствует описание семи устройств, четырех блочных и трех символьных. Для каждого файла можно увидеть его тип (первая буква в списке прав доступа), пользователя-владельца, группу-владельца, major number, minor number, дату модификации и имя файла.
Для поддержки работы с устройствами в ядре хранятся две таблицы, одна для списка символьных устройств, другая для списка блочных устройств. Каждая строка таблицы сопоставлена какой-то разновидности устройств соответствующего типа – например, для типа “символьные устройства” можно выделить следующие разновидности: COM-порты, LPT-порты, PS/2-мыши, USB-мыши и т.д., для типа “блочные устройства” можно выделить SCSI-диски, IDE-диски, SCSI-CD-приводы, виртуальные диски которыми представляются RAID-контролеры и т.п.
Каждая ячейка в этих системных таблицах сопоставляется конкретному экземпляру устройства. Таким образом, с точки зрения ядра каждое устройство оказывается однозначно проидентифицировано тремя параметрами – типом устройства (блочное или символьное) и двумя числами – номерами строки и номером столбца таблицы, в которой хранится ссылка на драйвер этого устройства.
Пример таблицы символьных и устройств
0 |
1 |
... |
63 |
64 |
65 |
66 |
... |
175 |
... |
|
---|---|---|---|---|---|---|---|---|---|---|
4 |
COM1 |
COM2 |
COM3 |
|||||||
6 |
LPT1 |
LPT2 |
||||||||
10 |
Мышь PS/2 |
Диспетчер томов LVM |
Слот AGP |
|||||||
14 |
Микшер первой зв. карты |
|||||||||
195 |
Первая видеокарта NVidia |
Пример таблицы блочных устройств
0 |
1 |
2 |
... |
16 |
... |
64 |
65 |
... |
|
---|---|---|---|---|---|---|---|---|---|
3 |
IDE Primary Master |
Раздел 1 на IDE Primary Master |
Раздел 2 на IDE Primary Master |
Раздел 16 на IDE Primary Master |
IDE Primary Slave |
Раздел 1 на IDE Primary Slave |
|||
13 |
SCSI диск 1 |
Раздел 1 на SCSI-диске 1 |
Раздел 2 на SCSI-диске 1 |
SCSI диск 2 |
SCSI диск 4 |
Раздел 1 на SCSI-диске 4 |
При попытке обращения к такому специальному файлу ядро переадресует обращение через нужный драйвер на устройство в соответствии
с теми данными, которые указаны в таблице устройств, причем конкретная таблица устройств будет выбрана в зависимости от типа
устройства, строка из таблицы будет выбрана по major number, и столбец будет выбран по minor number. Если мы посмотрим на
примеры наших таблиц, то увидим, что обращение на специальный файл /dev/ttyS1
, который представляет символьное устройство со старшим номером 4 и младшим номером 65 будет адресовано на последовательный
порт COM2, а обращение к файлу /dev/hda2
(блочное устройство со старшим номером 3 и младшим номером 2) будет адресовано на 2-й раздел жесткого диска IDE, работающего
в режиме primary master.
В настоящее время существует два подхода к организации /dev
– статическая организация и динамическая организация. В первом случае в каталоге /dev
заранее создаются специальные файлы для всех возможных устройств вне зависимости от того, загружен драйвер соответствующего
устройства или нет. Во втором случае специальные файлы в /dev
создаются по мере инициализации устройств и загрузки драйверов, и удаляются при выгрузке соответствующего драйвера или удалении
устройства.
Процесс работы со статическим /dev
особых проблем не вызывает – системный администратор при необходимости просто создает отсутствующие файлы командой mknod или MAKEDEV. В том случае, когда какая-либо программа обращается к устройству, чей драйвер не загружен (или загружен, но ни одного соответствующего
устройства не было обнаружено), операционная система возвращает ошибку при попытке открытия файла такого “неверного” устройства.
Ниже приведен пример создания специального файла, соответствующего блочному устройству с мажором 8 и минором 33 и попытка
его использования (отметим, что этот специальный файл соответствует разделу на жестком диске, который не существует на тестовой
машине, где выполнялись эти команды):
#
cd /dev
#
ls -l /dev/hda33
ls: /dev/hda33: No such file or directory#
mknod hda33 b 8 33
#
ls -l hda33
brw-r--r-- 1 root root 8, 33 Окт 11 09:27 hda33#
dd if=hda33 of=/dev/null
dd: opening `hda33': No such device or address
Сообщение No such device or address как раз и означает, что записи для данного устройства в таблице блочных устройств не существует.
Ядро Linux совместно с некоторыми системными утилитами поддерживает такую интересную возможность, как загрузка драйверов “по
требованию”. Реализуется это следующим образом – в момент, когда какая-либо программа пытается открыть специальный файл, не
связанный ни с каким драйвером, ядро делает попытку подобрать соответствующий драйвер самостоятельно. Необходимый драйвер
для каждого специального файла определяется в файле /etc/modules.conf
путем задания специального алиаса (alias) для модуля. Для активизации автоматической загрузки драйвера какого-либо символьного
устройства в большинстве случаев достаточно просто записать в /etc/modules.conf строку следующего вида:
alias char-major-X
-Y
имя_драйвера
Для блочных устройств соответствующая запись слегка меняет свою форму:
alias block-major-X
-Y
имя_драйвера
X и Y – это major и minor специального файла, попытка открыть который должна активизировать автоматическую загрузку драйвера.
Владельцы видеокарт на чипе nVidia могут увидеть этот подход в действии – программа инсталляции драйвера nVidia автоматически
прописывает в modules.conf
запись для загрузки «по требованию» той части драйвера, которая работает в режиме ядра.
Вместо X или Y может также быть подставлен символ “*” , означающий “любое число”. Например, пусть в modules.conf будет написан следующий текст:
alias char-major-81-* bttv
Тогда при обращении к любому символьному устройству с major number равным 81 и которое не ассоциировано ни с каким драйвером, система попытается загрузить драйвер bttv (драйвер TV-тюнера на основе чипа bt848).
Эта возможность обеспечивает Linux возможность плавной загрузки и эффективного использования ресурсов – драйвер не загружается,
пока в нем не возникнет необходимости. К сожалению, за простоту этой схемы приходится платить большим количеством специальных
файлов в /dev
.
Для того, чтобы избавить администратора от ручного создания специальных файлов и для уменьшения количества файлов в /dev
был реализован второй способ организации /dev
– динамическое создание специальных файлов процессе загрузки драйверов. Реализовано это было следующим образом:
Ядро монтирует к каталогу /dev
специальную файловую систему, называемую devfs – эта файловая система хранится целиком в оперативной памяти и не занимает
никакого места на диске. Когда какой-либо драйвер в процессе загрузки или работы обнаруживает обслуживаемое им устройство,
он регистрирует это устройство и сообщает о нем драйверу devfs. Драйвер devfs создает специальный файл, который виден прикладным
программам и может быть корректно открыт. При выгрузке же драйвер устройства сообщает devfs о том, что соответствующее устройство
уже не активно, и драйвер devfs удаляет запись о соответствующем специальном файле из файловой системы devfs.
Файловая система devfs отличается тем, что как правило специальный файл для устройства создается с длинным путем – например,
для раздела на scsi-диске путь может выглядеть примерно так: /dev/scsi/host1/bus1/target3/lun4/partition2
.
Эта особенность является весьма важным плюсом devfs, поскольку она позволяет адресовать дисковые устройства путем указания логического пути их подключения и избежать смены имен SCSI-дисков в некоторых случаях (об этих случаях будет рассказано позднее).
Для того, чтобы организовать более прозрачную структуру каталогов и файлов устройств, используется специальный демон devfsd.
Он взаимодействует с драйвером devfs и ядром и в процессе активизации и деактивизации устройств он создает и удаляет символьные
ссылки вида /dev/disks/disc0
или /dev/hda1
.
Надо отметить, что схема динамического /dev
в некотором смысле близка к той организации каталога /dev
, которая используется некоторыми коммерческими UNIX-системами (например, в Solaris), когда есть виртуальная файловая система
/devices
, и на ее файлы создаются ссылки из /dev
, только в Linux роль программы cfgadm играет демон devfsd, и все изменения в состав /dev
вносятся автоматически.
С помощью devfsd файловая система devfs также реализует автоматическую загрузку модулей, но в этом случае выбор модуля идет
не через комбинацию type/major/minor, а путем указания имени запрошенного файла – когда приложение пытается открыть несуществующий
файл устройства, devfs передает имя запрошенного файла демону devfsd, и последний загружает необходимые модули, например такой
код в файле modules.devfs
:
alias /dev/nvidia* nvidia
Приведет к тому, что при попытке обращения к любому файлу, чей полный путь начинается строкой /dev/nvidia
, будет произведена попытка загрузить драйвер nvidia.o
(для ядра 2.6 nvidia.ko
)
В принципе, на сегодняшний день выбор того, каким именно образом необходимо организовывать /dev
, остается за пользователем и создателем дистрибутива. Например, в Mandrake Linux используется devfs, а в RedHat, Fedora и
SUSE каталог /dev
организован статическим образом, а опытные пользователи часто меняют способ организации /dev
в зависимости от своих предпочтений.
В современных дистрибутивах и ядрах поддержка devfs/devfsd отключена, и на смену этой паре пришел специальный демон, называемый
udev. В отличие от devsfd, который требовал поддержки со стороны ядра, udev такой поддержки не требует. При инициализации
устройства ядро подает сигнал через файловую систему sysfs, и демон udevd, получив сигнал об этом событии, самостоятельно
создает соответствующий специальный файл устройства в каталоге /dev
в соответствии с правилами, описанными в его конфигурационных файлах. При необходимости в этих файлах можно указать например
вызов некоторой внешней программы, создание символьной ссылки и так далее.
Например, если некоторое устройство после подключения перед началом работы требует дополнительной настройки с использованием внешних программ, можно создать соответствующее правило для udev, в котором будет указано какую программу вызвать и какие параметры ей необходимо передать – в частности, это может потребоваться для data-кабелей к некоторым мобильным телефонам Nokia, для устройств которым для корректной работы требуется firmware, или для сохранения или восстановления текущих настроек устройства.
Тем не менее, несмотря на внешние отличия между статической организацией /dev
, devfs и udev, следует помнить что это всего лишь способ заполнения каталога /dev
, и во всех случаях в конечном итоге на файловой системе создаются те же самые файлы символьных и блочных устройств.
В начало → Linux не для идиотов → Секреты /dev |