Горячее подключение

Предыдущая  Содержание  Следующая V*D*V

Существуют два различных способа рассматривать горячее подключение. Ядро рассматривает горячее подключение как взаимодействие между оборудованием, ядром и драйвером ядра. Пользователи рассматривают горячее подключение как взаимодействие между ядром и пользовательским пространством в рамках программы, называемой /sbin/hotplug. Эта программа вызывается ядром, когда оно хочет уведомить пространство пользователя, что в ядре только что случился какой-то тип события горячего подключения.

Динамические устройства

Наиболее частое использование значение термина "горячее подключение" происходит при обсуждении того факта, что большинство всех компьютерных систем теперь может обрабатывать устройства, которые появляются или исчезают, когда система включена. Это очень отличается от компьютерных систем лишь несколько лет назад, когда программисты знали, что им необходимо сканировать все устройства только во время загрузки и им никогда не приходилось беспокоиться о своих устройствах, исчезающих при отключении питания для всей машины. Теперь, с появлением USB, CardBus PCMCIA, IEEE1394 и PCI контроллеров горячего подключения ядру Linux необходимо иметь способность работать надежно, независимо от того, какое оборудование добавляется или удаляется из системы. Это ложится дополнительным бременем на автора драйвера устройства, поскольку теперь они должны всегда работать с устройством, внезапно вырываемым из подчинения без предварительного уведомления.

 

Каждый тип шины обрабатывает потерю устройства по-разному. Например, когда PCI, CardBus или PCMCIA устройство удаляется из системы, это обычно происходит до того, как драйвер был уведомлен об этом действии через свою функцию remove. Прежде, чем это случается, все чтения из PCI шины возвращают все биты установленными. Это означает, что драйверам необходимо всегда проверять значение данных, которые они прочитали из шины PCI и быть в состоянии должным образом обработать значение 0xff.

 

Пример этого можно увидеть в драйвере drivers/usb/host/ehci-hcd.c, который представляет собой PCI драйвер для платы контроллера USB 2.0 (High-Speed). Он имеет следующий код в своём основном цикле установления связи для обнаружения, что плата контроллера была удалена из системы:

 

result = readl(ptr);

if (result == ~(u32)0) /* карта удалена */

    return -ENODEV;

 

Для драйверов USB, когда устройство, с которым связан USB драйвер удалено из системы, все ожидающие urb-ы, которые были отправлены в устройство, сначала заканчиваются неудачей с ошибкой -ENODEV. Драйвер должен распознать эту ошибку и надлежащим образом очистить весь ожидающий ввод/вывод, если он имеет место.

 

Устройства горячего подключения не ограничены только традиционными устройствами, такими как мышь, клавиатуры и сетевые карты. Есть много систем, которые теперь поддерживают удаление и добавление целиком процессоров и карт памяти. К счастью, ядро Linux должным образом обрабатывает добавление и удаление таких основных "системных" устройств, так что отдельные драйверы устройств не должны обращать внимание на эти вещи.

Утилита /sbin/hotplug

Как упоминалось ранее в этой главе, когда устройство добавляется или удаляется из системы, генерируется "событие горячего подключения". Это означает, что ядро вызывает программу пользовательского пространства /sbin/hotplug. Эта программа, как правило, очень небольшой скрипт bash, который просто передаёт выполнение списку других программ, которые находятся в дереве каталога /etc/hotplug.d/. Для большинства дистрибутивов Linux этот скрипт выглядит следующим образом:

 

DIR="/etc/hotplug.d"

