Open Way | Systems | Distro | Shell | Desktop | Applications
Network | Development | Download | OfftopicКарта сайта
FreeNotesФорум POSIX.ru
На главную страницу

Оборудование и Linux. Новая страница: sysfs

Владимир Попов
2007.02.19
Впервые опубликовано: LinuxFormat, 2006, 77

Вместо предисловия

Вам никогда не казалось парадоксальным, что Linux, который используется в мобильных телефонах и на суперкомпьютерах, пользователи IBM PC часто упрекают в том, что он "плохо поддерживает оборудование"? Не будем вспоминать, что для MicroSoft (а ведь именно с Windows сравнивают Linux на персональных компьютерах) львиную долю работы по поддержке оборудования выполняют производители этого самого оборудования, просто попытаемся проанализировать, есть ли реальные основания для таких упрёков.

Настоящая статья представляет собой попытку "защиты" Linux, опирающуюся на информацию о новой драйверной модели, ставшей частью ядра версии 2.6.

IBM PC: что внутри?

Для начала об оборудовании, непосредственно контактирующем с внутренними шинами IBM PC. Это те самые карты, которые раньше во множестве вставлялись непосредственно в слоты материнской платы. На настоящий момент большая часть этих устройств находится "на борту" - в составе микросхем, расположенных на материнской плате, но сути это не меняет. Каждое такое устройство использует определённые ресурсы - адреса памяти и портов ввода/вывода (о линиях прерывания и каналах прямого доступа к памяти (DMA) упоминать не будем, поскольку шина ISA усилиями Intel таки похоронена окончательно). Казалось бы: устройство скорее предоставляет ресурсы, чем использует их. Но поскольку адресное пространство у компьютера одно, то получается, что предоставляя ячейки памяти или регистры портов ввода/вывода, устройство использует именно адреса компьютера.

И хотя вышеупомянутых карт в составе IBM PC с каждым годом становится всё меньше, количество устройств неизменно возрастает. И каждому требуются ресурсы. Совершенно очевидно, что нужен был механизм, который бы эти ресурсы распределял (хотя бы для того, чтобы обеспечить возможность работы в составе компьютера двух одинаковых устройств, требующих, естественно, одинаковых ресурсов). Такой механизм существует, и называется он - Plug-and-Play. Это может показаться странным, но такое несколько "игривое" название получило именно распределение ресурсов устройствам, а не подключение устройства к компьютеру без перезагрузки последнего, как это можно было бы предположить.

Для такого "горячего" подключения используется совсем иной термин - hotplug. Первоначально о hotplug можно было говорить только в применении к USB и PCMCIA/Cardbus. Потом этот список был дополнен устройствами Firewire и CompactPCI, а в настоящее время hotplug возможен для "нормальных" PCI-устройств (посредством специальных hotplug-контроллеров), IDE (некоторые RAID контроллеры), SCSI, модулей памяти и, наконец, CPU. Вот уж, воистину: "всё смешалось в доме Облонских", как справедливо заметил великий русский писатель.

Основной шиной, посредством которой обмениваются информацией все устройства современного IBM PC, является PCI. Пользователи Linux могут легко в этом убедиться, набрав в командной строке lspci, lspci -vv или scanpci -v. Остальные шины, будь-то AGP или USB, получают доступ к PCI посредством контроллеров, называемых мостами. События, происходящие на этих "прочих" шинах, в принципе, не интересуют систему управления Plug-and-Play, поскольку не влекут за собой перераспределения шинных ресурсов. Так бы и сосуществовали мирно разные устройства: Plug-and-Play контролировал бы распределение внутренних ресурсов вычислительной системы, hotplug реагировал бы на подключения к шинам USB и PCMCIA, пока "горячее" подключение не стали использовать для PCI-устройств.

Кроме того, есть ещё одно обстоятельство, подталкивающее разработчиков Linux к пересмотру принципов работы с оборудованием. Дело в том, что, с точки зрения ОС, принципиально различным образом подключенные устройства, в общем-то, очень похожи. И тем, и другим требуется драйвер, да и представлены они, в конечном счёте, одинаково. Однако об этом - ниже.

Устройства Linux

Linux, как известно, унаследовал от UNIX представление устройств в виде файлов, находящихся в каталоге /dev и характеризующихся уникальным идентификаторами - числами minor и major. Существование таких файлов - абсолютно необходимое условие доступности устройств, и не так давно общепринятой практикой было их "заблаговременное" создание. На настоящий момент каталог /dev, заполненный подобным образом, выглядит просто необъятным, что, по меньшей мере, попросту раздражает.

