Библиотека сайта rus-linux.net
Драйверы устройств в Linux
Часть 5: Файлы символьных устройств – создание файлов и операции с ними
Оригинал: "Device Drivers, Part 5: Character Device Files — Creation & Operations"Автор: Anil Kumar Pugalia
Дата публикации: April 1, 2011
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.
Эта статья является продолжением серии статей о драйверах устройств в Linux. В ней обсуждаются вопросы, касающиеся символьных драйверов и их реализации.
В моей предыдущей статье я упоминал, что даже при регистрации
диапазона устройств <major, minor>, файлы устройств в директории /dev не создаются — Светлана должна была создать их вручную с помощью команды mknod. Но при дальнейшем изучении Светлана выяснила, что файлы устройств можно создавать автоматически с помощью демона udev. Она также узнала о втором шаге подключения файла устройства к драйверу устройства — связывание операций над файлом устройства с функциями драйвера устройства. Вот что она узнала.
Автоматическое создание файлов устройств
Ранее, в ядре 2.4, автоматическое создание файлов устройств выполнялось самим ядром в devfs с помощью вызова соответствующего API. Однако, по мере того, как ядро развивалось, разработчики ядра поняли, что файлы устройств больше связаны с пользовательским пространством и, следовательно, они должны быть именно там, а не в ядре. Исходя из этого принципа, теперь для рассматриваемого устройства в ядре в /sys только заполняется соответствующая информация о классе устройства и об устройстве. Затем в пользовательском пространстве эту информацию необходимо проинтерпретировать и выполнить соответствующее действие. В большинстве настольных систем Linux эту информацию собирает демон udev, и создает, соответственно, файлы устройств.
Демон udev можно с помощью его конфигурационных файлов настроить
дополнительно и точно указать имена файлов устройств, права доступа к
ним, их типы и т. д. Так что касается драйвера, требуется с помощью API
моделей устройств Linux, объявленных в <linux/device.h>, заполнить в /sys соответствующие записи. Все остальное делается с помощью udev. Класс устройства создается следующим образом:
struct class *cl = class_create(THIS_MODULE, "<device class name>");
Затем в этот класс информация об устройстве (<major, minor>) заносится следующим образом:
device_create(cl, NULL, first, NULL, "<device name format>", ...);
Здесь, в качестве first указывается dev_t. Соответственно, дополняющими или
обратными вызовами, которые должны вызыватся в хронологически обратном порядке, являются:
device_destroy(cl, first); class_destroy(cl);
Посмотрите на рис.1 на записи /sys, созданные с помощью chardrv — запись <device class name> (<имя класса устройств>) и с помощью mynull — запись <device name format> (<формат имени устройства>). Здесь также показан файл устройства, созданный с помощью udev по записи <major>:<minor>, находящейся в файле dev.
Рис.1: Автоматическое создание файла устройства
В случае, если указаны несколько младших номеров minor, API
device_create() и device_destroy() могут вызываться в цикле и в этом случае окажется полезной строка <device name format> (<формат имени устройства>). Например, вызов функции device_create() в цикле с использованием индекса i будет иметь следующий вид:
device_create(cl, NULL, MKNOD(MAJOR(first), MINOR(first) + i), NULL, "mynull%d", i);
Операции с файлами
Независимо от того, что системные вызовы (или, в общем случае, операции с файлами), о которых мы рассказываем, применяются к обычным файлам, их также можно использовать и с файлами устройств. Т.е. мы можем сказать: если смотреть из пользовательского пространства, то в Linux почти все является файлами. Различие - в пространстве ядра, где виртуальная файловая система (VFS) определяет тип файла и пересылает файловые операции в соответствующий канал, например, в случае обычного файла или директория - в модуль файловой системы, или в соответствующий драйвер устройства в случае использования файла устройства. Мы будем рассматривать второй случай.
Теперь, чтобы VFS передала операции над файлом устройства в драйвер, ее следует об этом проинформировать. И это то, что называется регистрацией драйвером в VFS файловых операций. Регистрация состоит из двух этапов. (Код, указываемый в скобках, взят из кода "null -драйвера", который приведен ниже).
Во-первых, давайте занесем нужные нам файловые операции (my_open, my_close, my_read, my_write, …) в структуру, описывающую файловые операции (struct file_operations pugs_fops) и ею инициализируем структуру, описывающую символьное устройство (struct cdev c_dev); используем для этого обращение cdev_init().
Затем передадим эту структуру в VFS с помощью вызова
cdev_add(). Обе операции cdev_init() и
cdev_add() объявлены в <linux/cdev.h>. Естественно, что также надо закодировать фактические операции с файлами (my_open, my_close, my_read, my_write).
Итак, для начала, давайте все это сделаем как можно проще - скажем, максимально просто в виде "null драйвера".
null - драйвер
#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>
#include <linux/device.h>
#include <linux/cdev.h>
static dev_t first; // Global variable for the first device number
static struct cdev c_dev; // Global variable for the character device structure
static struct class *cl; // Global variable for the device class
static int my_open(struct inode *i, struct file *f)
{
printk(KERN_INFO "Driver: open()\n");
return 0;
}
static int my_close(struct inode *i, struct file *f)
{
printk(KERN_INFO "Driver: close()\n");
return 0;
}
static ssize_t my_read(struct file *f, char __user *buf, size_t
len, loff_t *off)
{
printk(KERN_INFO "Driver: read()\n");
return 0;
}
static ssize_t my_write(struct file *f, const char __user *buf,
size_t len, loff_t *off)
{
printk(KERN_INFO "Driver: write()\n");
return len;
}
static struct file_operations pugs_fops =
{
.owner = THIS_MODULE,
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write
};
static int __init ofcd_init(void) /* Constructor */
{
printk(KERN_INFO "Namaskar: ofcd registered");
if (alloc_chrdev_region(&first, 0, 1, "Shweta") < 0)
{
return -1;
}
if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)
{
unregister_chrdev_region(first, 1);
return -1;
}
if (device_create(cl, NULL, first, NULL, "mynull") == NULL)
{
class_destroy(cl);
unregister_chrdev_region(first, 1);
return -1;
}
cdev_init(&c_dev, &pugs_fops);
if (cdev_add(&c_dev, first, 1) == -1)
{
device_destroy(cl, first);
class_destroy(cl);
unregister_chrdev_region(first, 1);
return -1;
}
return 0;
}
static void __exit ofcd_exit(void) /* Destructor */
{
cdev_del(&c_dev);
device_destroy(cl, first);
class_destroy(cl);
unregister_chrdev_region(first, 1);
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получила список всех загруженных модулей. - С помощью команды
cat /proc/devices. получила список используемых старших номеров major. - Поэкспериментировала с "null драйвером" (подробности смотрите на рис.2).
- Выгрузила драйвер с помощью команды
rmmod.
Рис.2: Эксперименты с "null драйвером"
Подведем итог
Светлана олпределенно была довольна; она сама написала символьный
драйвер, который работает точно также, как и стандартный файл
устройства /dev/null. Чтобы понять, что это значит, проверьте
пару <major, minor> для файла /dev/null, а также выполните с ним команды echo и cat.
Но Светлану стала беспокоить одна особенность. В своем драйвере она использовала свои собственные вызовы (my_open, my_close, my_read, my_write), но, к удивлению, они, в отличие от любых других вызовов файловой системы, работают таким необычным образом. Что же тут необычного? Необычно, по крайней мере с точки зрения обычных файловых операций, то, что чтобы Светлана не записывала, при чтении она ничего не могла получить. Как она сможет решить эту проблему? Читайте следующую статью.
| К предыдущей статье | Оглавление | К следующей статье |
