Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 3.2.Мажорные и минорные числа
| Английский оригинал | Перевод на русский | |
|---|---|---|
Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
3.2 Старший и младший номера victor8632 12.11.10 в 15:50 3.2 Основной и дополнительный номера vorba 03.04.12 в 19:45 |
|
|
Символьные устройства доступны через имена в файловой системе. Этими именами называют специальные файлы или файлы устройств или просто узлы дерева файловой системы; обычно они расположены в каталоге /dev. Специальные файлы символьных устройств идентифицируются символом "c" в первой колонке результата выполнения команды ls -l. Блочные устройства также расположены в каталоге /dev, но они идентифицируются символом "b". Основное внимание в этой главе уделяется символьным устройствам, но большая часть приведенной ниже информации также относится к блочным устройствам. victor8632 12.11.10 в 15:51 |
||
|
Если вы ввели команду ls -l, в результате ее выполнения в выведенной строке для файла устройства перед датой последнего изменения вы увидите два числа (разделенные запятой) на том месте, где для обычного файла выводится его размер. В показанном ниже листинге приведены строки для нескольких устройств типичной системы. Их старшие номера 1, 4, 7 и 10, в то время как младшие номера - 1, 3, 5, 64, 65 и 129. victor8632 17.11.10 в 12:26 Если вы ввели команду ls -l, в результате ее выполнения в выведенной строке для файла устройства перед датой последнего изменения вы увидите два числа (разделенные запятой) на том месте, где для обычного файла выводится его размер. Эти числа являются основным и дополнительным номерами данного устройства. vorba 03.04.12 в 19:50 |
||
|
crw-rw-rw- 1 root root 1, 3 Apr 11 2002 null victor8632 17.11.10 в 12:30 |
||
|
Младший номер используется ядром, чтобы точно определить, о каком устройстве идет речь. В зависимости от того, как написан ваш драйвер (как мы увидим дальше), вы можете или получить прямую ссылку на ваше устройство через ядро, или использовать младший номер как значение индекса в локальном массиве устройств. В любом случае само ядро почти ничего не знает о младший номерах за исключением того факта, что они ссылаются на устройства, которыми управляет ваш драйвер. victor8632 17.11.10 в 12:54 |
||
|
3.2.1 Внутреннее представление номеров устройств victor8632 17.11.10 в 13:04 |
||
|
В ядре для хранения номера устройства, как старшей так и младшей его части, используется тип dev_t (определен в types.h>). Начиная с версии ядра 2.6.0, dev_t представляет из себя 32-битную величину, где 12 битов занимает старший номер, а 20 - младший номер. Конечно, ваш исходный код не должен делать предположений, о внутренней организации номеров устройств, наоборот, должен использовать макросы из файла kdev_t.h>. Для получения старшей или младшей части dev_t, используйте: victor8632 17.11.10 в 13:15 |
||
|
MAJOR(dev_t dev); hardorsoft 28.10.10 в 11:43 |
||
|
В противном случае, если у вас есть старший и младший номер, а вам нужно получить dev_t, используйте: victor8632 17.11.10 в 13:24 |
||
|
Заметим, что ядро версии 2.6, может вместить большое количество устройств, в то время как предыдущие версии ядра были ограничены 255-ю старшими и 255-ю младшими номерами. Предполагается, что такого широкого диапазона будет достаточно в течение довольно продолжительного времени, но компьютерная область достаточно усеяна ошибочными предположениями. Таким образом, вы должны ожидать, что формат dev_t может снова измениться в будущем; однако, если вы внимательно пишете свои драйверы, эти изменения не будут проблемой. hardorsoft 19.11.10 в 07:28 |
||
|
Получение и освобождение номеров устройств hardorsoft 19.11.10 в 07:28 3.2.2 Получение и освобождение номеров устройств victor8632 26.11.10 в 13:42 |
||
|
Одним из первых шагов, который необходимо сделать вашему драйверу при установке символьного устройства, является получение одного или нескольких номеров устройств для работы с ними. Необходимой функцией для выполнения этой задачи является register_chrdev_region, которая объявлена в <linux></linux>fs.h>: hardorsoft 19.11.10 в 07:28 |
||
|
int register_chrdev_region(dev_t first, unsigned int count, char *name); hardorsoft 28.10.10 в 11:43 |
||
|
Здесь first - это начало диапазона номеров устройств, который вы хотели бы выделить. Младшее число first часто 0, но не существует никаких требований на этот счёт. count - запрашиваемое общее число смежных номеров устройств. Заметим, что если число count большое, запрашиваемый диапазон может перекинуться на следующей старший номер, но всё будет работать правильно, если запрашиваемый диапазон чисел доступен. Наконец, name - это имя устройства, которое должно быть связано с этим диапазоном чисел; оно будет отображаться в /proc/devices и sysfs. hardorsoft 19.11.10 в 07:28 Здесь first - это начальный номер устройства из диапазона, который вы хотели бы занять. Часть младшего номера в значении fist обычно равна 0, однако по этому поводу нет никаких специальных требований. count - общее запрашиваемое количество номеров устройств. Отметим, что если значение count велико, то запрашиваемый вами диапазон может перекинуться на соседний старший номер, однако все будет работать правильно, если только запрашиваемый диапазон номеров доступен. И наконец, name - это имя устройства, которе будет связано с указанным диапазоном номеров; оно появится в /proc/devices и sysfs. victor8632 26.11.10 в 13:45 |
||
|
Как и в большинстве функций ядра, возвращаемое значениеregister_chrdev_region будет 0, если выделение была успешно выполнено. В случае ошибки будет возвращён отрицательный код ошибки и вы не получите доступ к запрашиваемому региону. hardorsoft 19.11.10 в 07:29 Как и в большинстве функций ядра, функция register_chrdev_region возвращает 0, если выделение диапазона адресов произошло успешно. В случае ошибки будет возвращен отрицательный код ошибки и вы не получите доступа к запрашиваемому дипапзону номеров. victor8632 26.11.10 в 14:06 |
||
|
register_chrdev_region работает хорошо, если вы знаете заранее, какие именно номера устройств вы хотите. Однако, часто вы не будете знать, какие основные номера устройств будут использоваться; есть постоянные усилия в рамках сообщества разработчиков ядра Linux перейти к использованию динамически выделяемых номерам устройства. Ядро будет счастливо выделить старший номер для вас "на лету", но вы должны запрашивать это распределение, используя другую функцию: hardorsoft 19.11.10 в 07:30 register_chrdev_region хорошо работает, если вы заранее знаете, какие именно номера устройств вы хотите использовать. Зачастую, однако, вы не будете знать, какие старшие номера будет использовать ваше устройство; в рамках сообщества разрабочтиков ядра Linuх предпринимаются постоянные усилия, чтобы перейти к использованию динамически выделяемых номеров устройств. Ядро будет счастливо выделить для вас старший номер на лету, но при этом вы должны использовать другую функцию: victor8632 09.12.10 в 12:48 |
||
|
nt alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); hardorsoft 19.11.10 в 07:30 |
||
|
В этой функции dev является только выходным значением, которое при успешном завершении содержит первый номер выделенного диапазона. firstminor должен иметь значение первого младшего номера для использования; как правило, 0. Параметры count и name аналогичны request_chrdev_region. hardorsoft 19.11.10 в 07:30 В этой функции dev является параметром только для вывода значения, который при успешном завершении функции будет содержать первый номер устройства из выделенного диапазона. firstminor будет равен значению перовго младшего номера из используемых, обыно это 0. Количество и имена остальных параметров функции эквивалентны парметрам функции request_chrdev_region. victor8632 09.12.10 в 13:00 |
||
|
Независимо от того, как вы назначили номера устройств, вы должны освободить их, когда они больше не используются. Номера устройств освобождаются функцией: hardorsoft 19.11.10 в 07:30 |
||
|
void unregister_chrdev_region(dev_t first, unsigned int count); hardorsoft 28.10.10 в 11:43 |
||
|
Обычное место для вызова unregister_chrdev_region будет в функции очистки вашего модуля. hardorsoft 19.11.10 в 07:31 |
||
|
Вышеприведённые функции выделяют номера устройств для использования вашим драйвером, но ничего не говорят ядру, что вы в действительности будете делать с этими номерами. Перед тем, как какая-либо программа из пространства пользователя сможет получить доступ к одному из этих номеров устройств, вашему драйверу необходимо подключить их к своим внутренним функциям, которые осуществляют операции устройства. Мы скоро будем описывать, как это осуществляется, но сначала необходимо сделать несколько необходимых отступлений. hardorsoft 19.11.10 в 07:31 |
||
|
Динамическое выделение старших номеров hardorsoft 19.11.10 в 07:31 |
||
|
Некоторые старшие номера устройств для наиболее распространённых устройств выделены статически. Перечень этих устройств можно найти в Documentation/devices.txt в дереве исходных текстов ядра. Однако шансы, что статический номер уже был назначен перед использованием нового драйвера, малы и новые номера не назначаются (видимо, не будет назначен, если всё-таки совпадёт?). Так что, автор драйвера, у вас есть выбор: вы можете просто выбрать номер, который кажется неиспользованным, или вы можете определить старшие номера динамическим способом. Выбор номера может работать; пока вы единственный пользователь вашего драйвера; если ваш драйвер распространяется более широко, случайно выбранный старший номер будет приводить к конфликтам и неприятностям. hardorsoft 19.11.10 в 07:31 Некоторые старшие номера выделены статически для наиболее распространенных общих устройств. Перечень этих устройств можно найти в Documentation/devices.txt в дереве исходных текстов ядра. Шансы того, что статический номер, выбранный вами для вашего нового драйвера будет занят кем-то другим малы, однако новые номера обычно не назначают. Так что, уважаемый автор нового драйвера, у вас есть выбор: вы можете просто выбрать статический номер, который кажется вам незанятым или же можете выделить старшие номера динамически. Случайно выбранный статический номер может нормально работать пока вы остаетесь единственным пользователем вашего драйвера, при более широком распространении драйвера случайно выбранный старший номер будет приводить к конфликтам и ошибкам. victor8632 09.12.10 в 13:06 |
||
|
Таким образом, для новых драйверов мы настоятельно рекомендуем использовать динамическое выделение для получения старшего номера устройства, а не выбирать номер случайно из числа тех, которые в настоящее время свободны. Иными словами, ваш драйвер почти наверняка должен использовать alloc_chrdev_region вместо register_chrdev_region. hardorsoft 19.11.10 в 07:32 |
||
|
Недостатком динамического назначения является то, что вы не сможете создать узлы устройств заранее, так как старший номер, выделяемый для вашего модуля будет меняться. Вряд ли это проблема для нормального использования драйвера, потому что после того, как номер был назначен, вы можете прочитать его из /proc/devices. hardorsoft 19.11.10 в 07:32 |
||
|
(* Еще большая информация об устройстве обычно может быть получена из sysfs, обычно смонтированной в /sys в системах, базирующихся на ядре 2.6. Обучить scull экспортировать информацию через sysfs выходит за рамки данной главы, однако, мы вернёмся к этой теме в Главе 14.) hardorsoft 19.11.10 в 07:33 [1] Обычно из sysfs может быть получена еще большая информация об устройстве, в системах, базирущися на версиях ядра 2.6, sysfs обычно монтируется на /sys. Использование scull для получения информации через sysfs выходит за рамки этой главы, однако, мы вернемся к этой теме в главе 14. victor8632 09.12.10 в 13:42 |
||
|
Следовательно, чтобы загрузить драйвер, использующий динамический старший номер, вызов insmod может быть заменён простым скриптом, который после вызова insmod читает /proc/devices в целью создания специального файла (ов). hardorsoft 19.11.10 в 07:33 |
||
|
Типичный файл /proc/devices выглядит следующим образом: hardorsoft 19.11.10 в 07:33 |
||
|
Символьные устройства: hardorsoft 19.11.10 в 07:33 |
||
|
1 mem hardorsoft 19.11.10 в 07:34 |
||
|
Следующий скрипт, scull_load, является частью дистрибутива scull. Пользователь драйвера, который распространяется в виде модуля, может вызывать такой сценарий из системного файла rc.local или запускать его вручную каждый раз, когда модуль становится необходим. hardorsoft 19.11.10 в 07:34 |
||
|
#!/bin/sh hardorsoft 19.11.10 в 07:34 |
||
|
Скрипт может быть адаптирован для других драйверов путём переопределения переменных и корректировке строчек с mknod. Скрипт просто показывает создание четырёх устройств, потому что в исходниках scull по умолчанию используется число четыре. hardorsoft 19.11.10 в 07:35 |
||
|
Последние несколько строчек скрипта могут показаться неясными: зачем менять группы и режим работы устройства? Причина в том, что скрипт должен запускаться суперпользователем (superuser), так что вновь созданные специальные файлы принадлежат root-у. По умолчанию биты разрешения установлены так, что только root имеет доступ на запись, остальные могут получать доступ на чтение. Как правило, узлы устройств требуют иной политики доступа, так что тем или иным путём права доступа должны быть изменены. Установки в нашем скрипте предоставляют доступ группе пользователей, но ваши потребности могут отличаться. В разделе "Контроль доступа к файлу устройства" в Главе 6, код sculluid демонстрирует, как драйвер может реализовать свой собственный вид авторизации для доступа к устройству. hardorsoft 19.11.10 в 07:35 |
||
|
Для очистки каталога /dev и удаления модуля так же доступен скрипт scull_unload. hardorsoft 19.11.10 в 07:35 |
||
|
В качестве альтернативы использования пары скриптов для загрузки и выгрузки вы могли бы написать скрипт инициализации, готовый для размещения в каталоге вашего дистрибутива, используемого для таких скриптов. hardorsoft 19.11.10 в 07:36 |
||
|
(* Linux Standard Base указывает, что скрипты инициализации должны быть размещены в /etc/init.d, но некоторые дистрибутивы всё ещё размещают их в другом месте. Кроме того, если ваш скрипт будет работать во время загрузки, вам необходимо сделать ссылку на него из соответствующей директории уровня загрузки (run-level) (то есть .../rc3.d). hardorsoft 19.11.10 в 07:36 |
||
|
Если неоднократное создание и уничтожение узлов /dev выглядит как излишество, есть полезный обходной путь. Если вы загружаете и выгружаете только один драйвер, после первого создания специальных файлов вашим скриптом вы можете просто использовать rmmod и insmod: динамические номера не случайны (не рандомизированы) (* Хотя некоторые разработчики ядра угрожали сделать это в будущем.) и вы можете рассчитывать, что такие же номера выбираются каждый раз, если вы не загружаете какие-то другие (динамические) модули. Избегание больших скриптов полезно в процессе разработки. Но очевидно, что этот трюк не масштабируется более чем на один драйвер за раз. hardorsoft 19.11.10 в 07:42 |
||
|
Хотя определенные разработчики ядра хотели сделать это в будущем. hardorsoft 19.11.10 в 09:11 |
||
|
Лучшим способом присвоения старших номеров, на наш взгляд, является использование по умолчанию динамического присвоения, оставив себя возможность указать старший номер во время загрузки или даже во время компиляции. scull выполняет работу таким образом; он использует глобальную переменную scull_major, чтобы сохранить выбранный номер (есть также scull_minor для младшего номера). Переменная инициализируется SCULL_MAJOR, определенным в scull.h. Значение по умолчанию SCULL_MAJOR в распространяемых исходниках равно 0, что означает "использовать динамическое определение". Пользователь может принять значение по умолчанию или выбрать специфичный старший номер либо изменив макрос перед компиляцией, либо указав значение для scull_major в командной строки insmod. Наконец, с помощью скрипта scull_load пользователь может передать аргументы insmod в командной строке scull_load. hardorsoft 19.11.10 в 07:44 |
||
|
Инициализационный скрипт scull.init не принимает параметры драйвера в командной строке, но он поддерживает конфигурационный файл, потому что разработан для автоматического использования во время запуска и выключения. hardorsoft 19.11.10 в 07:44 |
||
|
if (scull_major) { hardorsoft 28.10.10 в 11:43 |
||
|
Почти все примеры драйверов, используемых в этой книге, для назначения своего старшего номера используют аналогичный код. hardorsoft 19.11.10 в 07:51 Почти все примеры драйверов, используемых в этой книге, для назначения своего старшего номера используют аналогичный код. hardorsoft 19.11.10 в 07:51 |
