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

UnixForum





Библиотека сайта 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");

Светлана повторила обычный процесс сборки, добавив при этом некоторые новые проверочные шаги, а именно:

  1. Собрала драйвер (файл .ko) с помощью запуска команды make.
  2. Загрузила драйвер с помощью команды insmod.
  3. С помощью команды lsmod получила список всех загруженных модулей.
  4. С помощью команды cat /proc/devices. получила список используемых старших номеров major.
  5. Поэкспериментировала с "null драйвером" (подробности смотрите на рис.2).
  6. Выгрузила драйвер с помощью команды rmmod.

Рис.2: Эксперименты с "null драйвером"

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

Светлана олпределенно была довольна; она сама написала символьный драйвер, который работает точно также, как и стандартный файл устройства /dev/null. Чтобы понять, что это значит, проверьте пару <major, minor> для файла /dev/null, а также выполните с ним команды echo и cat.

Но Светлану стала беспокоить одна особенность. В своем драйвере она использовала свои собственные вызовы (my_open, my_close, my_read, my_write), но, к удивлению, они, в отличие от любых других вызовов файловой системы, работают таким необычным образом. Что же тут необычного? Необычно, по крайней мере с точки зрения обычных файловых операций, то, что чтобы Светлана не записывала, при чтении она ничего не могла получить. Как она сможет решить эту проблему? Читайте следующую статью.


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