Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Драйверы устройств в Linux

Часть 4: Символьные драйверы Linux

Оригинал: "Device Drivers, Part 4: Linux Character Drivers"
Автор: Anil Kumar Pugalia
Дата публикации: February 1, 2011
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.

В этой статье, которая является частью серии статей о драйверах устройств в Linux, речь идет о различных понятиях, относящихся к символьным драйверам и их реализаций.

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

Поэтому для того, чтобы самостоятельно начать писать символьный драйвер, она взяла код первого драйвера и вытащила различные справочники. Она также скачала из интернета книгу "Драйверы устройств Linux" Джонатана Корбета, Алессандро Рубини и Грега Кроа-Хартмана (Linux Device Drivers Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman). Вот итог того, что она узнала.

Все о символьных драйверах

Мы уже знаем, что такое драйверы и для чего они нам нужны. Что же такого особенного в символьных драйверах? Если мы пишем драйверы для байт-ориентированных операций (или, на жаргоне языка C, символьно-ориентированных операций), то мы называем их символьными драйверами. Поскольку большинство устройств является байт-ориентированными, то большинство драйверов устройств являются символьными драйверами.

Возьмем, к примеру, драйверы последовательного порта, аудио драйверы, видео драйверы, драйверы для фотокамеры и драйверы базового ввода/вывода. На самом деле, все драйверы устройств, которые не являются ни драйверами устройств хранения данных, ни драйверами сетевых устройств, будут символьными драйверами некоторого вида. Давайте рассмотрим общие особенности этих символных драйверов и познакомимся с тем, как Светлана написала один из них.

Подключение устройств

Рис.1: Общий взгляд на символьный драйвер

Как показано на рис.1, для любого приложения пользовательского пространства, предназначенного для работы с байт-ориентированным устройством (в пространстве аппаратных средств), следует использовать соответствующий драйвер символьного устройства (в пространстве ядра). Использование символьных драйверов осуществляется через соответствующие файлы символьных устройств, которые прикомпонованы к виртуальной файловой системе (VFS). Это означает, что приложение выполняет обычные файловые операции с файлом символьного устройства. Эти операции будут перетранслированы виртуальной файловой системой VFS в соответствующие функции в прикомпонованном драйвере символьного устройства. Затем для того, чтобы получить нужные результаты, с помощью этих функций осуществляется окончательный низкоуровневый доступ к реальному устройству.

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

В этом полном подключении из приложения к устройству участвуют следующие четыре основных компонента:

  1. Приложение
  2. Файл символьного устройства
  3. Драйвер символьного устройства
  4. Символьное устройство

Интересно, что все они могут существовать независимо от системы и без всякого обязательного наличия других компонентов. Само существование их в системе не означает, что для того, чтобы сформировать полное соединение, они должны быть скомпонованы вместе. Правильнее сказать, что они должны явно подключаться друг с другом. Приложение подключается к файлу устройства при помощи системного вызова open, открывающего файл устройства.

Файлы устройств подключаются к драйверу устройства с помощью специального механизма регистрации, что осуществляется драйвером. Драйвер связывается с устройством с помощью специальных низкоуровневых операций, характерных для конкретного устройства. Таким образом, мы формируем полное соединение. При этом, обратите внимание, что файл символьного устройства не является реальным устройством, это просто специальная методика (place-holder) подключения реального устройства.

Старший и младший номера файлов устройств

При подключении приложения к файлу устройства используется имя файла устройства. Но при подключении файла устройства к драйверу устройства используется номер файла устройства, а не имя файла. В результате приложение пользовательского пространства может использовать для файла устройства любое имя, а в пространстве ядра для связи между файлом устройства и драйвером устройства можно использовать тривиальный механизм индексации. Таким номером файла обычно является пара <major, minor>, т. е. или старший и младший номера файла устройства.

