Библиотека сайта 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
), но, к удивлению, они, в отличие от любых других вызовов файловой системы, работают таким необычным образом. Что же тут необычного? Необычно, по крайней мере с точки зрения обычных файловых операций, то, что чтобы Светлана не записывала, при чтении она ничего не могла получить. Как она сможет решить эту проблему? Читайте следующую статью.
К предыдущей статье | Оглавление | К следующей статье |