Кроме того, поскольку под номера minor/major изначально было выделено всего по байту, в скором времени их перестанет хватать. Одним словом, изменения назрели, и, забегая вперёд, скажем, что udev, опирающийся на данные новой драйверной модели, доступные через sysfs, является очередной (после devfs) попыткой решения проблемы неуправляемого роста /dev.

Ещё одно условие использования устройства - это существование и загрузка соответствующего модуля ядра - драйвера. Модуль этот может быть включён в состав ядра и при компиляции последнего, но сути это не меняет: нет модуля - нет работоспособного устройства. Вот теперь картина получается вполне законченной:

Для виртуальных устройств, какими являются сетевые адаптеры ethN, достаточно последовательно загружать командой modprobe module_name модули из каталога /lib/modules/version/kernel/drivers/net, пока вывод команды cat /proc/net/dev не укажет на то, что устройство eth0 появилось. Именно так некоторые live-cd и делают.

Для устройств, нуждающихся в наличии "своего" файла в /dev, потребуется наличие devfs и соответствующего демона devfsd, реагирующего на подключение устройства загрузкой соответствующего драйвера и созданием файла в /dev. Так, для звукового PCI-адаптера модули нужно брать из /lib/modules/version/kernel/sound/pci, а результат оценивать по по наличию устройства /dev/dsp (digital speech processor).

Разумеется, существовали и более интеллектуальные способы поиска нужного драйвера для устройства, основанные на сопоставлении идентификаторов оборудования (cat /proc/bus/pci/devices) и наличия аналогичных данных модулей из файла /lib/modules/version/kernel/modules.pcimap, только то, что подходит для сетевого адаптера или звуковой карты, трудно приложить к неизвестному оборудованию: драйвер, положим, нашли и загрузили, а какой файл устройства создавать? Да и принципы работы devfs оказались не без изъяна. Чего стоило только "размножение" устройств при многократном подключении одного и того же USB-драйва...

Новая драйверная модель Linux

Довольно серьёзные проблемы в работе с PnP устройствами преодолены в Linux к середине 2004-го года. Из не-PnP системы Linux превратился в систему с децентрализованным механизмом PnP: Plug-and-Play реализуется для каждого отдельного устройства. Теперь драйвер, обнаружив "своё" устройство, запрашивает закреплённые за ним ресурсы и, как правило, "соглашается" с таким распределением. Драйвер, используя функции ядра, может попытаться переопределить ресурсы устройства. В этом случае за отсутствие возможных конфликтов отвечает ядро - оно просто не принимает адреса, конфликтующие с другими устройствами.

Драйверная модель требовала изменений. Прежде всего, устранению подлежали следующие недостатки ядра 2.4:

Вообще-то, говорить следовало бы прежде всего об изменениях в архитектуре ядра и появлении новых объектов подсистемы обслуживания устройств (kobjects), но это всё "дела ядерные". Для большинства же пользователей наиболее заметным следствием внедрения новой драйверной модели стало появление sysfs - виртуальной файловой системы, экспортирующей в пространство пользователя информацию ядра о присутствующих в системе устройствах и драйверах. Автором sysfs (ранее именовавшейся как driverfs) является Патрик Мошель (Patrick Mochel). А для больших систем sysfs дорабатывалась Манишем Сони (Maneesh Sony).

Если вернуться к вышеперечисленным недостаткам ядра 2.4, то можно сказать, что первые два устраняются благодаря тому, что информация как об устройствах, так и о загруженных драйверах, представляется в рамках одной файловой системы. Причём в список устройств включаются устройства на всех шинах IBM PC. procfs же разгружается потому, что sysfs монтируется не как подкаталог /proc, а как каталог /sys.

sysfs не может быть модулем, она всегда - часть ядра. Для обеспечения доступа к ней нужно выполнить mount -t sysfs sysfs /sys. Обычно это монтирование выполняется первым же стартовым скриптом.

Существует непосредственная связь между sysfs и объектами инфраструктуры ядра. Всем зарегистрированным системой объектам инфраструктуры (тем самым kobjects) в /sys соответствуют свои каталоги. Форма дерева каталогов, берущего своё начало в /sys, максимально точно соответствует соотношению между объектами. Активно используемые символические ссылки делают это дерево несколько "развесистым", но зато отражают отношения наследования и связи между объектами в максимальной степени.

Если каждое устройство или драйвер - каталог, то файлы этих каталогов - атрибуты этих объектов. Таким образом, sysfs превращает операции ввода/вывода для этих файлов в методы атрибутов объектов ядра. Операции записи/чтения атрибутов kobjects становятся такими же доступными, как операции записи и чтения файлов.