Ранее (вплоть до ядра 2.4) каждый старший номер использовался в качестве указания на отдельный драйвер, а младший номер использовался для указания на конкретное подмножество функциональных возможностей драйвера. В ядре 2.6 такое использование номеров не является обязательным; с одним и тем же старшим номером может быть несколько драйверов, но, очевидно, с различными диапазонами младших номеров.

Однако, такое использование больше характерно для незарезервированных старших номеров, а стандартные старшие номера обычно резервируются для вполне определенных конкретных драйверов. Например, 4 — для последовательных интерфейсов, 13 - для мышей, 14 — для аудио-устройств и так далее. С помощью следующей команды можно будет выдать список файлов различных символьных устройств, имеющихся в вашей системе:

$ ls -l /dev/ | grep "^c"

Использование чисел <major, minor> в ядре 2.6

Тип (определен в заголовке ядра linux/types.h):

  • dev_t - содержит старший и младший номера

Макрос (определен в заголовке ядра linux/kdev_t.h):

  • MAJOR(dev_t dev) - из dev извлекается старший номер
  • MINOR(dev_t dev) - из dev извлекается младший номер
  • MKDEV(int major, int minor) - из старшего и младшего номеров создается dev

Подключение файла устройства к драйверу устройства осуществляется за два шага:

  1. Выполняется регистрация файлов устройств для диапазона <major, minor>
  2. Подключение операций, выполняемых над файлом устройства, к функциям драйвера устройства.

Первый шаг выполняется с помощью одного из следующих двух API, определенных в заголовке ядра linux/fs.h:

+ int register_chrdev_region(dev_t first, unsigned int cnt, char *name);
+ int alloc_chrdev_region(dev_t *first, unsigned int firstminor, unsigned int cnt, char *name);

С помощью первого API число cnt регистрируется как среди номеров файлов устройств, которые начинаются с first и именем файла name . С помощью второго API динамически определяется свободный старший номер и регистрируется число cnt среди номеров файлов устройств, начинающиеся с <the free major, firstminor>, с заданным именем файла name. В любом случае в директории /proc/devices указывается список имен с зарегистрированным старшим номером. Имея эту информацию, Светлана добавила в код первого драйвера следующее:

#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
 
static dev_t first; // Global variable for the first device number

В конструктор она добавила:

if (alloc_chrdev_region(&first, 0, 3, "Shweta") < 0)
{
    return -1;
}
printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));

В деструктор она добавила:

unregister_chrdev_region(first, 3);

И собрала все это вместе следующим образом:

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
 
static dev_t first; // Global variable for the first device number
 
static int __init ofcd_init(void) /* Constructor */
{
    printk(KERN_INFO "Namaskar: ofcd registered");
    if (alloc_chrdev_region(&first, 0, 3, "Shweta") < 0)
    {
        return -1;
    }
    printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));
    return 0;
}
 
static void __exit ofcd_exit(void) /* Destructor */
{
    unregister_chrdev_region(first, 3);
    printk(KERN_INFO "Alvida: ofcd unregistered");
}
 
module_init(ofcd_init);
module_exit(ofcd_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>");
MODULE_DESCRIPTION("Our First Character Driver");

Затем Светлана повторила обычные шаги, которые она узнала при изучении первого драйвера:

  • Собрала драйвер (файл .ko), выполнив команду make.
  • Загрузила драйвер с помощью команды insmod.
  • Выдала список загруженных модулей с помощью команды lsmod.
  • Выгрузила драйвер с помощью команды rmmod.

Подведем итог

Кроме того, перед выгрузкой драйвера она заглянула в директорий /proc/devices для того, чтобы с помощью команды cat /proc/devices найти зарегистрированный старший номер с именем "Shweta". Он там был. Тем не менее, она не смогла в директории /dev найти ни одного файла устройств с таким же старшим номером, т.к. она создала этот номер вручную с помощью команды mknod, а затем пыталась выполнить операции чтения и записи. Все эти действия показаны на рис.2.

Рис.2: Эксперименты с файлом символьного устройства

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


К предыдущей статье Оглавление К следующей статье