for I in "${DIR}/$1/"*.hotplug "${DIR}/"default/*.hotplug ; do

    if [ -f $I ]; then

        test -x $I && $I $1 ;

    fi

done

exit 1

 

Другими словами, скрипт ищет все программы имеющие суффикс .hotplug, которые могут быть заинтересованы в этом событии и вызывает их, передавая им ряд различных переменных окружения, которые были установлены ядром. Более подробная информация о работе скрипта /sbin/hotplug можно найти в комментариях к программе и на странице руководства hotplug(8).

 

Как упоминалось ранее, /sbin/hotplug вызывается при создании или уничтожении kobject-а. Программа горячего подключения вызывается с одним аргументом командной строки, представляющим название для данного события. Основное ядро и определённая подсистема также участвуют в установке набора переменных окружения (смотрите ниже) с информацией о том, что только что произошло. Эти переменные используются в программах горячего подключения, чтобы определить, что только что произошло в ядре, и есть ли какое-то специальное действие, которое должно иметь место.

 

Аргумент командной строки, переданный в /sbin/hotplug, является именем, связанным с этим событием горячего подключения, как определено kset-ом, назначенным для kobject. Это имя может быть установлено вызовов функции name, которая является частью структуры hotplug_ops kset-а, описанной ранее в этой главе; если эта функция отсутствует или никогда не вызывалась, используется название самого kset-а.

 

Переменными окружения по умолчанию, которые всегда устанавливаются для программы /sbin/hotplug, являются:

 

ACTION

Строка add (добавить) или remove (удалить), в зависимости от того, был ли данный объект только что создан или уничтожен.

 

DEVPATH

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

 

SEQNUM

Порядковый номер для этого события горячего подключения. Порядковый номер представляет собой 64-х разрядное число, которое увеличивается с каждым генерируемым событием горячего подключения. Это позволяет пользовательскому пространству отсортировать события горячего подключения в том порядке, в котором их генерирует ядро, так как для программ пространства пользователя возможна работа не по порядку.

 

SUBSYSTEM

Та же строка, передаваемая в качестве аргумента командной строки, как описано выше.

 

Ряд различных шинных подсистем для вызова /sbin/hotplug добавляют свои собственные переменные окружения, когда связанное с шиной устройство было добавлено или удалено из системы. Они делают это в своём обратном вызове горячего подключения, указанном в struct kset_hotplug_ops, назначенной этой шине (как описано в разделе "Операции горячего подключения"). Это позволяет пользовательскому пространству иметь возможность автоматической загрузки необходимых модулей, которые могут быть необходимы для управления устройством, которое было обнаружено на шине. Вот список разных типов шин и переменных окружения, которые они добавляют для вызова /sbin/hotplug.

IEEE1394 (FireWire)

Все устройства на шине IEEE1394, также известной как FireWire, имеют параметр имени для /sbin/hotplug и переменная окружения SUBSYSTEM устанавливается в значение ieee1394. Подсистема Ieee1394 также всегда добавляет следующие четыре переменные окружения:

 

VENDOR_ID

24-х разрядный идентификатор поставщика для устройства IEEE1394.

MODEL_ID

24-х разрядный идентификатор модели для устройства IEEE1394.

GUID

64-х разрядный GUID для этого устройства.

SPECIFIER_ID

24-х разрядное значение, определяющее владельца спецификации протокола для этого устройства

VERSION

Значение, которое определяет версию спецификации протокола для этого устройства.

Сеть

Все сетевые устройства создают сообщение горячего подключения, когда устройство зарегистрировано или разрегистрировано в ядре. Вызов /sbin/hotplug имеет параметр имени и переменная окружения SUBSYSTEM устанавливается в значение net и добавляет только следующую переменную окружения:

 

INTERFACE

Имя интерфейса, который был зарегистрирован или разрегистрирован из ядра. Примерами его являются lo и eth0.

PCI

Любые устройства на шине PCI имеют параметр имени и переменная окружения SUBSYSTEM устанавливается в значение pci. Подсистема PCI также всегда добавляет следующие четыре переменные окружения:

 

PCI_CLASS

Номер PCI класса для данного устройства, в шестнадцатеричном виде.

PCI_ID

Идентификаторы поставщика и устройства PCI для данного устройства, в шестнадцатеричном виде, объединенные в формате vendor:device.

PCI_SUBSYS_ID

Идентификаторы поставщика и подсистемы PCI, объединенные в формате subsys_vendor:subsys_device.

PCI_SLOT_NAME

"Имя" слота PCI, которое даётся устройству ядром в формате domain:bus:slot:function. Примером может быть 0000:00:0d.0.

Ввод

Для всех устройств ввода (мышь, клавиатуры, джойстики и так далее), сообщение горячего подключения генерируется, когда устройство добавляется и удаляется из ядра. Параметр /sbin/hotplug и переменная окружения SUBSYSTEM устанавливаются в значение input. Подсистема ввода также всегда добавляет следующие переменные окружения:

 

PRODUCT

Многозначная строка, перечисляющая значения в шестнадцатеричном виде, без ведущих нулей, в формате bustype:vendor:product:version.

 

Следующие переменные окружения могут присутствовать, если устройство их поддерживает:

 

NAME

Название устройства ввода, как задано устройством.

PHYS

Физический адрес устройства, который подсистема ввода дала этому устройству. Он должен быть стабильным, зависящим от местонахождения шины, на которую было подключено устройство.

EV

KEY

REL

ABS

MSC

LED

SND

FF

Все они происходят из дескриптора устройства ввода и устанавливаются в соответствующие значения, если данное устройство ввода его поддерживает.

USB

Любые устройства на шине USB имеют параметр имени и переменная окружения SUBSYSTEM устанавливается в значение usb. Подсистема USB также всегда добавляет следующие переменные окружения:

 

PRODUCT

Строка в формате idVendor/idProduct/bcdDevice, которая определяет эти зависимые от устройства USB поля.

TYPE

Строка в формате bDeviceClass/bDeviceSubClass/bDeviceProtocol, которая определяет эти зависимые от устройства USB поля.

 

Если поле bDeviceClass установлено в 0, также устанавливается следующая переменная окружения:

 

INTERFACE

Строка в формате bInterfaceClass/bInterfaceSubClass/bInterfaceProtocol, которая определяет эти зависимые от устройства USB поля.

 

Если выбрана опция сборки ядра CONFIG_USB_DEVICEFS, который выбирает, что файловая система usbfs будет собрана в ядре, также устанавливается следующая переменная окружения:

 

DEVICE

Строка, которая показывает, где находится устройство в файловой системе usbfs. Эта строка имеет формат /proc/bus/usb/USB_BUS_NUMBER/ SB_DEVICE_NUMBER, в котором USB_BUS_NUMBER является трёхзначным номером шины USB, к которой подключено устройство, а USB_DEVICE_NUMBER является трёхзначным номером, который был назначен ядром для этого USB устройства.

 

SCSI

Все SCSI устройства создают событие горячего подключения, когда SCSI устройство создано или удалено из ядра. Вызов /sbin/hotplug имеет параметр имени и переменная окружения SUBSYSTEM установлена в значение scsi для каждого SCSI устройства, которое добавляется или удаляется из системы. Никакие дополнительные переменные окружения не добавляться системой SCSI, но она упоминается здесь потому, что существует специальный SCSI скрипт в пространстве пользователя, который может определить, что SCSI драйверы (дисковода, ленточного накопителя, обычный и т.д.) должны быть загружены для указанного устройства SCSI.

Установочные станции ноутбуков

Если поддерживающая Plug-and-Play установочная (док) станция ноутбука добавлена или удалена из работающей системы Linux (путём включения ноутбука в станцию, или его удаления), создаётся событие горячего подключения. Вызов /sbin/hotplug имеет параметр имени и переменная окружения SUBSYSTEM установлена в значение dock. Никакие другие переменные окружения не установлены.

S/390 и zSeries

На архитектуре S/390, архитектура канальной шины поддерживает широкий спектр оборудования, каждое из которых генерирует события /sbin/hotplug, когда они добавляются или удаляются из виртуальной системы Linux. Все эти устройства имеют для /sbin/hotplug параметр имени и переменная окружения SUBSYSTEM установлена в значение dasd. Никакие другие переменные окружения не установлены.

Использование /sbin/hotplug

Теперь, когда ядро Linux вызывает /sbin/hotplug для каждого добавляемого или удаляемого из ядра устройства, чтобы воспользоваться этим, в пользовательском пространстве были созданы ряд очень полезных инструментов. Двумя из наиболее популярных инструментов являются скрипты Linux горячего подключения и udev.

Скрипты горячего подключения Linux

Скрипты горячего подключения Linux начались в качестве самого первого пользователя вызова /sbin/hotplug. Эти скрипты смотрят на разные переменные окружения, которые ядро устанавливает для описание устройства, которое было только что обнаружено и затем пытаются найти модуль ядра, который соответствует этому устройству.

 

Как уже говорилось ранее, когда драйвер использует макрос MODULE_DEVICE_TABLE, программа, depmod, принимает эту информацию и создаёт файлы, находящиеся в /lib/module/KERNEL_VERSION/modules.*map. Знак * является различием, в зависимости от типа шины, которую поддерживает драйвер. В настоящее время файлы модульной карты создаются для драйверов, которые работают с устройствами с поддержкой подсистем PCI, USB, IEEE1394, INPUT, ISAPNP и CCW.

 

Скрипты горячего подключения используют эти текстовые файлы модульной карты для определения модуля, чтобы попытаться загрузить его для поддержки устройства, которое было недавно обнаружено ядро. Они загружают все модули и не останавливаться на первом соответствии, с тем, чтобы позволить ядру выбрать, какой модуль лучше подходит. Эти скрипты не выгружают все модули при удалении устройств. Если бы они попытались это сделать, они могли бы случайно выключить устройства, которые также управляются тем драйвером устройства, который был удалён.

 

Обратите внимание, теперь, когда программа modprobe может читать информацию MODULE_DEVICE_TABLE непосредственно из модулей без необходимости файлов модульной карты, скрипты горячего подключения могут быть сокращены до небольшой обёртки вокруг программы modprobe.

udev

Одной из основных причин для создания единой модели драйвера в ядре было позволить пользовательскому пространству управлять деревом /dev в динамическом стиле. Раньше это было сделано в пользовательском пространстве реализацией devfs, но эта кодовая база постепенно сгнила из-за отсутствия активного сопровождающего и некоторых неисправимых базовых ошибок. Несколько разработчиков ядра поняли, что если бы всю информацию устройства экспортировать в пространство пользователя, оно могло бы выполнять всё необходимое управление деревом /dev.

 

devfs имеет в своём дизайне некоторые весьма существенные недостатки. Она требует от каждого драйвера устройства быть измененным для её поддержки и она требует от драйвера устройства указать имя и местоположение в дереве /dev, где он помещён. Она также не надлежащим образом обрабатывает динамические старшие и младшие номера, заставляя политику именования устройств принадлежать ядру, а не пространству пользователя. Разработчики ядра Linux действительно ненавидят иметь политику в ядре и так как политика именования devfs не следует спецификации Linux Standard Base, это действительно их беспокоит.

 

С тех пор, как ядро Linux начало устанавливаться на огромных серверах, многие пользователи столкнулись с проблемой, как управлять очень большим количеством устройств. Массивы дисковых накопителей из более 10.000 уникальных устройств представляют очень сложную задачу обеспечения того, чтобы каждый диск всегда был проименован тем же точным именем, где бы он ни был помещен в дисковом массиве или когда он был обнаружен ядром. Эта та же проблема, от которой страдают пользователи настольных компьютеров, пытающиеся подключить два USB принтера к своей системе и затем понимающие, что они не имели возможности обеспечить, чтобы принтер, известный как /dev/lpt0, не был бы изменён и отнесён к другому принтеру в случае перезагрузки системы.

 

Таким образом, был создан udev. Он опирается на всю информацию устройства, экспортируемую в пользовательское пространство через sysfs и на уведомление через /sbin/hotplug, что устройство было добавлено или удалено. Политические решения, такие, как какое имя дать устройству, могут быть указаны в пространстве пользователя, вне ядра. Это гарантирует, что политика именования удалена из ядра и позволяет большую степень гибкости при именовании каждого устройства.

 

Для получения дополнительной информации по использованию udev и как его настроить, пожалуйста, смотрите документацию, которая поставляется включённой в пакет udev в вашем дистрибутиве.

 

Всё, что драйверу устройства необходимо сделать, чтобы udev правильно с ним работал, является обеспечение того, чтобы любые старшие и младшие номера, присвоенные устройству, управляемому драйвером, экспортировались в пользовательское пространство через sysfs. Для любого драйвера, который использует подсистему для присвоения ему старшего и младшего номера, это уже сделано подсистемой и драйвер не должен делать никакой работы. Примерами подсистем, которые делают это, являются подсистемы: tty, misc, usb, input, scsi, block, i2c, network и frame buffer. Если ваш драйвер самостоятельно обрабатывает получение старшего и младшего номера через вызов функции cdev_init или устаревшей функции register_chrdev, драйвер должен быть изменён, чтобы udev работал с ним должным образом.

 

udev ищет в дереве /class/ в sysfs файл с именем dev, чтобы определить, какой старший и младший номер присвоен данному устройству, когда оно вызывается ядром через интерфейс /sbin/hotplug. Драйверу устройства просто необходимо создать такой файл для каждого устройства, которым он управляет. Как правило, интерфейс class_simple - самый простой способ это сделать.

 

Как уже упоминалось в разделе "Интерфейс class_simple", первым шагом в использовании интерфейса class_simple является создание struct class_simple с помощью вызова функции class_simple_create:

 

static struct class_simple *foo_class;

...

foo_class = class_simple_create(THIS_MODULE, "foo");

if (IS_ERR(foo_class)) {

    printk(KERN_ERR "Error creating foo class.\n");

    goto error;

}

 

Этот код создаёт каталог в sysfs в /sys/class/foo.

 

Всякий раз, когда драйвер находит новое устройство и вы присваиваете ему младший номер, как описано в Главе 3, драйвер должен вызывать функцию class_simple_device_add:

 

class_simple_device_add(foo_class, MKDEV(FOO_MAJOR, minor), NULL, "foo%d", minor);

 

Этот код вызывает создание в /sys/class/foo поддиректории, названной fooN, где N - младший номер для этого устройства. В этом каталоге создаётся один файл, dev, и это именно то, что необходимо udev, чтобы создать узел устройства для вашего устройства. Когда ваш драйвер освобождается от устройства и вы отказываетесь от младшего номера, который был за ним закреплён, для удаления записи в sysfs для этого устройства необходим вызов class_simple_device_remove:

 

class_simple_device_remove(MKDEV(FOO_MAJOR, minor));

 

Позже, когда весь ваш драйвер выключается, для удаления класса, который вы первоначально создали вызовом class_simple_create, является необходимым вызов class_simple_destroy :

 

class_simple_destroy(foo_class);

 

Файл dev, который создаётся вызовом class_simple_device_add, состоит из старшего и младшего номера, разделенных символом :. Если ваш драйвер не хочет использовать интерфейс class_simple, потому что вы хотите предоставить для подсистемы другие файлы внутри каталога класса, используйте функцию print_dev_t для правильного формата старшего и младшего номера для каждого устройства.

Предыдущая  Содержание  Следующая