Файлы атрибутов - текстовые. Существует правило, в соответствии с которым одному атрибуту соответствует один файл. Поскольку хранение по одному значению в файле может оказаться неэффективным, то "на будущее" приветствуется выражение атрибутов в форме массива значений одного типа. Нужно сказать, что sysfs родилась не на пустом месте, и если информация о связях объектов ядра - это, действительно, новость, то большую часть атрибутов устройств можно было получить и в "старой" /proc/sys. Файлы атрибутов в /sys, однако, существенно отличаются от файлов procfs, парсинг содержимого которых мог быть довольно сложным.

Структура sysfs выражает соотношения структур данных ядра. Названия подкаталогов /sys говорят сами за себя:

Более детальную информацию об особенностях модели драйверов при желании можно найти в файлах каталога /usr/src/linux/Documentation/driver-model. Обширной, однако, её пока не назовёшь. Да и вообще, нельзя сказать, что информация в /sys предназначена для "простых пользователей". Sysfs используется утилитами, такими, как udev и HAL, обеспечивая их доступом к информации об устройствах и их драйверах. Модифицируя атрибуты устройств и драйверов, udev и HAL теперь могут даже изменять конфигурацию последних.

Что из этого следует?

Из того факта, что существование sysfs, в принципе, может быть проигнорировано "простым" пользователем, совершенно не следует, что новая драйверная модель не отразится на "повседневном" использовании Linux. Окончательный переход на ядро 2.6 влечёт за собой неминуемое изменение архитектуры предлагаемых ментейнерами дистрибутивов. Внешние изменения в системе управления загрузкой модулей (новые расширения файлов модулей, modprobe.conf вместо modules.conf) отнюдь не случайны: они введены для обеспечения возможности временного сосуществования старой и новой моделей.

Вслед за вышеупомянутыми udev и HAL (ближайшими к драйверной модели ядра утилитами), изменения коснутся определителей оборудования, инсталляторов, livecd - всех, кому приходится заниматься конфигурацией системы.

На настоящий момент загрузка драйверов для постоянно присутствующего в системе оборудования обеспечивается либо командами "modprobe module_name" в стартовых скриптах, либо строками "alias string module_name" в modprobe.conf, либо принудительным запуском hotplug (т.наз. coldplug, когда hotplug-события синтезируются в ходе загрузки системы), либо комбинацией всех трёх способов. За загрузку драйверов для подключаемых "на ходу" устройств, отвечает, обычно, hotplug. Тот же hotplug генерирует события для udev, заставляя последний создавать для вновь появившегося устройства файл в /dev. Всё это происходит при участии ядра, разумеется. Интерфейсом же между ядром и утилитами является, как вы, наверное, догадываетесь, sysfs.

Новая драйверная модель и рост вычислительных возможностей IBM PC делают возможным упрощение этой схемы. Судите сами: что такое десяток-другой килобайт модуля драйвера для ЭВМ с гигабайтом памяти? Так почему бы не загрузить модули "с запасом"? udev/HAL останется только создавать в /dev соответствующий файл при подключении устройства, hotplug же, таким образом, становится вообще не нужен.

Ещё один вариант: возложить на udev обработку hotplug-событий. Почему бы не устранить промежуточное звено, тем более, что стиль управления работой udev представляется более гибким? Очевидно, всё определяется назначением целевой системы. Так, домашний компьютер стоит настроить "вручную", поскольку потеря времени на определение оборудования при каждой загрузке раздражает ("ну что ты там ищешь? ничего нового за ночь не выросло"), а livecd должен определять оборудование "с нуля".

Практикум

В качестве практикума предлагаю посредством анализа /sys рассмотреть программную поддержку ide-устройств. Наиболее полно дерево устройств представлено, как уже говорилось, в каталоге /sys/devices. Однако полнота представления может быть и утомительной. Поэтому, оставив в стороне каталоги /sys/block и /sys/class (их содержимое зависит как от наличия собственно устройств, так и от присутствия драйверов, а хотелось бы пройти весь путь исключительно от "железа"), сосредоточимся на /sys/bus.

Переходим в подкаталог ide/. Поскольку управлять контроллерами через sysfs мы не планируем, то интересовать нас будут исключительно каталоги. Набираем: tree -d (ключ -d отфильтровывает файлы) - и вот, что мы имеем:

.
|-- devices
|   |-- 0.0 -> ../../../devices/pci0000:00/0000:00:1f.1/ide0/0.0
|   `-- 0.1 -> ../../../devices/pci0000:00/0000:00:1f.1/ide0/0.1
`-- drivers
    |-- ide-cdrom
    |-- ide-disk
    |   `-- 0.0 -> ../../../../devices/pci0000:00/0000:00:1f.1/ide0/0.0
    `-- ide-scsi
        `-- 0.1 -> ../../../../devices/pci0000:00/0000:00:1f.1/ide0/0.1

"Стрелочки", как вы, вероятно, догадались, обозначают символические ссылки. Анализ прост до примитивности: имеем два ide-устройства и три драйвера. О том, что одно из устройств - диск, а второе - cdrom, также говорить излишне. То, что драйвер ide-disk обслуживает винчестер - естественно, а вот то, что cdrom обслуживается драйвером ide-scsi, уже представляет некоторый интерес. И не потому, что cdrom - ide (cdrecord рекомендует эмулировать ide-cdrom как scsi-устройство), а потому, что, если память не изменяет, ядро 2.6 советует от этой эмуляции уже отказаться. Отметим это обстоятельство (нужно будет попробовать) и отправимся по ссылке ../../../devices/pci0000:00/0000:00:1f.1/ide0/0.0, поскольку в /sys/bus/ide ничего интересного больше нет. "По пути" к этому каталогу советую внимательно "смотреть по сторонам". Так мы узнаем, что контроллер ide "сидит" на шине PCI (о чём и так можно было догадаться) и имеет идентификатор 0000:00:1f.1. Способ формирования идентификатора особого интереса не представляет, да и потребуется он только в том случае, если мы захотим сопоставить наблюдаемые данные с результатом вывода упоминавшейся выше lspci. В каталоге /sys/devices/pci0000:00/0000:00:1f.1/ исследовав символические ссылки, можно узнать, что драйвер, используемый PCI-контроллером, - PIIX_IDE. Спустившись в ide0, увидим, что контроллер этот обслуживает два устройства, младшее из которых (0.0) и является винчестером. Символические ссылки указывают на то, что устройство идентифицируется в системе как /dev/hda, принадлежит шине ide и обслуживается драйвером ide-disk. Последовав по символической ссылке block/, можно получить данные о разметке диска.

Аналогичный поход для cdrom приведёт нас к устройству ide0/0.1, которое окажется scsi (а что ещё можно было ожидать от драйвера ide-scsi?). В соответствии с архитектурой scsi, мы обнаружим host0 (принадлежащий классу scsi_host, разумеется). host0, в свою очередь, содержит target0:0:0, единственное устройство которого (0:0:0:0) и является нашим cdrom. cdrom этот принадлежит шине scsi, идентифицируется в системе как /dev/sr0, обслуживается драйвером sr и входит в класс scsi_generic с именем sg0. Перебрав атрибуты, можно найти и модель, и тип, и производителя привода. Пожалуй, этого достаточно. Прошу прощения, если утомил. Зато, надеюсь, убедил в том, что sysfs предоставляет предостаточно информации для того, чтобы идентифицировать устройство, определить модули, которые обеспечивают его программную поддержку и определить положение в дереве устройств.

И ещё один пример для самых любознательных. На сей раз - более полезный.

Предлагаю попытаться подключить, скажем, USB-IrDA Bridge неизвестного производителя из одной большой восточной страны. USB-IrDA Bridge - это такая штуковина, которая, будучи подключена к usb-разъёму эмулирует ИК-порт. Для связи с мобильным телефоном, например. Ни о каком драйвере производителя и речи быть не может, да и опознавательных знаков всего два: "IrDA" и "Made in China". Итак...

Начать, как и в предыдущем случае, предлагаю с /sys/bus. Интересующий каталог, разумеется - usb/. Поскольку драйвер для моего "подопытного" в системе заведомо отсутствует, то переходим сразу в каталог devices/. USB-контроллеры, коих у меня обнаружилось целых пять, представляют из себя эдакие "дуальные" устройства: с одной стороны это мосты между шинами PCI и USB, а с другой - первое устройство на USB-шине, называемое, обычно, корневым концентратором (root hub). Оба "лица" представлены в каталоге /sys/bus/usb/devices/ в виде символических ссылок. Мосты имеют "собственные" имена (usb1..usb5), а все hub-ы (как, впрочем и остальные устройства, подключаемые к usb-шине) представлены численными идентификаторами вида N-P:С.I, где N - номер моста (он же - номер hub-а), P - номер порта hub-а, вообще-то соответствующий определённому usb-разъёму, а для самого hub-а всегда равный нулю. C - номер конфигурации и I - номер интерфейса. Мосты обнаруживаются драйвером usb, hub-ы - драйвером hub.

Любое подключение usb-устройства приводит к появлению в каталоге новых ссылок. Так, в частности, подключение "подопытного" устройства привело к появлению ссылки 2-1:1.0, что означает: к первому порту второго hub-а подключено устройство, конфигурация - 1, интерфейс - 0. Переход в каталог этого устройства подтвердит, что нужный драйвер не загружен - ссылки driver не существует. Теперь попытаемся применить подход, описанный для сетевых карт: командой modprobe последовательно загружаем модули из каталога /lib/modules/2.6.15/kernel/drivers/net/irda, благо их там пока всего 12. Если повезёт, то после загрузки очередного модуля (в моём случае это оказался stir4200) ссылка driver появится. Более того, в моём случае появилась ссылка net:irda0, что говорит о том, что в системе обнаружено устройство класса net с именем irda0. С чем и поздравляю.

Это, правда, не означает, что вы тут же сможете подключить и мобильный телефон: пока это только основание для уверенности, что связь по ИК-каналу между телефоном и вашей Linux-системой возможна. Однако, это уже совсем другая история...

Выводы

Драйверная модель ядра Linux версии 2.6.х предоставляет, без сомнения, новые возможности для определения оборудования. В некоторых случаях можно говорить о новом подходе к написанию инсталляторов и livecd. Можно также рассчитывать на увеличение количества модулей поддержки оборудования в составе ядра, поскольку одной из целей внедрения модели было упрощение разработки драйверов.

С другой стороны, мне кажется, что в данном случае, вслед за разработчиками ядра, приверженцы Linux выбирают не "windows-way". С новыми возможностями ядра специалист получит дополнительные возможности для оптимизации системы, а "простой пользователь" опять станет заложником ментейнеров дистрибутивов - как скоро (и насколько успешно) они "интегрируют" эти самые возможности.

Короче: Linux продолжает развиваться в направлении предоставления больших возможностей для создания систем различного назначения и сложности, а не как система, ориентированная на конечного пользователя офисного или домашнего компьютера. Последнего, будем надеяться, порадуют своими разработками составители "пользователь-ориентированных" дистрибутивов - ведь расширение возможностей системы означает и большие возможности для создания идеального десктоп-компьютера. Но не будем требовать от них слишком многого: динамично развивающееся ядро в который раз заставляет пересматривать архитектуру системы.

Врезки

PnP ОС - а хорошо бы...

Есть однако функции PnP ОС (ОС с централизованной системой Plag-n-Play), которые хотелось бы увидеть в Linux:

Перспектива роста - налицо. Подождём...

Драйвера от производителей для Linux

Не нужно думать, что производители оборудования не делают драйвера для Linux "из вредности". Просто поставка драйвера в форме двоичного модуля для "константного" ядра XP и предоставление спецификации, достаточной для построения модуля для произвольного ядра Linux - совершенно разные вещи. Существует, однако, компромисс в форме драйвера, часть из которого предоставляется в двоичном виде (и "закрыта", таким образом, от потенциальных конкурентов), а часть - в виде исходных текстов. Причём, именно вторая часть обеспечивает возможность "подгонки" создаваемого модуля к любому ядру. В таком виде предоставляет драйвера NVidia, за что мы ей весьма благодарны. К сожалению, сказать, что её примеру последовали многие, нельзя, к сожалению.

Использование ndis-драйверов

Покупая сетевую карту вы, как правило, оплачиваете и прилагаемый драйвер. Вот только драйвер этот - ndis, т.е. для MS Windows. Иногда, однако, и эти драйвера удаётся успешно использовать в Linux. Лучшие примеры - это DriverLoader и ndiswrapper, успешно применяемые для множества wi-fi адаптеров. Это - второй известный мне случай использования в Linux проприетарного ПО, написанного для MS Windows. Первый - видео кодеки в mplayer.

udev

Основной автор и популяризатор udev - Грег Кроах-Хартман (Greg Kroah-Hartman). По его замыслу udev должен:

Едва ли не самым замечательным было то, что первая реализация udev представляла собой всего 6Kb компилированного кода: можно понять интерес производителей гаджетов...

Как это ни странно, но управление статическими узлами ("нодами") устройств в /dev, основанное на информации ядра - лишь "сопутствующая выгода" использования udev. Разрабатывался-то он прежде всего для того, чтобы избавиться от ответственности за назначение major/minor номеров и получить возможность произвольного именования устройств. Первое достигается тем, что udev, в отличие от devfsd только "наследует" major/minor, отвечает же за них исключительно ядро. А механизм управления именованием получился настолько удачным, что подобным образом стали создавать симлинки и запускать скрипты. В конечном счёте, оказалось, что такой "вооружённый" скриптами udev вполне мог бы обойтись без /sbin/hotplug и такой подход становится в последнее время доминирующим.